<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>

          和女朋友爭(zhēng)論了1個(gè)小時(shí),在vue用throttle居然這么黑盒?

          共 6206字,需瀏覽 13分鐘

           ·

          2021-01-28 18:13

          開(kāi)篇

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

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

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

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

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

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

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

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

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

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

          vue throttle

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

          初舞臺(tái)

          問(wèn)題形態(tài)一:

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

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

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

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

          進(jìn)一步拆分

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

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

          升溫

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

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

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

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

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


          搜到了一些正確的打開(kāi)方式。


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

          emm。查不到,那開(kāi)始思考?為什么這個(gè)寫法不行?等等,我剛剛說(shuō)了什么?把時(shí)間倒退 3.3 秒前... (為什么是3.3秒,因?yàn)槿祟惼骄f(shuō)話語(yǔ)速是200字/分鐘)

          寫法?對(duì)啊,是寫法,這個(gè)只是 vue 的模板語(yǔ)法,真實(shí)瀏覽器運(yùn)行的并不是這個(gè)樣子啊。

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

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


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

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


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

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

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

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

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

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



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

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

          所以真相只有一個(gè)

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

          進(jìn)階

          那我們通過(guò) vue 的源碼來(lái)探索一下,vue 的模板解析的原理,來(lái)加深一些我們的印象。

          由于這里部分是 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)?{
          ??//?判斷如果是個(gè)方法或者是函數(shù)表達(dá)式,就返回?value
          ??if?(isMethodPath?||?isFunctionExpression)?{
          ????return?handler.value
          ??}
          ??/*?istanbul?ignore?if?*/
          ??if?(__WEEX__?&&?handler.params)?{
          ????return?genWeexHandler(handler.params,?handler.value)
          ??}
          ??//?如果不滿足以上的情況就會(huì)包一層方法
          ??return?`function($event){${
          ????isFunctionInvocation???`return?${handler.value}`?:?handler.value
          ??}
          }`
          ?//?inline?statement
          }?else?{
          ?...
          }

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

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

          通過(guò)以上代碼我們能明白,如果這個(gè)事件的寫法,滿足 isMethodPath 或者滿足isFunctionExpression。那么我們?cè)谑录械膶懛〞?huì)被直接返回,否則的話,會(huì)被包一層 function。

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

          https://jex.im/regulex/


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

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


          簡(jiǎn)單來(lái)講就是寫一個(gè)匿名函數(shù), (xx) => {} 或者 funciton(){}

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

          還記得 vue 的官方教程中,我們寫模板語(yǔ)法的時(shí)候,以下兩種方式是等價(jià)的。

          1.

          2.

          因?yàn)樵诰幾g的時(shí)候,他們會(huì)分別被編譯成以下形態(tài)。

          xxx.onclick?=?handle

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

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

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

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

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


          我們來(lái)畫個(gè)圖總結(jié)一下。


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

          throttle(download(xxx))

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

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

          最佳

          所以上述說(shuō)了這么多,我們需要有個(gè)最佳的實(shí)踐方案。



          升華

          那么我們?cè)賮?lái)解釋一個(gè)問(wèn)題,外部導(dǎo)入和內(nèi)部 methods 的差異性?



          先說(shuō)以上寫法是會(huì)出錯(cuò)的。

          因?yàn)樵谖覀兡0逯袑懙姆椒?,必須?methods 中的方法,否則就會(huì)找不到。

          也許這樣我們直接像在模板中寫 throttle 就必須將這個(gè)函數(shù)定義在 methods 中,這樣是非常不友好的,因?yàn)闀?huì)反直覺(jué),對(duì)于太久沒(méi)寫的我(T T忘記了)。

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

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

          以上模板代碼會(huì)被編譯成這個(gè)樣子。

          /*?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 只能訪問(wèn) vue 中定義的方法與變量。

          試探邊緣

          我們?cè)賮?lái)探究一下 vue 3.0 是否對(duì)這個(gè)有改動(dòng)。

          答案是: 沒(méi)有。

          我特地去找了 ?@vue/compiler-sfc 進(jìn)行了測(cè)試。

          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é)尾

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

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

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

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  免费看国产黄色 | 台湾无码免费电影 | 啪啪啪啪网 | 亚洲激情区 | 欧美8区 奇米成人 |