Vue(2.6.11)源碼閱讀——總結(jié)

數(shù)據(jù)驅(qū)動(dòng)

釋疑:
那為什么不用class來(lái)寫(xiě)vue而是用構(gòu)造函數(shù)的形式呢?
這是因?yàn)閂ue這個(gè)對(duì)象的方法太多了,很多都需要放在不同的模塊來(lái)寫(xiě)的,在不同的模塊我只要在vue的prototype上去添加方法就好了。如果是class,就不好添加了,只能在一個(gè)class上添加,繼承的話調(diào)用的也是其他class了,所以采用構(gòu)造函數(shù)的形式。

構(gòu)造函數(shù)和class的使用場(chǎng)景區(qū)別
當(dāng)一個(gè)對(duì)象或模塊頻繁用到繼承或者內(nèi)部屬性比較固定,采用OOB的思想,即采用class,如果一個(gè)模塊非常大,要分散到多個(gè)子模塊去編寫(xiě),且不怎么用到繼承采用構(gòu)造函數(shù)的形式。

整體流程:

new Vue => init => $mount => compile => render => vnode => update(patch) => dom

new Vue & init:
混入了一些全局方法

$mount
調(diào)用render函數(shù),沒(méi)有則創(chuàng)建一個(gè)
調(diào)用beforeMount,beforeUpdate,mounted鉤子
通過(guò)update方法渲染dom
返回vm
render
render就是createElement方法

Visual DOM
一個(gè)描述DOM的類

createElement
格式化children
創(chuàng)建vnode
update
其本質(zhì)就是調(diào)用了patch方法
patch方法就是將vnode遍歷創(chuàng)建DOM并插入(中間處理了文本節(jié)點(diǎn),注釋節(jié)點(diǎn))

組件化

Vue的組件化我們需要了解這么幾個(gè)部分:

1. createComponent(創(chuàng)建組件)

  1. 創(chuàng)建子類構(gòu)造函數(shù)

  2. 安裝組件鉤子,組件鉤子包括(組件的生命周期會(huì)在這幾個(gè)鉤子內(nèi)部具體調(diào)用)

    1. init
    2. prepatch
    3. insert
    4. destroy
  3. 創(chuàng)建vnode實(shí)例

2. patch

組件的patch的時(shí)候會(huì)合并配置項(xiàng),最后調(diào)用$mount(通過(guò)update)去進(jìn)行最后的渲染。
ps:
先父組件后子組件的有:
beforeCreate
created
beforeMount
beforeUpdate
updated
beforeDestroy
先子組件后父組件的有:
mounted
destroyed

3. 合并配置

vue源碼內(nèi)部會(huì)根據(jù)參數(shù)的不同采取不同的合并策略進(jìn)行配置合并。

4. 生命周期

1.beforeCreate & created

beforeCreate 在initState之前,而initState 的作用是初始化 props、data、methods、watch、computed 等屬性,所以beforeCreate拿不到。

  1. beforeMount & mounted

    1. beforeMount 調(diào)用在_render之前
    2. mounted 調(diào)用在_update之后
    3. 子組件在insert方法中調(diào)用了mounted
  2. beforeUpdate & updated
    這2個(gè)鉤子會(huì)在mounted之后觸發(fā)。

  3. beforeDestroy & destroyed
    在 $destroy 的執(zhí)行過(guò)程中,它又會(huì)執(zhí)行 vm.patch(vm._vnode, null) 觸發(fā)它父組件的銷毀鉤子函數(shù),這樣一層層的遞歸調(diào)用,所以 destroy 鉤子函數(shù)執(zhí)行順序是先子后父,和 mounted 過(guò)程一樣。

5. 組件注冊(cè)

  1. vue在初始化全局API的時(shí)候會(huì)將所有的組件合并到配置項(xiàng)中(全局注冊(cè)會(huì)合并到Vue原型鏈上的opinion,否則添加到該組件名下的配置項(xiàng)中)
  2. 我們?cè)谑褂玫倪^(guò)程中會(huì)根據(jù)tag的名字去拿,如果拿不到用駝峰的形式去拿,還拿不到用短橫線的形式去拿,這就是為什么我們注冊(cè)組件后使用的時(shí)候可以用駝峰和短橫線的原因。

