摘要:對(duì)綁定的事件和屬性等進(jìn)行處理,其中包含指令。有專門的方法來處理指令,這個(gè)方法是,其作用,獲取指令鉤子,和對(duì)不同鉤子進(jìn)行不同處理。
寫文章不容易,點(diǎn)個(gè)贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧
【Vue原理】Directives - 源碼版
咦,上一篇我們已經(jīng)講過白話版啦,主要的邏輯大家應(yīng)該也清楚了的,今天我們就直接開干源碼。有興趣讀源碼的同學(xué),希望對(duì)你們有幫助哦~
沒看過白話版的,還是先別看源碼版了,那么多代碼看了估計(jì)會(huì)懵逼...
首先,上一篇說過,Vue 會(huì)在DOM 創(chuàng)建之后,插入父節(jié)點(diǎn)之前。對(duì)DOM綁定的事件和屬性等進(jìn)行處理,其中包含指令。
Vue 有專門的方法來處理指令,這個(gè)方法是 updateDirectives,其作用,獲取指令鉤子,和對(duì)不同鉤子進(jìn)行不同處理。
updateDirectives 的源碼不是很短,其中還涉及其他方法,不打算一次性放出來,打算一塊一塊分解地講,所以 源碼會(huì)被我分成很多塊
今天我們以兩個(gè)問題開始
1、怎么獲取到設(shè)置的指令鉤子
2、內(nèi)部怎么調(diào)用鉤子函數(shù)
還有,模板上指令會(huì)被解析成數(shù)組,比如下面這個(gè)模板
會(huì)被解析成下面的渲染函數(shù),看下其中的 directives,這就是指令被解析成的終極形態(tài)了。下面 updateDirectives 方法處理指令,處理的就是這個(gè)數(shù)組
with(this) { return _c("div", { directives: [{ name: "test", rawName: "v-test" },{ name: "test2", rawName: "v-test2" }] }) }怎么獲取設(shè)置的指令鉤子
在 updateDirectives 中,處理的是指令的鉤子,那么第一步肯定是要先獲取鉤子啊,不要處理個(gè)錘子。
function updateDirectives(oldVnode, vnode) { // 獲取舊節(jié)點(diǎn)的指令 var oldDirs = normalizeDirectives$1( oldVnode.data.directives, oldVnode.context); // 獲取新節(jié)點(diǎn)的指令 var newDirs = normalizeDirectives$1( vnode.data.directives, vnode.context); }
你也看到了,上面的源碼中有一個(gè) normalizeDirectives$1,他就是獲取鉤子的幕后黑手。
先看作用,再看源碼
1、遍歷本節(jié)點(diǎn)所有的指令,逐個(gè)從組件中獲取
2、把獲取的鉤子添加到 遍歷到的當(dāng)前指令上
function normalizeDirectives$1(dirs, vm) { var res = {}; var i, dir; for (i = 0; i < dirs.length; i++) { dir = dirs[i]; res[dir.name] = dir; dir.def = vm.$options["directives"][dir.name]; } return res }
最后返回的是什么呢,舉個(gè)例子看下
比如開始處理的指令數(shù)組是下面
directives: [{ name: "test", rawName: "v-test" }]
v-test 的鉤子函數(shù)是
new Vue({ directives:{ test:{ bind(){...}, inserted(){...}, .... 等其他鉤子 } } })
經(jīng)過 normalizeDirectives$1 ,就會(huì)返回下面這個(gè)
directives: [{ name: "test", rawName: "v-test", def:{ bind(){...}, .... 等其他鉤子 } }]
好的,拿到了鉤子,那我們下一步就是要處理鉤子了!
怎么調(diào)用鉤子哈哈,看過白話版的,就知道這里不同的鉤子的處理流程大概是什么樣子,今天,這里是不會(huì)重復(fù)去描述啦,大概放些源碼,供大家去學(xué)習(xí)。
bind 、update、unbind 都是直接觸發(fā)的,沒有什么好講的,觸發(fā)的代碼我已經(jīng)標(biāo)藍(lán)了
function updateDirectives(oldVnode, vnode) { // 如果舊節(jié)點(diǎn)為空,表示這是新創(chuàng)建的 var isCreate = oldVnode === emptyNode; // 如果新節(jié)點(diǎn)為空,表示要銷毀 var isDestroy = vnode === emptyNode; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) { dir.def.bind(vnode.elm, dir, vnode, oldVnode, isDestroy) ...inserted 處理 } else { dir.def.update(vnode.elm, dir, vnode, oldVnode, isDestroy) ...componentUpdated處理 } } ... ...inserted 和 componentUpdated 處理 ... if (!isCreate) { for (key in oldDirs) { if (!newDirs[key]) { oldDirs[key].def.unbind(vnode.elm, dir, vnode, oldVnode, isDestroy) } } } }
重點(diǎn)我們講 inserted 和 componentUpdated 兩個(gè)鉤子就好了
1、insertedinserted 是在DOM 插入父節(jié)點(diǎn)之后才觸發(fā)的,而 處理 inserted 是在 DOM 插入之前,所有這里不可能直接觸發(fā),只能是先保存起來,等到 節(jié)點(diǎn)被插入之后再觸發(fā)
所以,inserted 分為 保存和 執(zhí)行兩個(gè)步驟,我們按兩個(gè)步驟來看源碼
保存鉤子
下面保存 inserted 鉤子的源碼可以看成三步
1、保存進(jìn)數(shù)組 dirsWithInsert
2、組裝成函數(shù) callInsert
3、合并到 insert 鉤子
function updateDirectives(oldVnode, vnode) { // 如果舊節(jié)點(diǎn)為空,表示這是新創(chuàng)建的 var isCreate = oldVnode === emptyNode; var dirsWithInsert = []; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) { if (dir.def && dir.def.inserted) { dirsWithInsert.push(dir); } } } if (dirsWithInsert.length) { var callInsert = function() { for (var i = 0; i < dirsWithInsert.length; i++) { callHook$1(dirsWithInsert[i], "inserted", vnode, oldVnode); } }; if (isCreate) { // 把callInsert 和本節(jié)點(diǎn)的 insert 合并起來 vnode.data.hook["insert"] = callInsert } else { callInsert(); } } }
執(zhí)行鉤子
通過白話版的測(cè)試我們已經(jīng)知道,inserted 鉤子是所有節(jié)點(diǎn)都插入完畢之后才觸發(fā)的,而不是插入一個(gè)節(jié)點(diǎn)就觸發(fā)一次
現(xiàn)在我們從頭探索這個(gè)執(zhí)行的流程
頁面初始化,調(diào)用 patch 處理根節(jié)點(diǎn),開始插入頁面的步驟,其中會(huì)不斷遍歷子節(jié)點(diǎn)
function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { var insertedVnodeQueue=[] if(需要更新){...省略...} // 不是更新,而是頁面初始化 else{ // 其中會(huì)不斷地遍歷子節(jié)點(diǎn),遞歸秭歸等.... createElm(vnode,insertedVnodeQueue,...); invokeInsertHook(vnode, insertedVnodeQueue); } return vnode.elm }
上面的 createElm 會(huì)創(chuàng)建本節(jié)點(diǎn)以及其后代節(jié)點(diǎn),然后插入到父節(jié)點(diǎn)中
等到 createElm 執(zhí)行完,所有節(jié)點(diǎn)都已經(jīng)插入完畢了
function createElm( vnode,insertedVnodeQueue, parentElm,refElm ){ vnode.elm = document.createElement(vnode.tag); // 不斷遍歷子節(jié)點(diǎn),遞歸調(diào)用 createElm if (Array.isArray(children)) { for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); } } // 處理本節(jié)點(diǎn)的事件,屬性等,其中包含對(duì)指令的處理 invokeCreateHooks(vnode, insertedVnodeQueue); // 插入 本DOM 到父節(jié)點(diǎn)中 insert(parentElm, vnode.elm, refElm); }
此時(shí),invokeInsertHook 開始執(zhí)行,invokeInsertHook 是統(tǒng)一調(diào)用 inserted 鉤子的地方。
function invokeInsertHook(vnode, insertedVnodeQueue) { for (var i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data.hook.insert(queue[i]); } }
因?yàn)?patch 只會(huì)在 根節(jié)點(diǎn)調(diào)用一次,invokeInsertHook 只在 patch 中調(diào)用
所以 inserted 才會(huì)在所有節(jié)點(diǎn)都插入父節(jié)點(diǎn)完畢之后,統(tǒng)一觸發(fā),而不是一個(gè)個(gè)來。
收集節(jié)點(diǎn)
invokeCreateHooks 用于調(diào)用各種函數(shù)處理事件、屬性、指令等
也是在這里添加節(jié)點(diǎn)到 insertedVnodeQueue
function invokeCreateHooks(vnode, insertedVnodeQueue) { // 其中會(huì)執(zhí)行 updateDirectives... for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode); } i = vnode.data.hook; // 保存含有 insert 函數(shù)的節(jié)點(diǎn) if (isDef(i) && isDef(i.insert)) { insertedVnodeQueue.push(vnode); } }
然后,執(zhí)行 inserted 的源碼可以看成 兩步 1、把所有含有 insert 函數(shù)的節(jié)點(diǎn),保存到 insertedVnodeQueue 2、所有節(jié)點(diǎn)插入完畢,遍歷 insertedVnodeQueue ,執(zhí)行其中節(jié)點(diǎn)的 insert 函數(shù) 注意,insert 不是 inserted 哦,只是邏輯上 insert 包含 inserted 大概的函數(shù)調(diào)用邏輯如下2、componentUpdated
這個(gè)鉤子和 inserted 差不多,只是執(zhí)行的流程不一樣
同樣分為保存和執(zhí)行兩段源碼
保存鉤子
function updateDirectives(oldVnode, vnode) { // 如果舊節(jié)點(diǎn)為空,表示這是新創(chuàng)建的 var isCreate = oldVnode === emptyNode; var dirsWithPostpatch = []; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) {....} else { if (dir.def && dir.def.componentUpdated) { dirsWithPostpatch.push(dir); } } } // 把指令componentUpdated的函數(shù) 和本節(jié)點(diǎn)的 postpatch 合并起來 if (dirsWithPostpatch.length) { vnode.data.hook["postpatch"] = function() { for (var i = 0; i < dirsWithPostpatch.length; i++) { callHook$1(dirsWithPostpatch[i], "componentUpdated", vnode, oldVnode); } }); } }
執(zhí)行鉤子
componentUpdated 鉤子是更新一個(gè)節(jié)點(diǎn)就馬上執(zhí)行的
更新一個(gè)節(jié)點(diǎn)的意思是包括其內(nèi)部的子節(jié)點(diǎn)的
那內(nèi)部的流程是怎么樣的呢?
同樣,更新就是更新節(jié)點(diǎn),也會(huì)調(diào)用 patch
function patch(oldVnode, vnode) { if(需要更新){ patchVnode(oldVnode, vnode) } return vnode.elm } function patchVnode(oldVnode, vnode){ // 遞歸調(diào)用 patchVnode 更新子節(jié)點(diǎn) updateChildren(oldVnode, vnode,.....); // 執(zhí)行本節(jié)點(diǎn)的 postpatch if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } }
舉個(gè)栗子走下流程
需要更新的時(shí)候,調(diào)用順序
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/105267.html
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理源碼版之節(jié)點(diǎn)數(shù)據(jù)拼接上一篇我們 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】Mixins - 源碼版 今天探索的是 mixins 的源碼,mixins 根據(jù)不同的選項(xiàng)類型會(huì)做不同的處理 篇幅會(huì)有些長(zhǎng),...
摘要:首先,兄弟,容我先說幾句涉及源碼很多,篇幅很長(zhǎng),我都已經(jīng)分了上下三篇了,依然這么長(zhǎng),但是其實(shí)內(nèi)容都差不多一樣,但是我還是毫無保留地給你了。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也...
摘要:還原的難度就在于變成模板了,因?yàn)槠渌氖裁吹仁窃獠粍?dòng)的哈哈,可是直接照抄最后鑒于本人能力有限,難免會(huì)有疏漏錯(cuò)誤的地方,請(qǐng)大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤剑瑲g迎后臺(tái)聯(lián)系本人,有重謝 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版...
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理源碼版之創(chuàng)建組件今天就要開啟我 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
閱讀 816·2023-04-25 20:18
閱讀 2097·2021-11-22 13:54
閱讀 2537·2021-09-26 09:55
閱讀 3903·2021-09-22 15:28
閱讀 2978·2021-09-03 10:34
閱讀 1714·2021-07-28 00:15
閱讀 1637·2019-08-30 14:25
閱讀 1285·2019-08-29 17:16