摘要:的鉤子函數(shù)會在組件停用時被調(diào)用。是在構(gòu)造函數(shù)中的聲明的變量執(zhí)行鉤子函數(shù)執(zhí)行執(zhí)行鉤子函數(shù)執(zhí)行鉤子函數(shù)刷新前根據(jù)對中的進行排序。
Vue 生命周期詳解 Vue 生命周期流程
最開始,用戶使用 new Vue() 創(chuàng)建根 Vue 實例,或者 Vue 實例化子組件都會調(diào)用_init方法(我們將這兩種實例都稱為vm):
function Vue(options) { //Vue 構(gòu)造函數(shù) ... this._init(options) } ... const Sub = function (options) { // 定義子組件構(gòu)造函數(shù) this._init(options) }
vm實例化時會調(diào)用原型方法this._init方法進行初始化:
Vue.prototype._init = function(options) { vm.$options = mergeOptions( // 合并options resolveConstructorOptions(vm.constructor), options || {}, vm ) ... initLifecycle(vm) // 開始一系列的初始化 initEvents(vm) initRender(vm) callHook(vm, "beforeCreate") //執(zhí)行 beforeCreate 鉤子 initInjections(vm) initState(vm) initProvide(vm) callHook(vm, "created") //執(zhí)行 created 鉤子 ... if (vm.$options.el) { vm.$mount(vm.$options.el) } }beforeCreate
首先,將用戶提供的options對象,父組件定義在子組件上的event、props(子組件實例化時),vm原型方法,和Vue構(gòu)造函數(shù)內(nèi)置的選項合并成一個新的options對象,賦值給vm.$options。
接下來,執(zhí)行 3 個初始化方法:
initLifecycle(vm): 主要作用是確認組件的父子關(guān)系和初始化某些實例屬性。找到父組件實例賦值給vm.$parent,將自己push給父組件的$children;
initEvents(vm): 主要作用是將父組件使用v-on或@注冊的自定義事件添加到子組件的私有屬性vm._events中;
initRender(vm): 主要作用是初始化用來將render函數(shù)轉(zhuǎn)為vnode的兩個方法vm._c 和vm.$createElement。用戶自定義的render函數(shù)的參數(shù)h就是vm.$createElement方法,它可以返回vnode。等以上操作全部完成,就會執(zhí)行beforeCreate鉤子函數(shù),此時用戶可以在函數(shù)中通過this訪問到vm.$parent和vm.$createElement等有限的屬性和方法。
created接下來會繼續(xù)執(zhí)行 3 個初始化方法:
initInjections(vm): 初始化inject,使得vm可以訪問到對應(yīng)的依賴;
initState(vm): 初始化會被使用到的狀態(tài),狀態(tài)包括props,methods,data,computed,watch五個選項。調(diào)用相應(yīng)的init方法,使用vm.$options中提供的選項對這些狀態(tài)進行初始化,其中initData方法會調(diào)用observe(data, true),實現(xiàn)對data中屬性的監(jiān)聽,實際上是使用Object.defineProperty方法定義屬性的getter和setter方法;
initProvide(vm):初始化provide,使得vm可以為子組件提供依賴。
這 3 個初始化方法先初始化inject,然后初始化props/data狀態(tài),最后初始化provide,這樣做的目的是可以在props/data中使用inject內(nèi)所注入的內(nèi)容。
等以上操作全部完成,就會執(zhí)行created鉤子函數(shù),此時用戶可以在函數(shù)中通過this訪問到vm中的props,methods,data,computed,watch和inject等大部分屬性和方法。
如果用戶在創(chuàng)建根 Vue 實例時提供了el選項,那么在實例化時會直接調(diào)用vm.$mount方法開始掛載:
if (vm.$options.el) { vm.$mount(vm.$options.el) }
如果未提供el選項,則需要用戶手動調(diào)用vm.$mount方法開掛載。vm.$mount方法:
運行時版本: Vue.prototype.$mount = function(el) { // 最初的定義 return mountComponent(this, query(el)); } 完整版: const mount = Vue.prototype.$mount Vue.prototype.$mount = function(el) { // 拓展編譯后的 var options = this.$options; if(!options.render) { if(options.template) { ... //一些判斷 } else if (el) { //傳入的 el 選項不為空 options.template = getOuterHTML(el); } if (options.template) { options.render = compileToFunctions(template, ...).render //將 template 編譯成 render 函數(shù) } } ... return mount.call(this, query(el)) //即 Vue.prototype.$mount.call(this, query(el)) }
在完整版的vm.$mount方法中,如果用戶未提供render函數(shù),就會將template或者el.outerHTML編譯成render函數(shù)。
然后會執(zhí)行mountComponent函數(shù):
export function mountComponent(vm, el) { vm.$el = el ... callHook(vm, "beforeMount") ... const updateComponent = function () { vm._update(vm._render()) // 調(diào)用 render 函數(shù)生成 vnode,并掛載到 HTML中 } ... if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, "mounted"); } }
如果用戶提供了el選項,則會獲取用于掛載的真實節(jié)點,將此節(jié)點賦值給vm.$el屬性。
等以上操作全部完成,就會執(zhí)行beforeMount鉤子函數(shù),如果用戶提供了el選項,此時在函數(shù)中可以通過this訪問到vm.$el屬性,此時它的值為el提供的真實節(jié)點。
在mountComponent方法中,會執(zhí)行vm._render方法獲取vnode:
Vue.prototype._render = function() { const vm = this const { render } = vm.$options const vnode = render.call(vm, vm.$createElement) return vnode }
在vm._render方法中會調(diào)用vm.$options.render函數(shù),傳入實參vm.$createElement(對應(yīng)聲明render函數(shù)時的形參h),得到返回結(jié)果vnode。
在執(zhí)行一個如下的render函數(shù)的過程中:
render(h) { return h( "div", //標簽名 [ //子節(jié)點數(shù)組 [ [h("h1", "title h1")], //子節(jié)點也是通過 h 函數(shù)生成 vnode 的 [h("h2", "title h2")] ], [ h(obj, [ //子組件傳入 obj 而不是標簽名 h("p", "paragraph") ]) ] ] ); }
執(zhí)行render函數(shù)的過程就是遞歸調(diào)用h函數(shù)的過程,h函數(shù)會根據(jù)子組件的options選項對象生成一個vnode,以便之后將它轉(zhuǎn)化為真實節(jié)點。
不管是根節(jié)點掛載時首次渲染,還是在數(shù)據(jù)改變后更新頁面,都會調(diào)用updateComponent方法。_render方法返回的vnode是一個樹形結(jié)構(gòu)的JavaScript對象,接下來在updateComponent中會調(diào)用_update將這棵虛擬DOM樹轉(zhuǎn)化為真實的DOM樹:
const updateComponent = function () { vm._update(vm._render()) // 調(diào)用 render 函數(shù)生成 vnode,并掛載到 HTML中 }
而vm._update方法會將vm.__patch__方法返回的真實Dom節(jié)點賦值給vm.$el:
Vue.prototype._update = function(vnode) { ... if (!prevVnode) { // 首次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode); } ... }
往vm.__patch__方法傳入的參數(shù)vm.$el是之前在mountComponent方法中賦值的真實Dom元素,是掛載對象。vm.__patch__會生成并插入真實Dom:
Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules })
nodeOps是一些操作原生Dom的方法的集合,modules是class/attrs/style等屬性創(chuàng)建、更新、銷毀時相應(yīng)鉤子方法的集合,而createPatchFunction函數(shù)返回了一個patch函數(shù):
export function createPatchFunction(backend) { ... const { modules, nodeOps } = backend return function patch (oldVnode, vnode) { // 接收新舊 vnode 的 `patch`函數(shù) ... //isDef 函數(shù) : (v) => v !== undefined && v !== null const isRealElement = isDef(oldVnode.nodeType) // 是否是真實 Dom if(isRealElement) { // 首次渲染傳入的 vm.$el 是真實 Dom oldVnode = emptyNodeAt(oldVnode) // 將 vm.$el 轉(zhuǎn)為 VNode 格式 } ... } }
調(diào)用emptyNodeAt函數(shù)將傳入的vm.$el轉(zhuǎn)化為VNode格式。VNode是Vue定義的虛擬節(jié)點類,vnode是VNode類的實例對象。
function emptyNodeAt(elm) { return new VNode( nodeOps.tagName(elm).toLowerCase(), // 對應(yīng)tag屬性 {}, // 對應(yīng)data [], // 對應(yīng)children undefined, //對應(yīng)text elm // 真實dom賦值給了elm屬性 ) } 包裝后的: { tag: "div", elm: "" // 真實dom }
然后繼續(xù)創(chuàng)建真實Dom:
export function createPatchFunction(backend) { ... return function patch (oldVnode, vnode) { const insertedVnodeQueue = [] //用于緩存 insertedVnode ... const oldElm = oldVnode.elm //包裝后的真實 Dom const parentElm = nodeOps.parentNode(oldElm) // 首次父節(jié)點為 createElm( // 創(chuàng)建真實 Dom vnode, // 傳入的 vnode insertedVnodeQueue, // 空數(shù)組 parentElm, // nodeOps.nextSibling(oldElm) // 下一個兄弟節(jié)點 ) return vnode.elm // 返回真實 Dom ,之后在 _update 中覆蓋 vm.$el } }
createElm方法根據(jù)節(jié)點類型生成真實Dom節(jié)點,并插入parentElm中。而createElm方法在創(chuàng)建元素節(jié)點的過程中,會調(diào)用createChildren方法創(chuàng)建子節(jié)點,而createChildren方法又會調(diào)用createElm方法生成子節(jié)點的真實Dom節(jié)點,形成了createElm方法的遞歸調(diào)用:
function createElm(vnode, insertedVnodeQueue, parentElm, ...) { ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { //此時可忽略這一步 return } ... // 如果要創(chuàng)建的節(jié)點是元素節(jié)點 vnode.elm = nodeOps.createElement(tag) // 先創(chuàng)建一個空元素用于掛載子節(jié)點 createChildren(vnode, children, insertedVnodeQueue) // 調(diào)用 `createChildren` 方法創(chuàng)建子節(jié)點 insert(parentElm, vnode.elm, refElm) // 將真實元素 vnode.elm 插入父節(jié)點中 ... }
遞歸創(chuàng)建子節(jié)點,插入父節(jié)點,最終生成vm的真實Dom節(jié)點vnode.elm。
等以上操作全部完成,就會執(zhí)行mounted鉤子函數(shù),此時在函數(shù)中可以通過this訪問到vm.$el屬性,此時它為虛擬vnode轉(zhuǎn)化而來的真實Dom。
如果我們研究的實例vm是一個組件實例,而且它被
在root掛載時,會在它的patch方法中調(diào)用createElm方法生成真實Dom節(jié)點并插入(root的父節(jié)點)。
如果有子節(jié)點,會先調(diào)用createChildren方法,在createChildren中通過createElm方法生成每個子節(jié)點的真實Dom節(jié)點,再將子Dom節(jié)點插入root的Dom節(jié)點中:
function createChildren(vnode, children, insertedVnodeQueue) { if (Array.isArray(children)) { for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); // 實參 vnode.elm 傳給 parentElm 形參 } } ... }
所以再次回到上面的createElm方法,此時它被用于創(chuàng)建子節(jié)點,如果子節(jié)點為組件,在createElm中會調(diào)用createComponent方法對子組件進行初始化,生成子組件實例(假設(shè)就是vm),初始化子組件調(diào)用的是 init 鉤子(vnode 有 4 個 management hook:init, prepatch, insert 和 destroy,在 render 函數(shù)生成 vnode 時會加載到 vnode.data.hook 上)。
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; if (isDef(i)) { var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */ ); // 暫停執(zhí)行 createComponent,開始調(diào)用 vnode.data.hook.init 鉤子進行初始化 } if (isDef(vnode.componentInstance)) { // 等 init 鉤子執(zhí)行完再執(zhí)行,此時 vm 已執(zhí)行完 $mount 方法,所以在 initComponent 方法中將 vnode push 到 insertedVnodeQueue 中 initComponent(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); // 將真實元素 vnode.elm 插入父節(jié)點中 if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true } } }
在 init 鉤子中調(diào)用 Sub構(gòu)造函數(shù)實例化子組件:
init: function init(vnode, hydrating) { ... //調(diào)用 `Sub`構(gòu)造函數(shù)實例化子組件,執(zhí)行 `beforeCreate` 和 `created` 鉤子 var child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance); //調(diào)用 vm.$mount,執(zhí)行 `beforeMount` 鉤子,然后執(zhí)行 updateComponent,重復(fù)上面的流程 child.$mount(hydrating ? vnode.elm : undefined, hydrating); },
初始化完成后,會調(diào)用子組件實例vm的$mount方法進行掛載,執(zhí)行patch方法,在vm的patch方法中又會調(diào)用createElm方法生成真實Dom,這時子組件實例會難以避免地再次執(zhí)行createComponent方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm) { ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { // 如果子節(jié)點為組件,調(diào)用 createComponent 方法對子組件進行初始化;之后在子組件的 `patch` 方法中又會調(diào)用 `createElm` 方法 return } //繼續(xù)創(chuàng)建真實節(jié)點 ... vnode.elm = nodeOps.createElement(tag) createChildren(vnode, children, insertedVnodeQueue); //從這里開始暫停,在 createChildren 中 createElm 子節(jié)點 insert(parentElm, vnode.elm, refElm); //將真實元素 vnode.elm 插入父節(jié)點中 ... }
這個時候createComponent不會執(zhí)行初始化操作,而是直接返回undefined,這樣就可以繼續(xù)創(chuàng)建真實節(jié)點,如果后代還有組件,又是一個循環(huán)……
所以,父子節(jié)點的創(chuàng)建、掛載鉤子執(zhí)行順序為:
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount
回到mounted生命周期的createPatchFunction方法,在它返回的patch方法中,私有變量insertedVnodeQueue用于存儲這些插入的后代組件的vnode:
function patch() { var insertedVnodeQueue = []; ... invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); //調(diào)用 insert 鉤子 return vnode.elm //真實 Dom 元素 } ... //`patch`方法就是 _update 中的 __patch__ 方法, //它返回真實 Dom 元素給根 Vue 實例的 $el,之后會在 mountComponent 中調(diào)用根 Vue 實例的 mounted 鉤子(具體看前面 mountComponent 和 _update 方法) root.$el = root.__patch__(...) // _update 中 ... callHook(root, "mounted"); // mountComponent 中
vm是root的后代,vm.$vnode也在root實例的patch方法的insertedVnodeQueue中。在invokeInsertHook函數(shù)中,會調(diào)用這些vnode的insert鉤子:
function invokeInsertHook(vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; //緩存 insertedVnode } else { //只有最初的實例的 initial 為 false,所以會延遲到根 Vue 實例 patch 方法的末尾調(diào)用所有后代組件的 insert 鉤子 for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); //調(diào)用緩存的 insertedVnode 的 insert 鉤子 } } }
假如當前調(diào)用的是vm.$vnode.data.hook.insert方法:
insert: function insert(vnode) { //傳入 vm.$vnode var context = vnode.context; //父組件實例 var componentInstance = vnode.componentInstance; //vnode 對應(yīng)的組件實例 vm if (!componentInstance._isMounted) { componentInstance._isMounted = true; callHook(componentInstance, "mounted"); //調(diào)用 vm 的 mounted 鉤子函數(shù)(所以子組件的 mounted 鉤子先于父組件被調(diào)用) } if (vnode.data.keepAlive) { //true if (context._isMounted) { // 父組件更新中 queueActivatedComponent(componentInstance); // 父組件更新時,將 `vm` push 到 Vue 全局變量 activatedChildren 中,等待執(zhí)行 `activated` 鉤子函數(shù) } else { // 父組件掛載中 activateChildComponent(componentInstance, true /* direct */ ); //調(diào)用 `vm` 的 `activated` 鉤子函數(shù) } } }
由此可知,Vue會按照root實例的patch方法的insertedVnodeQueue中vnode的順序執(zhí)行mounted鉤子。而在節(jié)點樹中,越底端的組件越先創(chuàng)建好完好的真實Dom節(jié)點并插入父Dom節(jié)點中,其vnode也越先被push到insertedVnodeQueue中,所以越先執(zhí)行它的mounted鉤子。
所以,完整的父子節(jié)點的創(chuàng)建、掛載鉤子執(zhí)行順序為:
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted
在vm.$vnode.data.hook.insert方法中調(diào)用的activateChildComponent函數(shù)會調(diào)用vm及其后代組件的activated鉤子函數(shù):
function activateChildComponent(vm, direct) { ... if (vm._inactive || vm._inactive === null) { vm._inactive = false; for (var i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]); //遞歸調(diào)用子組件的 activated 鉤子 } callHook(vm, "activated"); //調(diào)用 vm 的 activated 鉤子 } }
在vm首次掛載,調(diào)用mounted鉤子函數(shù)后,會馬上調(diào)用activated鉤子函數(shù)。
之后vm的activated鉤子函數(shù)會在 keep-alive 組件激活時調(diào)用激活時被調(diào)用,具體調(diào)用時機是在flushSchedulerQueue函數(shù)執(zhí)行完queue中所有的watchers后。
vm的deactivated鉤子函數(shù)會在 keep-alive 組件停用時被調(diào)用。
在patch方法的最后,會刪除舊節(jié)點:
function patch() { ... removeVnodes(parentElm, [oldVnode], 0, 0); // 在 removeVnodes 中調(diào)用 invokeDestroyHook(oldVnode) 刪除舊節(jié)點 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm }
如果要刪除的vnode有destroy鉤子,則調(diào)用vnode.data.hook.destroy:
function invokeDestroyHook(vnode) { var i, j; var data = vnode.data; if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); //調(diào)用 vnode.data.hook.destroy 鉤子 } ... } } destroy: function destroy(vnode) { var componentInstance = vnode.componentInstance; if (!componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { componentInstance.$destroy(); // 調(diào)用 vm.$destroy() } else { deactivateChildComponent(componentInstance, true /* direct */ ); //調(diào)用子組件的 "deactivated" 鉤子 } } }
調(diào)用`vm的deactivated鉤子,遞歸調(diào)用子組件的deactivated 鉤子:
function deactivateChildComponent() { ... for (var i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]); //遞歸調(diào)用子組件的 "deactivated" 鉤子 } callHook(vm, "deactivated"); //調(diào)用 "deactivated" 鉤子 ... }
這些操作在父組件的patch方法中執(zhí)行,父組件patch后,會調(diào)用mounted或者updated鉤子。
beforeUpdate每個組件實例都對應(yīng)一個watcher實例,它是在mountComponent方法中,在調(diào)用mounted鉤子之前實例化的:
export function mountComponent(vm, el) { ... callHook(vm, "beforeMount") ... const updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, { before: function before () { //在 run 之前執(zhí)行 if (vm._isMounted && !vm._isDestroyed) { callHook(vm, "beforeUpdate"); // beforeUpdate 鉤子等待執(zhí)行 } } }, true /* isRenderWatcher */); ... callHook(vm, "mounted"); }
如果是RenderWatcher,vm._watcher會用它賦值:
var Watcher = function Watcher (vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm; //關(guān)聯(lián)組件 if (isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); ... this.before = options.before; ... if (typeof expOrFn === "function") { this.getter = expOrFn; //即 vm._watcher.getter = updateComponent } this.value = this.lazy ? undefined : this.get(); //this.get 中會調(diào)用 this.getter,所以 new Watcher 就立即調(diào)用 updateComponent }
watcher會在組件渲染的過程中把接觸過的數(shù)據(jù)屬性記錄為依賴。之后當依賴的值發(fā)生改變,觸發(fā)依賴的setter方法時,會通知watcher,從而使它關(guān)聯(lián)的組件(vm)重新渲染。
一旦偵聽到數(shù)據(jù)變化,Vue將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更。如果同一個watcher被多次觸發(fā),只會被推入到隊列中一次。
等當前事件循環(huán)結(jié)束,下一次事件循環(huán)開始,Vue會刷新隊列并執(zhí)行已去重的工作。Vue會嘗試使用Promise.then、MutationObserver和setImmediate發(fā)布的微任務(wù)來執(zhí)行queue中的watcher。
function flushSchedulerQueue () { queue.sort(function (a, b) { return a.id - b.id; }); //queue 是在 Vue 構(gòu)造函數(shù)中的聲明的變量 ... for (index = 0; index < queue.length; index++) { watcher = queue[index]; if (watcher.before) { watcher.before(); //執(zhí)行 beforeUpdate 鉤子函數(shù) } id = watcher.id; has[id] = null; watcher.run(); //執(zhí)行 watcher ... } ... // call component updated and activated hooks callActivatedHooks(activatedChildren.slice()); //執(zhí)行 activated 鉤子函數(shù) callUpdatedHooks(queue.slice()); //執(zhí)行 updated 鉤子函數(shù) }
刷新前根據(jù) id 對 queue 中的 watcher 進行排序。這樣可以確保:
父watcher排在子watcher前,組件從父級更新到子級。(因為父母總是在子級之前創(chuàng)建,所以id更?。?;
在一個組件中,用戶聲明的watchers總是在render watcher之前執(zhí)行,因為user watchers更先創(chuàng)建;
如果在父組件的watcher運行期間,銷毀了某個子組件,可以跳過該子組件的watcher。
在執(zhí)行watcher.run方法之前,會執(zhí)行watcher.before方法,從而執(zhí)行beforeUpdate鉤子函數(shù)。
updated在執(zhí)行watcher.run方法時,會調(diào)用watcher.getter方法,而其中某個watcher(vm._watcher)關(guān)聯(lián)的就是我們的vm,它的getter是可以更新vm的updateComponent方法:
Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); //調(diào)用 watcher.get 方法 ... } ... } Watcher.prototype.get = function get () { ... try { value = this.getter.call(vm, vm); //調(diào)用 watcher.getter 方法 } ... }
調(diào)用updateComponent方法
updateComponent = function () { vm._update(vm._render(), hydrating); };
vm._render方法會重新執(zhí)行render函數(shù)生成vnode,然后vm._update方法會將vnode轉(zhuǎn)化為真實Dom,掛載到HTML中,并覆蓋vm.$el。
等以上操作全部完成,在flushSchedulerQueue函數(shù)的最后會執(zhí)行子組件的activated鉤子函數(shù)和vm的updated鉤子函數(shù):
function flushSchedulerQueue () { ... callActivatedHooks(activatedChildren.slice()); //執(zhí)行 activated 鉤子函數(shù) callUpdatedHooks(queue.slice()); //執(zhí)行 updated 鉤子函數(shù) } function callUpdatedHooks (queue) { var i = queue.length; while (i--) { var watcher = queue[i]; var vm = watcher.vm; if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) { callHook(vm, "updated"); //執(zhí)行 updated 鉤子函數(shù) } } }
在updated鉤子函數(shù)中通過this.$el訪問到的vm.$el屬性的值為更新后的真實Dom。
beforeUpdate和updated鉤子函數(shù)的執(zhí)行順序真好相反,因為在flushSchedulerQueue函數(shù)中是索引遞增處理queue中的watcher的,所以執(zhí)行beforeUpdate鉤子函數(shù)的順序和queue中watcher的順序相同;而在callUpdatedHooks函數(shù)中是按索引遞減的順序執(zhí)行_watcher關(guān)聯(lián)實例的updated鉤子的,和queue中_watcher順序相反。
再加上父watcher排在子watcher前,所以如果父、子組件在同一個事件循環(huán)中更新,那么生命周期鉤子的執(zhí)行順序為:
父beforeUpdate => 子beforeUpdate => 子updated => 父updated
調(diào)用vm.$destroy銷毀vm實例:
Vue.prototype.$destroy = function() { var vm = this; if (vm._isBeingDestroyed) { return } callHook(vm, "beforeDestroy"); vm._isBeingDestroyed = true; // remove self from parent var parent = vm.$parent; if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm); } // teardown watchers if (vm._watcher) { vm._watcher.teardown(); } var i = vm._watchers.length; while (i--) { vm._watchers[i].teardown(); } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount--; } // call the last hook... vm._isDestroyed = true; // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null); // fire destroyed hook callHook(vm, "destroyed"); // turn off all instance listeners. vm.$off(); // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null; } // release circular reference (#6759) if (vm.$vnode) { vm.$vnode.parent = null; } };
在調(diào)用beforeDestroy鉤子前未進行銷毀操作,所以在這一步,實例仍然完全可用。
destroyedvm.$destroy執(zhí)行的操作有
刪除vm.$parent.$children中的vm;
銷毀vm._watcher(渲染 watcher),銷毀vm._watchers[i]中的所有watcher;
刪除數(shù)據(jù) observer 中的引用;
調(diào)用destroyed鉤子函數(shù);
...
其中vm.__patch__(vm._vnode, null)可以銷毀所有子實例。
Vue 生命周期流程圖 Vue 父子組件生命周期鉤子執(zhí)行順序父子組件掛載過程:父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted
子組件被keep-alive組件包裹(忽視keep-alive組件),父子組件掛載過程:父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 子activated => 父mounted
只修改父組件或子組件的數(shù)據(jù):beforeUpdate => updated
在同一事件循環(huán)中修改父子組件的數(shù)據(jù)(無論先后):父beforeUpdate => 子beforeUpdate => 子updated => 父updated
父組件將數(shù)據(jù)傳給子組件的一個 prop,且它們分別是父、子組件的依賴,在修改父組件的數(shù)據(jù)時:父beforeUpdate => 子beforeUpdate => 子updated => 父updated
子組件的v-show指令綁定父組件的數(shù)據(jù),在修改父組件的數(shù)據(jù)時:父beforeUpdate => 父updated,子組件保持mounted狀態(tài)不變;
子組件的v-show指令綁定父組件的數(shù)據(jù),子組件被keep-alive組件包裹,在修改父組件的數(shù)據(jù)時:父beforeUpdate => 父updated,子組件保持activated狀態(tài)不變;
子組件的v-if指令綁定父組件的數(shù)據(jù),在修改父組件的數(shù)據(jù)時:
true => false: 父beforeUpdate => 子beforeDestroy => 子destroyed => 父updated
false => true: 父beforeUpdate => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父updated
子組件的v-if指令綁定父組件的數(shù)據(jù),子組件被keep-alive組件包裹,在修改父組件的數(shù)據(jù)時:
true => false: 父beforeUpdate => 子deactivated => 父updated
首次 false => true: 父beforeUpdate => 子beforeCreate => 子created => 子beforeMount => 子mounted => 子activated => 父updated
再次 false => true: 父beforeUpdate => 子activated => 父updated
子組件的is屬性綁定父組件的數(shù)據(jù),父組件將子組件一切換為子組件二:
父beforeUpdate => 子二beforeCreate => 子二created => 子二beforeMount => 子二mounted => 父beforeUpdate => 子一beforeDestroy => 子一destroyed => 父updated => 父updated
子組件的is屬性綁定父組件的數(shù)據(jù),子組件被keep-alive組件包裹,父組件將子組件一切換為子組件二:
首次:父beforeUpdate => 父beforeUpdate => 子二beforeCreate => 子二created => 子二beforeMount => 子一deactivated => 子二mounted => 子二activated => 父updated => 父updated
再次:父beforeUpdate => 子一deactivated => 子二activated => 父updated
動態(tài)組件觸發(fā)兩次父beforeUpdate、updated的原因:
在第一次事件循環(huán)只觸發(fā)了一次父組件的_watcher,在調(diào)用 render函數(shù)重新生成父組件vnode的過程中:
var render = function() { //Vue 編譯 template 而來的 render 函數(shù) var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c( "div", { attrs: { id: "app" } }, [ _c("img", { attrs: { alt: "Vue logo", src: require("./assets/logo.png") } }), _c("p", [_vm._v(_vm._s(_vm.message))]), //被 keep-alive 組件包裹的情況,在生成 keep-alive 組件的 vnode 時,第二次觸發(fā)了父組件的`_watcher` _c("keep-alive", [_c(_vm.now, { tag: "component" })], 1) //不被 keep-alive 組件包裹的情況,在生成子二組件的`vnode`時,第二次觸發(fā)了父組件的`_watcher` _c(_vm.now, { tag: "component" }) ], 1 ) }
其實 keep-alive 組件情況更具體一點,也是在生成 keep-alive 組件的孩子,子二組件的vnode時觸發(fā)的_watcher。
然后這個watcher會被插到queue中當前wacther的后面(根據(jù) wacther.id的大小插入正確的位置):
function queueWatcher(watcher) { var id = watcher.id; if (has[id] == null) { //在 flushSchedulerQueue 中,執(zhí)行 watcher.run 之前,已經(jīng)令 has[id] = null; has[id] = true; //所以同 id 的 wacther 可以被插入 queue 中 if (!flushing) { queue.push(watcher); } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. var i = queue.length - 1; while (i > index && queue[i].id > watcher.id) { i--; } queue.splice(i + 1, 0, watcher); } } ... }
等當前watcher.run執(zhí)行完,再執(zhí)行它。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106940.html
摘要:注意看下此時還是沒有選項鉤子函數(shù)和間的生命周期在這一階段發(fā)生的事情還是比較多的。鉤子函數(shù)和鉤子函數(shù)間的生命周期當發(fā)現(xiàn)中的數(shù)據(jù)發(fā)生了改變,會觸發(fā)對應(yīng)組件的重新渲染,先后調(diào)用和鉤子函數(shù)。 首先,每個Vue實例在被創(chuàng)建之前都要經(jīng)過一系列的初始化過程,這個過程就是vue的生命周期。首先看一張圖吧~這是官方文檔上的圖片相信大家一定都會很熟悉: showImg(https://segmentfau...
摘要:實例在文檔中經(jīng)常會使用這個變量名表示實例,在實例化時,需要傳入一個選項對象,它可以包含數(shù)據(jù)模板掛載元素方法生命周期鉤子等選項。通俗說就是實例從創(chuàng)建到銷毀的過程,就是生命周期。 Vue 實例中的生命周期鉤子 Vue 框架的入口就是 Vue 實例,其實就是框架中的 view model ,它包含頁面中的業(yè)務(wù)處理邏輯、數(shù)據(jù)模型等,它的生命周期中有多個事件鉤子,讓我們在控制整個Vue實例的過程...
摘要:實例化發(fā)生了什么詳解生命周期本文將對的生命周期進行詳細的講解讓你了解一個實例的誕生都經(jīng)歷了什么我在上建立了一個存放筆記的倉庫以后會陸續(xù)更新一些知識和項目中遇到的坑有興趣的同學(xué)可以去看看哈歡迎傳送門實例化一個這是一個方法觸發(fā)鉤子函數(shù)組件實例剛 實例化vue發(fā)生了什么?(詳解vue生命周期) 本文將對vue的生命周期進行詳細的講解,讓你了解一個vue實例的誕生都經(jīng)歷了什么~ 我在Githu...
摘要:在這一步,實例已完成以下的配置數(shù)據(jù)觀測,屬性和方法的運算,事件回調(diào)。可以直接寫等標簽的寫法之前會的工程師上手框架的成本較低 簡介 1.美團工程師推出的基于Vue.js封裝的用于開發(fā)小程序的框架2.融合了原生小程序和Vue.js的特點3.可完全組件化開發(fā) 特點 1.組件化開發(fā)2.完成的Vue.js開發(fā)體驗(前提是熟悉Vue)3.可使用Vuex管理狀態(tài)4.Webpack構(gòu)建項目5.最終H5...
閱讀 2821·2023-04-25 18:46
閱讀 711·2021-11-19 09:40
閱讀 2077·2021-09-28 09:36
閱讀 3384·2021-09-10 11:11
閱讀 3464·2019-08-30 15:55
閱讀 1803·2019-08-30 15:54
閱讀 2598·2019-08-29 16:16
閱讀 3542·2019-08-29 15:08