小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

越來越受歡迎的Vue想學(xué)么,90后小姐姐今兒來教你

 怡紅公子0526 2022-12-24 發(fā)布于北京
摘要:Vue的相關(guān)技術(shù)原理成為了前端崗位面試中的必考知識點,掌握 Vue 對于前端工程師來說更像是一門“必修課”。

本文原作者為尹婷,擅長前端組件庫研發(fā)和微信機器人。

我們發(fā)現(xiàn), Vue 越來越受歡迎了。

不管是BAT大廠,還是創(chuàng)業(yè)公司,Vue都被廣泛的應(yīng)用。對比Angular 和 React,三者都是非常優(yōu)秀的前端框架,但從 GitHub 上來看,Vue 已經(jīng)達(dá)到了 170 萬的 Star。Vue的相關(guān)技術(shù)原理也成為了前端崗位面試中的必考知識點,掌握 Vue 對于前端工程師來說更像是一門“必修課”。為此,華為云社區(qū)邀請了90后前端開發(fā)工程師尹婷帶來了《Vue3.0新特性介紹以及搭建一個vue組件庫》的分享。

了解Vue3.0先從六大特性說起

Vue.js 是一個JavaScriptMVVM庫,是一套構(gòu)建用戶界面的漸進(jìn)式框架。在2019年10月05日凌晨,Vue3的源代碼alpha。目前已經(jīng)發(fā)布正式版,作者表示, Vue 3.0具有六大特性:Tree Shaking;Composition;Fragment;Teleport;Suspense;渲染Performance。渲染Performance主要是框架內(nèi)部的性能優(yōu)化,相對比較底層,本文會主要為大家介紹前四個特性的解讀。

Tree Shaking

大多數(shù)編譯器都會為我們的代碼進(jìn)行一個死代碼的去除工作。首先我們要了解一下,什么是死代碼呢?

以下幾個特性的代碼,我們把它稱之為死代碼:代碼不會被執(zhí)行,不可到達(dá);代碼執(zhí)行的結(jié)果不會被用到;代碼只會影響死變量(只寫不讀)。比如我們給一個變量賦值,但是并沒有去用這個變量,那么這就是一個死變量。這就是在我們定義階段會把它去除的一部分,比如說roll up消除死代碼的工作。

如上圖示例,左邊是開發(fā)的源碼提供的兩個函數(shù),但最終只用到了baz函數(shù)。在最后打包的時候,會把foo函數(shù)去除掉,只把baz這個函數(shù)打包進(jìn)瀏覽器里面運行。Tree Shaking是消除死代碼的一種方式,更關(guān)注于無用模塊的消除,消除那些引用了但并沒有被使用的模塊。

左邊這塊代碼,export有兩個函數(shù),一個是post,一個是get,但是在我們生產(chǎn)里邊真正使用到只有post。那么rollup在打包之后,就會直接消除掉get的函數(shù),然后只把post的函數(shù)打包進(jìn)入我們的生產(chǎn)里。除了rollup支持這個特性外,webpack也支持。

接下來,我們看一下VUE3.0對Tree Shaking的支持都做了哪些事情?

首先以VUE2和VUE3對nextTick的使用進(jìn)行對比:VUE2把nextTick掛載到VUE實例上的一個global API式;VUE3先把nextTick模塊剔除,在要使用的時候,再把這個模塊引入。

通過這個對比,我們可以看到使用VUE2的時候,即使沒有nextTick或者其他方法,但由于它是一個GLOBA API,它一定會被掛載到一個實例上,最后打包生產(chǎn)代碼的時候,會把這個函數(shù)給打包進(jìn)去,這一段代碼進(jìn)而也會影響到文件體積。在VUE3.0如果不需要這個模塊的話,最后打包的這個文件里邊就不會有這一塊代碼。通過這種方式就減少了最后生產(chǎn)代碼的體積。

當(dāng)然,不只是nextTick,在VUE3.0內(nèi)部也做了其他很多tree-shaking。例如:如果不使用keep-alive組件或v-show指令,它會少引入很多跟keep-alive或者v-show不相關(guān)的包。

