<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          和女朋友爭論了1個小時,在vue用throttle居然這么黑盒?

          共 6575字,需瀏覽 14分鐘

           ·

          2021-01-31 02:39

          開篇

          首先我們都知道,throttle(節(jié)流)debounce(防抖) 是性能優(yōu)化的利器。

          本文會簡單介紹一下這兩個的概念,但是并不會對這兩個函數(shù)再進行老生常談地說原理了,而是會說它和 vue 之間的愛恨情仇~,但是在步入正題以前,我們得先知道它的一些簡介。

          函數(shù)節(jié)流(throttle) 是指一定時間內(nèi) js 方法只運行一次。

          節(jié)流節(jié)流就是節(jié)省水流的意思,就像水龍頭在流水,我們可以手動讓水流(在一定時間內(nèi))小一點,但是他會一直在流。

          函數(shù)節(jié)流的情況下,函數(shù)將每隔 n 秒執(zhí)行一次,常見的場景為:

          • DOM 元素的拖拽功能實現(xiàn)(mousemove)
          • 搜索聯(lián)想(keyup)
          • 計算鼠標移動的距離(mousemove)
          • Canvas 模擬畫板功能(mousemove)
          • 射擊游戲的 mousedown/keydown 事件(單位時間只能發(fā)射一顆子彈)

          函數(shù)防抖(debounce) 只當(dāng)有足夠的空閑時間,才運行代碼一次。

          比如生活中的坐公交,就是一定時間內(nèi),如果有人陸續(xù)刷卡上車,司機就不會開車。只有別人沒刷卡了,司機才開車。(其實只要記住了節(jié)流的思想就能通過排除法判斷節(jié)流和防抖了)

          函數(shù)防抖的情況下,函數(shù)將一直推遲執(zhí)行,造成不會被執(zhí)行的效果,常見的場景為:

          • 每次 resize/scroll 觸發(fā)統(tǒng)計事件
          • 文本輸入的驗證(連續(xù)輸入文字后發(fā)送 AJAX 請求進行驗證,驗證一次就好)

          vue throttle

          那么它們和 vue 結(jié)合會擦除怎么樣的火花呢?你有了以上的基礎(chǔ)知識后,下面正片就正式開始了~ 最近和女朋友談了下 vue throttle 相關(guān)的問題,一開始以為是簡單的的東西,沒想到真的討論了1個小時.... 前方高能硬核,層層遞進涉及到 vue 源碼。

          初舞臺

          問題形態(tài)一:

          <input?@input="download"?/>

          ...
          methods:?{
          ?download:?()?{
          ??this.throttle(xxx)
          ?},
          }
          ...

          我們來分析為什么這樣是不行,首先我們來看看正常情況下 throttle 是怎么寫的,再來拆分拆分 throttle 。

          window.addEventListener('mousemove',?throttle(xxx));

          進一步拆分

          const?handleMove?=?throttle(xxx)
          window.addEventListener('mousemove',?handleMove);

          我們一直調(diào)用的是 handleMove 方法,而 throttle 的原理是依賴于 JS 的閉包原理,依賴于handleMove 中的閉包變量。而如果你在 handleMove 外層再套一層 download 函數(shù),賊無法讓 handleMove 中的閉包內(nèi)的變量進行了緩存,因此也失去了throttle 的效果。

          升溫

          那我們來改造一下,看起來是正確地形態(tài)。

          <input?@input="throttle(download(xxx))">

          ...
          methods:?{
          ???download:?(xxx)?{
          ????..
          ???},
          ???throttle:?...
          }
          ...

          開始一頓疑惑,沒錯呀,這的確就是 throttle 正確寫法的樣子,為什么這樣就不行呢,再加上好久沒有寫 vue 的黑魔法了,一時不知道如何解釋。

          趕緊偷偷查資料,默默地在谷歌輸入下了 vue debounce ...


          搜到了一些正確的打開方式。


          發(fā)現(xiàn)它這樣是可以使用的,而我將他寫到模板中不行。

          emm。查不到,那開始思考?為什么這個寫法不行?等等,我剛剛說了什么?把時間倒退 3.3 秒前... (為什么是3.3秒,因為人類平均說話語速是200字/分鐘)

          寫法?對啊,是寫法,這個只是 vue 的模板語法,真實瀏覽器運行的并不是這個樣子啊。

          感覺有思路了!快快快,快找 vue 模板編譯完后的樣子

          在瀏覽器輸入下下了vue 模板 在線這幾個關(guān)鍵詞。


          很快我們就查到了這個地址 https://template-explorer.vuejs.org/

          我們將我們的模板輸入到左側(cè)的輸入框。


          我們得到了這樣的一個解析后的 render 函數(shù)。

          function?render()?{
          ??with(this)?{
          ????return?_c('input',?{
          ??????on:?{
          ????????"input":?function?($event)?{
          ??????????throttle(download(xxx));
          ????????}
          ??????}
          ????})
          ??}
          }

          在這里我們看到,我們能大概知道,通過解析后,input 監(jiān)聽方法已經(jīng)被包裹了一層函數(shù)。也很容猜出,最終解析成真正的綁定的函數(shù)會變成以下這個樣子。

          xxxx.addEventListener('input',?function?($event)?{
          ??throttle(download(xxx));
          })

          如果是這個樣子的 throttle ,我相信有了解 throttle 的朋友們一眼就能看出來,這樣子的 throttle 是完全不起效果的。

          而我們剛才資料中查詢到的方式呢?



          function?render()?{
          ??with(this)?{
          ????return?_c('input',?{
          ??????on:?{
          ????????"input":?click
          ??????}
          ????})
          ??}
          }

          這種方式下,vue 是直接傳遞綁定的實踐方法的,并不會有任何包裝。

          所以真相只有一個

          果然是 vue 模板的黑魔法?。。。?!

          進階

          那我們通過 vue 的源碼來探索一下,vue 的模板解析的原理,來加深一些我們的印象。

          由于這里部分是 vue 事件編譯相關(guān)的代碼,我們很容易地找到了 vue 源碼(目前看的是 v2.6.12版本)的位置。

          https://github.com/vuejs/vue/blob/v2.6.12/src/compiler/codegen/events.js#L96

          我們看到 vue 源碼中含關(guān)于事件生成是以下代碼。

          const?fnExpRE?=?/^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
          const?fnInvokeRE?=?/\([^)]*?\);*$/
          const?simplePathRE?=?/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
          ...

          const?isMethodPath?=?simplePathRE.test(handler.value)
          const?isFunctionExpression?=?fnExpRE.test(handler.value)
          const?isFunctionInvocation?=?simplePathRE.test(handler.value.replace(fnInvokeRE,?''))

          if?(!handler.modifiers)?{
          ??//?判斷如果是個方法或者是函數(shù)表達式,就返回?value
          ??if?(isMethodPath?||?isFunctionExpression)?{
          ????return?handler.value
          ??}
          ??/*?istanbul?ignore?if?*/
          ??if?(__WEEX__?&&?handler.params)?{
          ????return?genWeexHandler(handler.params,?handler.value)
          ??}
          ??//?如果不滿足以上的情況就會包一層方法
          ??return?`function($event){${
          ????isFunctionInvocation???`return?${handler.value}`?:?handler.value
          ??}
          }`
          ?//?inline?statement
          }?else?{
          ?...
          }

          由于我們的是沒有 修飾符(modifiers)的,因此我們關(guān)于含有修飾符的代碼注釋了,防止不必要的干擾。

          為了能更好地梳理情況,我們將 isMethodPath 稱作方法路徑,而將 isFunctionExpression稱作函數(shù)表達式,isFunctionInvocation稱為函數(shù)調(diào)用(雖然英文就是這個意思,但是為了大家都能看明白吧)

          通過以上代碼我們能明白,如果這個事件的寫法,滿足 isMethodPath 或者滿足isFunctionExpression。那么我們在事件中的寫法會被直接返回,否則的話,會被包一層 function。

          我們一一來看看關(guān)于事件的情景。isMethodPath 的判斷方法是const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/, 乍一看有點長,我們通過可視化工具分析分析。

          https://jex.im/regulex/


          通過可視化可以看出,我們的事件方式如果是以上形態(tài)就會通過正則的檢驗(例如 handle, handle['xx'], handle["xx"],handle[xxx], handle[0], console.log )這些情況都是不會被包裹一層函數(shù)。

          還有一種情況就是 正則 const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/。


          簡單來講就是寫一個匿名函數(shù), (xx) => {} 或者 funciton(){}。

          除了以上兩種情況之外的所有情況都會被包含一層方法。

          還記得 vue 的官方教程中,我們寫模板語法的時候,以下兩種方式是等價的。

          1.

          2.

          因為在編譯的時候,他們會分別被編譯成以下形態(tài)。

          xxx.onclick?=?handle

          xxx.onclick?=?function($event)?{
          ?return?handler();
          }

          通過包一層函數(shù)來達到相同的目的,現(xiàn)在你能明白了吧?在 vue 中寫,怎么寫都不會出問題,有時候可能是你偶然手誤,它都講這些情況考慮在內(nèi)了,就像是吃飯一樣,飯已經(jīng)喂到我們嘴邊了。

          而在被函數(shù)包裹的情況又分了兩種情況。

          isFunctionInvocation ? return ${handler.value} : handler.value

          isFunctionInvocation的檢測就是將函數(shù)調(diào)用的部分去掉,如果去掉后,滿足方法路徑的情況,那么就會多一個 return


          我們來畫個圖總結(jié)一下。


          而我們的情況是怎么樣的呢?

          throttle(download(xxx))

          顯然我們既不滿足方法路徑、也不滿足函數(shù)表達式,因此就會出現(xiàn)我們上述的 "bug",讓我們的 throttle 失效了。

          至此,我們已經(jīng)清楚了關(guān)于 vue 中的黑魔法了,vue 給我們帶來便利的同時,我們運用的不好,或者說不理解它的一些思想原理,就會發(fā)生一些神奇的事情。

          最佳

          所以上述說了這么多,我們需要有個最佳的實踐方案。



          升華

          那么我們再來解釋一個問題,外部導(dǎo)入和內(nèi)部 methods 的差異性?



          先說以上寫法是會出錯的。

          因為在我們模板中寫的方法,必須是 methods 中的方法,否則就會找不到。

          也許這樣我們直接像在模板中寫 throttle 就必須將這個函數(shù)定義在 methods 中,這樣是非常不友好的,因為會反直覺,對于太久沒寫的我(T T忘記了)。

          那為什么不可以直接寫在模板上面呢,其實這也和 vue 的編譯相關(guān)的,因為 vue 模板中的方法都會被編譯成 _vm.xxx,舉個例子。

          <template>
          ?<input?@click="debounce(download(xxx))"?/>
          template>

          以上模板代碼會被編譯成這個樣子。

          /*?template?*/
          var?__vue_render__?=?function()?{
          ????var?_vm?=?this;
          ????var?_h?=?_vm.$createElement;
          ????var?_c?=?_vm._self._c?||?_h;
          ????return?_c("input",?{
          ??????on:?{
          ????????click:?function($event)?{
          ??????????_vm.debounce(_vm.download(_vm.xxx));
          ????????}
          ??????}
          ????})
          };

          以上才是真正在瀏覽器執(zhí)行的代碼,所以我們可以很清楚地看到 _vm 中是不存在 debounce,這也是 template 只能訪問 vue 中定義的方法與變量。

          試探邊緣

          我們再來探究一下 vue 3.0 是否對這個有改動。

          答案是: 沒有。

          我特地去找了 ?@vue/compiler-sfc 進行了測試。

          const?sfc?=?require('@vue/compiler-sfc');

          const?template?=?sfc.compileTemplate({
          ????filename:?'example.vue',
          ????source:?'',
          ????id:?''
          });
          //?output
          import?{?createVNode?as?_createVNode,?openBlock?as?_openBlock,?createBlock?as?_createBlock?}?from?"vue"

          export?function?render(_ctx,?_cache)?{
          ??return?(_openBlock(),?_createBlock("input",?{
          ????onInput:?_cache[1]?||?(_cache[1]?=?$event?=>?(_ctx.throttle(_ctx.download(_ctx.xxx))));

          結(jié)尾

          從這一次的探索來看,vue 自身模板語言需要很多心智模型,而在本實例中,vue給了我們很多語法糖,讓我們沉醉其中,不得不說這樣的方式很舒服,但是總有一天我們獨自承受這些苦楚。

          這就不得不討論到 React 的 JSX,雖然它麻煩,對我們很殘酷,但是我們對自身的行為更加可控(雖然 vue 也可以用 JSX,但是 Templates 依舊是是官方推薦的方法)我也能理解 vue 上述的這些表現(xiàn),因為它幫我們做了很多處理,對于某些情況它需要給我們注入 $event, 也就是我們常用的事件對象,但是別人幫我們手把手處理了這些事情,也使得我們慢慢忘記了它原本的形態(tài),一旦出現(xiàn)問題,會讓我們舉手無措。而 JSX 中則要求我們寫出完整的代碼,這樣的方式使得我們寫什么都需要付出額外的勞動,也許像 vue 官方文檔中所說,談?wù)?JSX 和 vue 的 Templates 是膚淺的的,但是不管怎么樣,每個人都會對它有不一樣的理解,不一樣的喜好,所以自己總結(jié)了一下。

          都學(xué)就完si兒了 :)

          福利時刻

          本次粉絲福利為免費贈送Web前端工程師修煉之道》3本,這是一本完整的Web?設(shè)計和制作的入門指南。詳解WEB前端基礎(chǔ)知識,如HTMLCSS、JavaScriptWeb圖像制作等等。本書分為六部分,每一部分都是Web開發(fā)的一個重要方面。

          • 開獎時間: 2021?年 01?月?25?日 10:30
          • 一等獎: 《Web前端工程師修煉之道》1本?(3人)
          • 二等獎: 6.66 元紅包?(2人)

          ???交流討論
          歡迎關(guān)注公眾號?秋風(fēng)的筆記,主要記錄日常中覺得有意思的工具以及分享開發(fā)實踐,保持深度和專注度。
          最近整理了一些大廠的相關(guān)內(nèi)推信息,基本上都是部門直招,包含美團、字節(jié)、阿里飛豬、騰訊、快手、58集團、哈嘍出行、京東等多個公司,有興趣的可以公眾號后臺回復(fù)"面試",加入群聊,一起交流面試學(xué)習(xí)~

          點贊、在看、分享是對作者最大的支持??

          瀏覽 32
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲综合在线婷婷 | 豆花无码视频一区二区三区四区 | 丁香婷婷五月激情 | 日日日av| 天天射天天干天天做 |