6. 異步組件

vue將異步組件分成了普通組件,promsie組件和高階組件分別進(jìn)行處理。

  1. 先定義通用的forceRender, resolve, reject3個(gè)方法
  2. 由于異步組件組的解析過(guò)程中數(shù)據(jù)沒(méi)有發(fā)生變化,因此需要通過(guò)foceRender中的foreceupdate去強(qiáng)制更新
  3. 最后拿到解析后的組件,通過(guò)extend方法轉(zhuǎn)成組件的構(gòu)造函數(shù)。
  4. 由于之前定義好了resolve,reject,webpack通過(guò)import的方式返回的是一個(gè)promsie對(duì)象,那么該promsie最后的resovle和reject也會(huì)變成入?yún)⒈唤Y(jié)合進(jìn)來(lái)。
  5. 高級(jí)組件就是加了loading和timeout字段,由于vue解析異步組件的時(shí)候會(huì)先判斷是不是對(duì)象,對(duì)象的話就回去拿loading,和timeout字段。這里注意的一點(diǎn)是如果這是延遲為0,那么第一次解析的時(shí)候會(huì)返回我們定義好的loading組件否則在之后會(huì)創(chuàng)建一個(gè)注釋vnode,最后在foreRender的過(guò)程中會(huì)被替換。loading和timeout最后都會(huì)通過(guò)forceRender去渲染最終的結(jié)果。
  6. 由于開(kāi)始都回生成一個(gè)占位注釋節(jié)點(diǎn),等拿到組件后會(huì)去調(diào)用foreRender方法去更新,所以所有的異步組件其實(shí)都是渲染了2次。

Vue響應(yīng)式系統(tǒng)

1. 創(chuàng)建響應(yīng)式對(duì)象:

observe(xxx) => xxx是數(shù)組 ?遞歸observe :walk(調(diào)用defineReactive), observe整個(gè)動(dòng)作是vue核心庫(kù)需要的,最后變成響應(yīng)式對(duì)象是靠defineReactive實(shí)現(xiàn)的。

依賴收集和派發(fā)更新有2個(gè)概念:

  1. dep ,這是一個(gè)包含 依賴id, 訂閱者(watcher), 依賴數(shù)組的類,這類主要是用來(lái)管理watcher的。
  2. watcher,這個(gè)類的作用主要是數(shù)據(jù)發(fā)生變化的時(shí)候,調(diào)用各個(gè)方法產(chǎn)生計(jì)算和更改視圖的,包含了各種屬性。

2. 依賴收集:

依賴收集發(fā)生在數(shù)據(jù)屬性的get階段。干了這么一件事:
將當(dāng)前wather變成持有這個(gè)dep的訂閱者。

依賴收集要清空原來(lái)依賴,使用的新的依賴。防止重復(fù)訂閱的浪費(fèi)。

3. 派發(fā)更新:

派發(fā)更新主要發(fā)生在數(shù)據(jù)屬性的set階段, 做了這么幾個(gè)步驟:

  1. 判斷新值和舊值如果相等或者同為NaN,則直接返回。
  2. 通過(guò)observe將新值變?yōu)轫憫?yīng)式對(duì)象
  3. 通過(guò)dep.notify()派發(fā)更新。

派發(fā)更新的流程:
值發(fā)生改變 => dep.notify()=> 遍歷觸發(fā)訂閱的wathcer的update=> 根據(jù)是否同步等條件最后觸發(fā)watcher.run方法(渲染函數(shù)觸發(fā)get執(zhí)行更新DOM)=> 觸發(fā)watcher的回調(diào)(用戶可以拿到新值和舊值)

4. [檢測(cè)變化的注意事項(xiàng)]

通過(guò)a.b = 1給對(duì)象新添加屬性。
通過(guò)arr[0] = 1直接給原數(shù)組的元素賦值。
通過(guò)vm.items.length = newLength修改數(shù)組長(zhǎng)度。
上述3中可以通過(guò)set方法變成響應(yīng)式。核心還是通過(guò)defineReactive和dep.notify()