上圖為Vue2.0的這段代碼,左邊是引入utils函數(shù),然后把這個函數(shù)指為mixins。這一段代碼是在Vue2里邊是最常用到的,但這段代碼是有問題的。
如果對這個項目不熟悉,第一次看到這個代碼的時候,由于不知道這個utils里邊有哪些屬性和方法,也就是說這個mixins對于開發(fā)者就是個黑盒。很容易遇到一種場景:在開發(fā)組件初期,應(yīng)用了mixins的一個方法,現(xiàn)在不需要使用該方法了,在刪除的過程發(fā)現(xiàn)不知道其他的地方是否引用過mixins其他的屬性和方法。

Composition

如果使用的是Vue3.0 的Composition,該怎么規(guī)避這個問題呢?如上圖所示,假設(shè)它是一個組件實例,我們使用useMouse函數(shù)并返回了X和Y兩個變量。從左邊代碼可以看到useMouse函數(shù)就是根,它監(jiān)聽了鼠標(biāo)的移動事件之后,返回了鼠標(biāo)的XY坐標(biāo)。通過這種方式來組織代碼,就可以很明確的知道這個函數(shù)返回的變量和改變的值。

接下來我們再看一個Composition的例子:左邊是在Vue2中最常用的一段代碼,首先在data里邊聲明first name和last name,然后在回帖的時候去請求接口,拿到接口返回到值,在computed之后獲取他的full Name。那么,這段代碼的問題是什么呢?

這里的computed,因為我們不知道返回的full Name的邏輯是什么。在獲取了data之后,是希望通過data的返回值來拿到它的first name和last name,然后來獲取它的full name。但是這一段代碼的邏輯在獲取接口之后就已經(jīng)斷掉,這就是Vue2.0 設(shè)計不合理的一個地方,導(dǎo)致我們的邏輯是分裂派的,分裂在個配置下。那么,如果用Composition的話,怎么樣實現(xiàn)呢?

請求接口之后,直接拿到它的返回數(shù)據(jù),然后把這個返回數(shù)據(jù)的值賦給computed函數(shù)里,這里就可以拿到full Name。通過這段代碼可以看到,邏輯是更加的聚合了。

如何做到使用useMouse函數(shù),里邊的變量也是可響應(yīng)的。在Vue 3.0中提供了兩個函數(shù):reactive和ref。reactive可以傳一個對象進(jìn)去,然后這個函數(shù)返回之后的state,是可響應(yīng)的;ref是直接傳一個值進(jìn)去,然后返回到看法對象,它也是可響應(yīng)的。如果我們在setup函數(shù)里邊返回一個可響應(yīng)值的對象,是可以在字符串模板渲染的時候使用。比如,有時候我們直接在修改data的時候,視圖也會相應(yīng)的改變。

Vue2中,一般會采用mixins來復(fù)用邏輯代碼,但存在一些問題:例如代碼來源不清晰、方法屬性等沖突?;诖?,在vue3中引入了Composition API(組合API),使用純函數(shù)分隔復(fù)用代碼,和React中的hooks的概念很相似。

Composition的優(yōu)點是暴露給模板的屬性來源清晰,它是從函數(shù)返回的;第二,可以進(jìn)行邏輯重用;第三,返回值可以被任意的命名,不存在秘密空間的沖突;第四,沒有創(chuàng)建額外的組件實力帶來的性能損耗。

以前我們?nèi)绻胍@取一個響應(yīng)式的data,我們必須要把這個data放在component里邊,然后在data里邊進(jìn)行聲明,這樣的話才能使這個對象是可響應(yīng)的,現(xiàn)在可直接使用reactive和ref函數(shù)就可以使被保變成可響應(yīng)的。

Fragment

在書寫vue2時,由于組件必須只有一個根節(jié)點,很多時候會添加一些沒有意義的節(jié)點用于包裹。Fragment組件就是用于解決這個問題的(這和React中的Fragment組件是一樣的)。

Fragment其實就是在Vue2的一個組間里邊,它的template必須要有一個根的DIV把它包住,然后再寫里邊的you。在Vue3,我們就不需要這個根的DIV來把這個組件包住了。上圖就是2和3的對比。

Teleport