5. 計(jì)算屬性VS偵聽(tīng)屬性

  1. 計(jì)算屬性在創(chuàng)建wathcer的時(shí)候會(huì)置上一個(gè)標(biāo)志位lazy,做了2層優(yōu)化;

    1. 在初始化的時(shí)候不會(huì)去計(jì)算
    2. 在更新時(shí)候比較前后值是否一樣否則不會(huì)渲染(所有響應(yīng)式數(shù)據(jù)都一樣)。
  2. watch
    就是通過(guò)traverse做了遞歸響應(yīng)式。
    watcher的種類:
    deep watcher(遞歸添加watcher)
    user watcher(用戶的watcher)
    computed watcher
    sync watcher(可以在user watcher里配置)
    renderwatcher(渲染 watcher,一般來(lái)說(shuō)處的位置靠上)

6. 組件更新:

說(shuō)下數(shù)據(jù)變化到更新整個(gè)流程:

依賴發(fā)生變化 => watcher.getter => vm._update => vm.patch

判斷是否已是同一個(gè)節(jié)點(diǎn)的邏輯:
key => tag => isComment => data => sameInputType

如果新舊節(jié)點(diǎn)不同,那么主要分3步進(jìn)行:

  1. 創(chuàng)建新節(jié)點(diǎn)
  2. 更新父的占位節(jié)點(diǎn)
  3. 刪除舊節(jié)點(diǎn)

如果新舊節(jié)點(diǎn)相同:

  1. 執(zhí)行prepatch鉤子函數(shù)(去拿新的 vnode 的組件配置以及組件實(shí)例,去執(zhí)行 updateChildComponent 方法)
  2. 執(zhí)行 update 鉤子函數(shù)
  3. 完成 patch 過(guò)程

patch大致邏輯是這樣的:

  1. 新節(jié)點(diǎn)存在文本節(jié)點(diǎn)的情況下,當(dāng)舊節(jié)點(diǎn)的子節(jié)點(diǎn)和新節(jié)點(diǎn)的子節(jié)點(diǎn)都存在的情況下,開(kāi)始diff算法。
  2. 當(dāng)舊節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)的情況下,檢查新節(jié)點(diǎn)子節(jié)點(diǎn)的key的重復(fù)性.如果舊節(jié)點(diǎn)存在文本節(jié)點(diǎn),則清空文本節(jié)點(diǎn)。然后批量將新節(jié)點(diǎn)的子節(jié)點(diǎn)添加到elem后面
  3. 如果舊節(jié)點(diǎn)存在子節(jié)點(diǎn),則移除所以的子節(jié)點(diǎn)。
  4. 如果新節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)但是舊節(jié)點(diǎn)有子節(jié)點(diǎn),則清空舊節(jié)點(diǎn)的文本內(nèi)容
  5. 若新舊節(jié)點(diǎn)文本內(nèi)容不同則替換。

diff算法其實(shí)遵循幾個(gè)原則:

能移動(dòng)就移動(dòng)
先比頭和尾
頭尾比不了,就找中間key或索引一樣的比,然后添加到舊頭
新vnode較長(zhǎng)則在舊vnode上添加node,較短則刪除。

7. Props;

  1. 規(guī)范化
    當(dāng) props 是一個(gè)數(shù)組,每一個(gè)數(shù)組元素 prop 只能是一個(gè) string,表示 prop 的 key,轉(zhuǎn)成駝峰格式,prop 的類型為空。
    當(dāng) props 是一個(gè)對(duì)象,對(duì)于 props 中每個(gè) prop 的 key,我們會(huì)轉(zhuǎn)駝峰格式,而它的 value,如果不是一個(gè)對(duì)象,我們就把它規(guī)范成一個(gè)對(duì)象。
    如果 props 既不是數(shù)組也不是對(duì)象,就拋出一個(gè)警告。
  2. 初始化(校驗(yàn)、響應(yīng)式和代理)