Teleport其實就是React中的Portal。Portal 提供了一種將子節(jié)點渲染到存在于父組件以外的 DOM 節(jié)點的優(yōu)秀的方案。Teleport提供一個Teleport的組件,會指定一個目標(biāo)的元素,比如說這里指定的是body,然后Teleport任何的內(nèi)容都會渲染到這個目標(biāo)元素中,也就是說下面的這一部分Teleport代碼,它會直接渲染到body。

那么關(guān)于Teleport應(yīng)用的位置,我們可以為大家舉個例子來說明一下。比如說我們在做組件的時候,經(jīng)常會實現(xiàn)一個dialog。dialog的背景是一個黑的鋪滿全屏DIV,我們對它的布局是position: absolute。如果父級元素是relative布局,我們的這個背景層就會受它的父元素的影響。那么此時,如果用Teleport直接把父組件定為body,這樣它就不會再受到副組件元素樣式的影響,就可以確認(rèn)一個我們想要的黑色背景畫。

下面我寫一下react和vue的diff算法的比對,我是一邊寫代碼,一邊寫文章,整理一下思路。注:這里只討論tag屬性相同并且多個children的情況,不相同的tag直接替換,刪除,這沒啥好寫的。

用這個例子來說明:

簡單diff,把原有的刪掉,把更新后的插入。

變化前后的標(biāo)簽都是li,所以只用比對vnodeData和children即可,復(fù)用原有的DOM。

先只從這個例子出發(fā),我只用遍歷舊的vnode,然后把舊的vnode和新的vnode patch就行。

這樣就省掉移除和新增dom的開銷,現(xiàn)在的問題是,我的例子剛好是新舊vnode數(shù)量一樣,如果不一樣就有問題,示例改成這樣:

實現(xiàn)思路改成:先看看是舊的長度長,還是新的長,如果舊的長,我就遍歷新的,然后把多出來的舊節(jié)點刪掉,如果新的長,我就遍歷舊的,然后多出來的新vnode加上。

仍然有可優(yōu)化的空間,還是下面這幅圖:

通過我們上面的diff算法,實現(xiàn)的過程會比對 preve vnode和next vnode,標(biāo)簽相同,則只用比對vnodedata和children。發(fā)現(xiàn)

? 標(biāo)簽的子節(jié)點(文本節(jié)點a,b,c)不同,于是分別刪除文本節(jié)點a,b,c,然后重新生成新的文本節(jié)點c,b,a。但是實際上這幾個

? 只是位置不同,那優(yōu)化的方案就是復(fù)用已經(jīng)生成的dom,把它移動到正確的位置。

怎么移動?我們使用key來將新舊vnode做一次映射。

首先我們找到可以復(fù)用的vnode,可以做兩次遍歷,外層遍歷next vnode,內(nèi)層遍歷prev vnode

如果next vnode和prev vnode只是位置移動,vnodedata和children沒有任何變動,調(diào)用patchVnode之后不會有任何dom操作。
接下來只需要把這個key相同的vnode移動到正確的位置即可。我們的問題變成了怎么移動。

首先需要知道兩個事情:

  • 每一個prev vnode都引用了一個真實dom節(jié)點,每個next vnode這個時候都沒有真實dom節(jié)點。
  • 調(diào)用patchVnode的時候會把prevVnode引用的真實Dom的引用賦值給nextVnode,就像這樣:

還是拿上面的例子,外層遍歷next vnode,遍歷第一個元素的時候, 第一個vnode是li?,然后去prev vnode里找,在最后一個節(jié)點找到了,這里外層是第一個元素,不做任何移動的操作,我們記錄一下這個vnode在prevVnode中的索引位置lastIndex,接下來在遍歷的時候,如果j<lastIndex,說明原本prevVnode在前面的元素,在nextVnode中變到了后面來了,那么我們就把prevVnode[j]放到nextVnode[i-1]的后面。

這里多說一句,dom操作的api里,只有insertBefore(),沒有insertAfter()。也就是說只有把某個dom插入到某個元素前面這個方法,沒有插入到某個元素后面這個方法,所以我們只能用insertBefore()。那么思路就變成了,當(dāng)j<lastIndex的時候,把prevChildren[j]插入到nextVnode[i-1]的真實dom的后面元素的前面。

當(dāng)j>=lastIndex的時候,說明這個順序是正確的的,不用移動,然后把lastIndex = j;
也就是說,只把prevVnode中后面的元素往前移動,原本順序是正確的就不變。
現(xiàn)在我們的diff的代碼變成了這樣:

同樣的問題,如果新舊vnode的元素數(shù)量一樣,那就已經(jīng)可以工作了。接下來要做的就是新增節(jié)點和刪除節(jié)點。

首先是新增節(jié)點,整個框架中將vnode掛載到真實dom上都調(diào)用patch函數(shù),patch里調(diào)用createElm來生成真實dom。按照上面的實現(xiàn),如果nextVnode中有一個節(jié)點是prevVnode中沒有的,就有問題:

在prevVnode中找不到li(d),那我們需要調(diào)用createElm掛在這個新的節(jié)點,因為這里的節(jié)點需要超入到li(b)和li?之間,所以需要用insertBefore()。在每次遍歷nextVnode的時候用一個變量find=false表示是否能夠在prevVnode中找到節(jié)點,如果找到了就find=true。如果內(nèi)層遍歷后find是false,那說明這是一個新的節(jié)點。

我們的createElm函數(shù)需要判斷一下第四個參數(shù),如果沒有就是用appendChild直接把元素放到父節(jié)點的最后,如果有第四個參數(shù),則需要調(diào)用insertBefore來插入到正確的位置。

接下來要做的是刪除prevVnode多余節(jié)點:

在nextVnode中已經(jīng)沒有l(wèi)i(d)了,我們需要在執(zhí)行完上面所講的所有流程后在遍歷一次prevVnode,然后拿到nextVnode里去找,如果找不到相同key的節(jié)點,那就說明這個節(jié)點已經(jīng)被刪除了,我們直接用removeChild方法刪除Dom。

完整的代碼:https://github.com/TingYinHelen/tempo/blob/main/src/platforms/web/patch.js在react-diff分支(目前有可能代碼倉庫還沒有開源,等我實現(xiàn)更完善的時候會開源出來,項目結(jié)構(gòu)可能有變化,看tempo倉庫就行)

這里我的代碼實現(xiàn)的diff算法很明顯看出來時間復(fù)雜度是O(n2)。那么這里在算法上依然又可以優(yōu)化的空間,這里我把nextChildren和prevChildren都設(shè)計成了數(shù)組的類型,這里可以把nextChildren、prevChildren設(shè)計成對象類型,用戶傳入的key作為對象的key,把vnode作為對象的value,這樣就可以只循環(huán)nextChildren,然后通過prevChildren[key]的方式找到prevChidren中可復(fù)用的dom。這樣就可以把時間復(fù)雜度降到O(n)。

以上就是react的diff算法的實現(xiàn)。

vue的diff算法

先說一下上面代碼的問題,舉個例子,下面這個情況:

如果按照react的方法,整個過程會移動2次:
li?是第一個節(jié)點,不需要移動,lastIndex=2
li(b), j=1, j<lastIndex, 移動到li?后面 (第1次移動)
li(a), j=0, j<lastIndex, 移動到li(b)后面 (第2次移動)

但是通過肉眼來看,其實只用把li?移動到第一個就行,只需要移動1一次。
于是vue2這么來設(shè)計的:

首先找到四個節(jié)點vnode:prev的第一個,next的第一個,prev的最后一個,next的最后一個,然后分別把這四個節(jié)點作比對:1. 把prev的第一個節(jié)點和next的第一個比對;2. 把prev的最后一個和next的最后一個比對;3.prev的第一個和next的最后一個;4. next的第一個和prev的最后一個。如果找到相同key的vnode,就做移動,移動后把前面的指針往后移動,后面的指針往前移動,直到前后的指針重合,如果key不相同就只patch更新vnodedata和children。下面來走一下流程:

  1. li(a)和li(b),key不同,只patch,不移動
  2. li(d)和li?,key不同,只patch,不移動
  3. li(a)和li?,key不同,只patch,不移動
  4. li(d)和li(d),key相同,先patch,需要移動移動,移動的方法就是把prev的li(d)移動到li(a)的前面。然后移動指針,因為prev的最后一個做了移動,所以把prev的指向后面的指針往前移動一個,因為next的第一個vnode已經(jīng)找到了對應(yīng)的dom,所以next的前面的指針往后移動一個。