釋疑:
1. 為什么說(shuō)Vue是異步更新,因?yàn)閐ep.notify()調(diào)用了watcher的udpate方法,這個(gè)方法調(diào)用了queueWatcher方方法,最終調(diào)用了nextTick這個(gè)異步方法,所以是異步更新的。
2. 在依賴收集階段如果碰到對(duì)象里面的屬性是數(shù)組的,如果數(shù)組的值是基本類型的,那么通過(guò)Object.defineproperty無(wú)法對(duì)其響應(yīng)式化,因此數(shù)組元素的變化是無(wú)法觸發(fā)更新的
我們能在watch拿到新值和舊值的原因是因?yàn)樵趙ather執(zhí)行run的時(shí)候,會(huì)將新舊值傳到回調(diào)里
3. push, unshift, splice是響應(yīng)式操作的原因是Vue重寫(xiě)了這3個(gè)方法。
4. 為什么每次獲取計(jì)算屬性的值時(shí)都要進(jìn)行依賴收集呢,而不是僅進(jìn)行一次性的依賴收集?原因是,計(jì)算屬性的依賴項(xiàng)可能會(huì)改變,這次有x個(gè)依賴項(xiàng),下次可能有y個(gè)依賴項(xiàng)。比如三元表達(dá)式

編譯

  1. 解析模板字符串就是根據(jù)通過(guò)正則按照不同的情況生成一個(gè)js對(duì)象,
  2. 優(yōu)化語(yǔ)法書(shū)其實(shí)就是標(biāo)記靜態(tài)節(jié)點(diǎn)和靜態(tài)根,提高編譯的效率。
  3. 生成代碼就是根據(jù)不同的條件將代碼串生成可以執(zhí)行的代碼。

擴(kuò)展

1. event

先通過(guò)正則把節(jié)點(diǎn)上的元素都解析出來(lái),并對(duì)事件是原生事件還是自定義事件加以區(qū)分。然后把所有的事件用 vm._events 存儲(chǔ)起來(lái). on 就是往_events 里push,off就是對(duì)_events的元素進(jìn)行刪除,once就是兩者結(jié)合一下。

2. v-model

v-model其實(shí)是一種語(yǔ)法糖,其本質(zhì)利用了父子組件的通信完成的。

3. slot

普通插槽是在父組件編譯和渲染階段生成 vnodes,所以數(shù)據(jù)的作用域是父組件實(shí)例,子組件渲染的時(shí)候直接拿到這些渲染好的 vnodes。而對(duì)于作用域插槽,父組件在編譯和渲染階段并不會(huì)直接生成 vnodes,而是在父節(jié)點(diǎn) vnode 的 data 中保留一個(gè) scopedSlots 對(duì)象,存儲(chǔ)著不同名稱的插槽以及它們對(duì)應(yīng)的渲染函數(shù),只有在編譯和渲染子組件階段才會(huì)執(zhí)行這個(gè)渲染函數(shù)生成 vnodes,由于是在子組件環(huán)境執(zhí)行的,所以對(duì)應(yīng)的數(shù)據(jù)作用域是子組件實(shí)例。

4. keep-alive

vue內(nèi)部對(duì)keep-alive做了特殊處理,在執(zhí)行prepatch階段會(huì)重新渲染緩存的組件,沒(méi)有重新生成一個(gè)vue實(shí)例,因此也沒(méi)有標(biāo)準(zhǔn)組件的生命周期。

transition && transition-group

自動(dòng)嗅探目標(biāo)元素是否應(yīng)用了 CSS 過(guò)渡或動(dòng)畫(huà),如果是,在恰當(dāng)?shù)臅r(shí)機(jī)添加/刪除 CSS 類名。

如果過(guò)渡組件提供了 JavaScript 鉤子函數(shù),這些鉤子函數(shù)將在恰當(dāng)?shù)臅r(shí)機(jī)被調(diào)用。

如果沒(méi)有找到 JavaScript 鉤子并且也沒(méi)有檢測(cè)到 CSS 過(guò)渡/動(dòng)畫(huà),DOM 操作 (插入/刪除) 在下一幀中立即執(zhí)行。

所以真正執(zhí)行動(dòng)畫(huà)的是我們寫(xiě)的 CSS 或者是 JavaScript 鉤子函數(shù),而 Vue 的 <transition> 只是幫我們很好地管理了這些 CSS 的添加/刪除,以及鉤子函數(shù)的執(zhí)行時(shí)機(jī)。
transition && transition-group

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容