現(xiàn)在比對的圖變成了下面這樣:

這個時候的真實DOM:

繼續(xù)比對

  1. li(a)和li(b),key不同,只patch,不移動。
  2. li?和li?,相同相同,先patch,因為next的最后一個元素也剛好是prev的最后一個,所以不移動,prev和next都往前移動指針。

這個時候真實DOM:

現(xiàn)在最新的比對圖:

繼續(xù)比對

  1. li(a)和li(b),key不同,只patch,不移動。
  2. li(b)和li(a),key不同,只patch,不移動。
  3. li(a) 和li (a),key相同,patch,把prev的li(a)移動到next的后面指針的元素的后面。

真實的DOM變成了這樣:

比對的圖變成這樣:

繼續(xù)比對:
li(b)和li(b)的key相同,patch,都是前指針相同所以不移動,移動指針
這個時候前指針就在后指針后面了,這個比對就結(jié)束了。

這就完成了常規(guī)的比對,還有不常規(guī)的,如下圖:

經(jīng)過1,2,3,4次比對后發(fā)現(xiàn),沒有相同的key值能夠移動。

這種情況我們沒有辦法,只有用老辦法,用newStartIndex的key拿去依次到prev里的vnode,直到找到相同key值的老的vnode,先patch,然后獲取真實dom移動到正確的位置(放到oldStartIndex前面),然后在prevChildren中把移動過后的vnode設(shè)置為undefined,在下次指針移動到這里的時候直接跳過,并且next的start指針向右移動。

function updateChildren (elm, prevChildren, nextChildren) {
  let oldStartIndex = 0;
  let oldEndIndex = prevChildren.length - 1;
  let newStartIndex = 0;
  let newEndIndex = nextChildren.length - 1;

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    let oldStartVnode = prevChildren[oldStartIndex];
    let oldEndVnode = prevChildren[oldEndIndex];
    let newStartVnode = nextChildren[newStartIndex];
    let newEndVnode = nextChildren[newEndIndex];

    if (oldStartVnode === undefined) {
      oldStartVnode = prevChildren[++oldStartIndex];
    }
    if (oldEndVnode === undefined) {
      oldEndVnode = prevChildren[--oldEndIndex];
    }

    if (oldStartVnode.key === newStartVnode.key) {
      patchVnode(newStartVnode, oldStartVnode);
      oldStartIndex++;
      newStartIndex++;
    } else if (oldEndVnode.key === newEndVnode.key) {
      patchVnode(newEndVnode, oldEndVnode);
      oldEndIndex--;
      newEndIndex--;
    } else if (oldStartVnode.key === newEndVnode.key) {
      patchVnode(newEndVnode, oldStartVnode);
      elm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
      newEndIndex--;
      oldStartIndex++;
    } else if (oldEndVnode.key === newStartVnode.key) {
      patchVnode(newStartVnode, oldEndVnode);
      elm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
      oldEndIndex--;
      newStartIndex++;
    } else {
      const idxInOld = prevChildren.findIndex(child => child.key === newStartVnode.key);
      if (idxInOld >= 0) {
        elm.insertBefore(prevChildren[idxInOld].elm, oldStartVnode.elm);
        prevChildren[idxInOld] = undefined;
        newStartIndex++;
      }
    }
  }
}

接下來就是新增節(jié)點:

這種排列方法,按照上面的方法,經(jīng)過1,2,3,4比對后找不到相同key,然后然后用newStartIndex到老的vnode中去找,仍然找不著,這個時候說明是一個新節(jié)點,把它插入到oldStartIndex前面

最后是刪除節(jié)點,我把他作為課后作業(yè),同學(xué)可以自己實現(xiàn)最后的刪除的算法。

完整代碼在 https://github.com/TingYinHelen/ tempo的vue分支。

PS.本文部分內(nèi)容參考自《比對一下react,vue2.x,vue3.x的diff算法》。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多