亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Vue 生命周期詳解

snowLu / 3733人閱讀

摘要:的鉤子函數(shù)會(huì)在組件停用時(shí)被調(diào)用。是在構(gòu)造函數(shù)中的聲明的變量執(zhí)行鉤子函數(shù)執(zhí)行執(zhí)行鉤子函數(shù)執(zhí)行鉤子函數(shù)刷新前根據(jù)對(duì)中的進(jìn)行排序。

Vue 生命周期詳解 Vue 生命周期流程

最開(kāi)始,用戶使用 new Vue() 創(chuàng)建根 Vue 實(shí)例,或者 Vue 實(shí)例化子組件都會(huì)調(diào)用_init方法(我們將這兩種實(shí)例都稱為vm):

function Vue(options) {        //Vue 構(gòu)造函數(shù)
    ...
    this._init(options)
}
...
const Sub = function (options) {  // 定義子組件構(gòu)造函數(shù)
    this._init(options)
}

vm實(shí)例化時(shí)會(huì)調(diào)用原型方法this._init方法進(jìn)行初始化:

Vue.prototype._init = function(options) {
    vm.$options = mergeOptions(  // 合并options
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
    ...
    initLifecycle(vm) // 開(kāi)始一系列的初始化
    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對(duì)象,父組件定義在子組件上的event、props(子組件實(shí)例化時(shí)),vm原型方法,和Vue構(gòu)造函數(shù)內(nèi)置的選項(xiàng)合并成一個(gè)新的options對(duì)象,賦值給vm.$options
接下來(lái),執(zhí)行 3 個(gè)初始化方法:

initLifecycle(vm): 主要作用是確認(rèn)組件的父子關(guān)系和初始化某些實(shí)例屬性。找到父組件實(shí)例賦值給vm.$parent,將自己push給父組件的$children

initEvents(vm): 主要作用是將父組件使用v-on@注冊(cè)的自定義事件添加到子組件的私有屬性vm._events中;

initRender(vm): 主要作用是初始化用來(lái)將render函數(shù)轉(zhuǎn)為vnode的兩個(gè)方法vm._cvm.$createElement。用戶自定義的render函數(shù)的參數(shù)h就是vm.$createElement方法,它可以返回vnode。等以上操作全部完成,就會(huì)執(zhí)行beforeCreate鉤子函數(shù),此時(shí)用戶可以在函數(shù)中通過(guò)this訪問(wèn)到vm.$parentvm.$createElement等有限的屬性和方法。

created

接下來(lái)會(huì)繼續(xù)執(zhí)行 3 個(gè)初始化方法:

initInjections(vm): 初始化inject,使得vm可以訪問(wèn)到對(duì)應(yīng)的依賴;

initState(vm): 初始化會(huì)被使用到的狀態(tài),狀態(tài)包括propsmethods,datacomputed,watch五個(gè)選項(xiàng)。調(diào)用相應(yīng)的init方法,使用vm.$options中提供的選項(xiàng)對(duì)這些狀態(tài)進(jìn)行初始化,其中initData方法會(huì)調(diào)用observe(data, true),實(shí)現(xiàn)對(duì)data中屬性的監(jiān)聽(tīng),實(shí)際上是使用Object.defineProperty方法定義屬性的gettersetter方法;

initProvide(vm):初始化provide,使得vm可以為子組件提供依賴。

這 3 個(gè)初始化方法先初始化inject,然后初始化props/data狀態(tài),最后初始化provide,這樣做的目的是可以在props/data中使用inject內(nèi)所注入的內(nèi)容。
等以上操作全部完成,就會(huì)執(zhí)行created鉤子函數(shù),此時(shí)用戶可以在函數(shù)中通過(guò)this訪問(wèn)到vm中的propsmethods,data,computed,watchinject等大部分屬性和方法。

beforeMount

如果用戶在創(chuàng)建根 Vue 實(shí)例時(shí)提供了el選項(xiàng),那么在實(shí)例化時(shí)會(huì)直接調(diào)用vm.$mount方法開(kāi)始掛載:

if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}

如果未提供el選項(xiàng),則需要用戶手動(dòng)調(diào)用vm.$mount方法開(kāi)掛載。vm.$mount方法:

運(yùn)行時(shí)版本:
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 選項(xiàng)不為空
            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ù),就會(huì)將template或者el.outerHTML編譯成render函數(shù)。
然后會(huì)執(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選項(xiàng),則會(huì)獲取用于掛載的真實(shí)節(jié)點(diǎn),將此節(jié)點(diǎn)賦值給vm.$el屬性。
等以上操作全部完成,就會(huì)執(zhí)行beforeMount鉤子函數(shù),如果用戶提供了el選項(xiàng),此時(shí)在函數(shù)中可以通過(guò)this訪問(wèn)到vm.$el屬性,此時(shí)它的值為el提供的真實(shí)節(jié)點(diǎn)。

mounted

mountComponent方法中,會(huì)執(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方法中會(huì)調(diào)用vm.$options.render函數(shù),傳入實(shí)參vm.$createElement(對(duì)應(yīng)聲明render函數(shù)時(shí)的形參h),得到返回結(jié)果vnode
在執(zhí)行一個(gè)如下的render函數(shù)的過(guò)程中:

render(h) {
    return h(
        "div",    //標(biāo)簽名
        [                //子節(jié)點(diǎn)數(shù)組
            [
                [h("h1", "title h1")],    //子節(jié)點(diǎn)也是通過(guò) h 函數(shù)生成 vnode 的
                [h("h2", "title h2")]
            ],
            [
                h(obj, [                //子組件傳入 obj 而不是標(biāo)簽名
                    h("p", "paragraph")
                ])
            ]
        ]
    );
}

執(zhí)行render函數(shù)的過(guò)程就是遞歸調(diào)用h函數(shù)的過(guò)程,h函數(shù)會(huì)根據(jù)子組件的options選項(xiàng)對(duì)象生成一個(gè)vnode,以便之后將它轉(zhuǎn)化為真實(shí)節(jié)點(diǎn)。

不管是根節(jié)點(diǎn)掛載時(shí)首次渲染,還是在數(shù)據(jù)改變后更新頁(yè)面,都會(huì)調(diào)用updateComponent方法。_render方法返回的vnode是一個(gè)樹(shù)形結(jié)構(gòu)的JavaScript對(duì)象,接下來(lái)在updateComponent中會(huì)調(diào)用_update將這棵虛擬DOM樹(shù)轉(zhuǎn)化為真實(shí)的DOM樹(shù):

const updateComponent = function () {
    vm._update(vm._render())    // 調(diào)用 render 函數(shù)生成 vnode,并掛載到 HTML中
}

vm._update方法會(huì)將vm.__patch__方法返回的真實(shí)Dom節(jié)點(diǎn)賦值給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方法中賦值的真實(shí)Dom元素,是掛載對(duì)象。vm.__patch__會(huì)生成并插入真實(shí)Dom

Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules }) 

nodeOps是一些操作原生Dom的方法的集合,modulesclass/attrs/style等屬性創(chuàng)建、更新、銷毀時(shí)相應(yīng)鉤子方法的集合,而createPatchFunction函數(shù)返回了一個(gè)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) // 是否是真實(shí) Dom
        if(isRealElement) {  // 首次渲染傳入的 vm.$el 是真實(shí) Dom
            oldVnode = emptyNodeAt(oldVnode)  // 將 vm.$el 轉(zhuǎn)為 VNode 格式
        }
        ...
    }
}

調(diào)用emptyNodeAt函數(shù)將傳入的vm.$el轉(zhuǎn)化為VNode格式。VNodeVue定義的虛擬節(jié)點(diǎn)類,vnodeVNode類的實(shí)例對(duì)象。

function emptyNodeAt(elm) {
    return new VNode(
        nodeOps.tagName(elm).toLowerCase(), // 對(duì)應(yīng)tag屬性
        {},  // 對(duì)應(yīng)data
        [],   // 對(duì)應(yīng)children
        undefined,  //對(duì)應(yīng)text
        elm  // 真實(shí)dom賦值給了elm屬性
    )
}
包裝后的:
{
    tag: "div",
    elm: "
" // 真實(shí)dom }

然后繼續(xù)創(chuàng)建真實(shí)Dom

export function createPatchFunction(backend) { 
    ...
    return function patch (oldVnode, vnode) {
        const insertedVnodeQueue = []        //用于緩存 insertedVnode
        ...
        const oldElm = oldVnode.elm  //包裝后的真實(shí) Dom 
const parentElm = nodeOps.parentNode(oldElm) // 首次父節(jié)點(diǎn)為 createElm( // 創(chuàng)建真實(shí) Dom vnode, // 傳入的 vnode insertedVnodeQueue, // 空數(shù)組 parentElm, // nodeOps.nextSibling(oldElm) // 下一個(gè)兄弟節(jié)點(diǎn) ) return vnode.elm // 返回真實(shí) Dom ,之后在 _update 中覆蓋 vm.$el } }

createElm方法根據(jù)節(jié)點(diǎn)類型生成真實(shí)Dom節(jié)點(diǎn),并插入parentElm中。而createElm方法在創(chuàng)建元素節(jié)點(diǎn)的過(guò)程中,會(huì)調(diào)用createChildren方法創(chuàng)建子節(jié)點(diǎn),而createChildren方法又會(huì)調(diào)用createElm方法生成子節(jié)點(diǎn)的真實(shí)Dom節(jié)點(diǎn),形成了createElm方法的遞歸調(diào)用:

function createElm(vnode, insertedVnodeQueue, parentElm, ...) {
    ...
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {    //此時(shí)可忽略這一步
        return 
    }
    ...
    // 如果要?jiǎng)?chuàng)建的節(jié)點(diǎn)是元素節(jié)點(diǎn)
    vnode.elm = nodeOps.createElement(tag)  // 先創(chuàng)建一個(gè)空元素用于掛載子節(jié)點(diǎn)
    createChildren(vnode, children, insertedVnodeQueue)  // 調(diào)用 `createChildren` 方法創(chuàng)建子節(jié)點(diǎn)
    insert(parentElm, vnode.elm, refElm)  // 將真實(shí)元素 vnode.elm 插入父節(jié)點(diǎn)中
    ...
}

遞歸創(chuàng)建子節(jié)點(diǎn),插入父節(jié)點(diǎn),最終生成vm的真實(shí)Dom節(jié)點(diǎn)vnode.elm。
等以上操作全部完成,就會(huì)執(zhí)行mounted鉤子函數(shù),此時(shí)在函數(shù)中可以通過(guò)this訪問(wèn)到vm.$el屬性,此時(shí)它為虛擬vnode轉(zhuǎn)化而來(lái)的真實(shí)Dom

activated

如果我們研究的實(shí)例vm是一個(gè)組件實(shí)例,而且它被組件包裹,那么它將額外具有兩個(gè)鉤子函數(shù)activateddeactivated。我們假設(shè)vm是根 Vue 實(shí)例root的一個(gè)后代組件。
root掛載時(shí),會(huì)在它的patch方法中調(diào)用createElm方法生成真實(shí)Dom節(jié)點(diǎn)并插入root的父節(jié)點(diǎn))。
如果有子節(jié)點(diǎn),會(huì)先調(diào)用createChildren方法,在createChildren中通過(guò)createElm方法生成每個(gè)子節(jié)點(diǎn)的真實(shí)Dom節(jié)點(diǎn),再將子Dom節(jié)點(diǎn)插入rootDom節(jié)點(diǎn)中:

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);    // 實(shí)參 vnode.elm 傳給 parentElm 形參
        }
    }
    ...
}

所以再次回到上面的createElm方法,此時(shí)它被用于創(chuàng)建子節(jié)點(diǎn),如果子節(jié)點(diǎn)為組件,在createElm中會(huì)調(diào)用createComponent方法對(duì)子組件進(jìn)行初始化,生成子組件實(shí)例(假設(shè)就是vm),初始化子組件調(diào)用的是 init 鉤子(vnode 有 4 個(gè) management hookinit, prepatch, insertdestroy,在 render 函數(shù)生成 vnode 時(shí)會(huì)加載到 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,開(kāi)始調(diào)用 vnode.data.hook.init 鉤子進(jìn)行初始化
        }
        if (isDef(vnode.componentInstance)) {
            // 等 init 鉤子執(zhí)行完再執(zhí)行,此時(shí) vm 已執(zhí)行完 $mount 方法,所以在 initComponent 方法中將 vnode push 到 insertedVnodeQueue 中
            initComponent(vnode, insertedVnodeQueue);    
            insert(parentElm, vnode.elm, refElm);    // 將真實(shí)元素 vnode.elm 插入父節(jié)點(diǎn)中
            if (isTrue(isReactivated)) {
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
            }
            return true
        }
    }
}

init 鉤子中調(diào)用 Sub構(gòu)造函數(shù)實(shí)例化子組件:

init: function init(vnode, hydrating) {
    ...
    //調(diào)用 `Sub`構(gòu)造函數(shù)實(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);    
},

初始化完成后,會(huì)調(diào)用子組件實(shí)例vm$mount方法進(jìn)行掛載,執(zhí)行patch方法,在vmpatch方法中又會(huì)調(diào)用createElm方法生成真實(shí)Dom,這時(shí)子組件實(shí)例會(huì)難以避免地再次執(zhí)行createComponent方法:

function createElm(vnode, insertedVnodeQueue, parentElm, refElm) { 
    ...
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {    // 如果子節(jié)點(diǎn)為組件,調(diào)用 createComponent 方法對(duì)子組件進(jìn)行初始化;之后在子組件的 `patch` 方法中又會(huì)調(diào)用 `createElm` 方法
          return      
    }    
    //繼續(xù)創(chuàng)建真實(shí)節(jié)點(diǎn)
    ...    
    vnode.elm = nodeOps.createElement(tag) 
    createChildren(vnode, children, insertedVnodeQueue);    //從這里開(kāi)始暫停,在 createChildren 中 createElm 子節(jié)點(diǎn)
    insert(parentElm, vnode.elm, refElm);        //將真實(shí)元素 vnode.elm 插入父節(jié)點(diǎn)中
    ...
}

這個(gè)時(shí)候createComponent不會(huì)執(zhí)行初始化操作,而是直接返回undefined,這樣就可以繼續(xù)創(chuàng)建真實(shí)節(jié)點(diǎn),如果后代還有組件,又是一個(gè)循環(huán)……
所以,父子節(jié)點(diǎn)的創(chuàng)建、掛載鉤子執(zhí)行順序?yàn)椋?br>父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount

回到mounted生命周期的createPatchFunction方法,在它返回的patch方法中,私有變量insertedVnodeQueue用于存儲(chǔ)這些插入的后代組件的vnode

function patch() {
    var insertedVnodeQueue = [];
    ...
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);    //調(diào)用 insert 鉤子
    return vnode.elm    //真實(shí) Dom 元素
}
...
//`patch`方法就是 _update 中的 __patch__ 方法,
//它返回真實(shí) Dom 元素給根 Vue 實(shí)例的 $el,之后會(huì)在 mountComponent 中調(diào)用根 Vue 實(shí)例的 mounted 鉤子(具體看前面 mountComponent 和 _update 方法)
root.$el = root.__patch__(...)    // _update 中
...
callHook(root, "mounted");        // mountComponent 中

vmroot的后代,vm.$vnode也在root實(shí)例的patch方法的insertedVnodeQueue中。在invokeInsertHook函數(shù)中,會(huì)調(diào)用這些vnodeinsert鉤子:

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 {
        //只有最初的實(shí)例的 initial 為 false,所以會(huì)延遲到根 Vue 實(shí)例 patch 方法的末尾調(diào)用所有后代組件的 insert 鉤子
        for (var i = 0; i < queue.length; ++i) {
            queue[i].data.hook.insert(queue[i]);    //調(diào)用緩存的 insertedVnode 的 insert 鉤子
        }
    }
}

假如當(dāng)前調(diào)用的是vm.$vnode.data.hook.insert方法:

insert: function insert(vnode) {    //傳入 vm.$vnode
    var context = vnode.context;        //父組件實(shí)例
    var componentInstance = vnode.componentInstance;    //vnode 對(duì)應(yīng)的組件實(shí)例 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);    // 父組件更新時(shí),將 `vm` push 到 Vue 全局變量 activatedChildren 中,等待執(zhí)行 `activated` 鉤子函數(shù)
        } else {
            // 父組件掛載中
            activateChildComponent(componentInstance, true /* direct */ );    //調(diào)用 `vm` 的 `activated` 鉤子函數(shù)
        }
    }
}

由此可知,Vue會(huì)按照root實(shí)例的patch方法的insertedVnodeQueuevnode的順序執(zhí)行mounted鉤子。而在節(jié)點(diǎn)樹(shù)中,越底端的組件越先創(chuàng)建好完好的真實(shí)Dom節(jié)點(diǎn)并插入父Dom節(jié)點(diǎn)中,其vnode也越先被pushinsertedVnodeQueue中,所以越先執(zhí)行它的mounted鉤子。
所以,完整的父子節(jié)點(diǎn)的創(chuàng)建、掛載鉤子執(zhí)行順序?yàn)椋?br>父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted

vm.$vnode.data.hook.insert方法中調(diào)用的activateChildComponent函數(shù)會(huì)調(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ù)后,會(huì)馬上調(diào)用activated鉤子函數(shù)。
之后vmactivated鉤子函數(shù)會(huì)在 keep-alive 組件激活時(shí)調(diào)用激活時(shí)被調(diào)用,具體調(diào)用時(shí)機(jī)是在flushSchedulerQueue函數(shù)執(zhí)行完queue中所有的watchers后。

deactivated

vmdeactivated鉤子函數(shù)會(huì)在 keep-alive 組件停用時(shí)被調(diào)用。
patch方法的最后,會(huì)刪除舊節(jié)點(diǎn):

function patch() {
    ...
    removeVnodes(parentElm, [oldVnode], 0, 0);        // 在 removeVnodes 中調(diào)用 invokeDestroyHook(oldVnode) 刪除舊節(jié)點(diǎn)
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
}

如果要?jiǎng)h除的vnodedestroy鉤子,則調(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)用`vmdeactivated鉤子,遞歸調(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后,會(huì)調(diào)用mounted或者updated鉤子。

beforeUpdate

每個(gè)組件實(shí)例都對(duì)應(yīng)一個(gè)watcher實(shí)例,它是在mountComponent方法中,在調(diào)用mounted鉤子之前實(shí)例化的:

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");
}

如果是RenderWatchervm._watcher會(huì)用它賦值:

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 中會(huì)調(diào)用 this.getter,所以 new Watcher 就立即調(diào)用 updateComponent
}

watcher會(huì)在組件渲染的過(guò)程中把接觸過(guò)的數(shù)據(jù)屬性記錄為依賴。之后當(dāng)依賴的值發(fā)生改變,觸發(fā)依賴的setter方法時(shí),會(huì)通知watcher,從而使它關(guān)聯(lián)的組件(vm)重新渲染。
一旦偵聽(tīng)到數(shù)據(jù)變化,Vue將開(kāi)啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更。如果同一個(gè)watcher被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次。
等當(dāng)前事件循環(huán)結(jié)束,下一次事件循環(huán)開(kāi)始,Vue會(huì)刷新隊(duì)列并執(zhí)行已去重的工作。Vue會(huì)嘗試使用Promise.then、MutationObserversetImmediate發(fā)布的微任務(wù)來(lái)執(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 對(duì) queue 中的 watcher 進(jìn)行排序。這樣可以確保:

watcher排在子watcher前,組件從父級(jí)更新到子級(jí)。(因?yàn)楦改缚偸窃谧蛹?jí)之前創(chuàng)建,所以id更?。?/p>

在一個(gè)組件中,用戶聲明的watchers總是在render watcher之前執(zhí)行,因?yàn)?b>user watchers更先創(chuàng)建;

如果在父組件的watcher運(yùn)行期間,銷毀了某個(gè)子組件,可以跳過(guò)該子組件的watcher。

在執(zhí)行watcher.run方法之前,會(huì)執(zhí)行watcher.before方法,從而執(zhí)行beforeUpdate鉤子函數(shù)。

updated

在執(zhí)行watcher.run方法時(shí),會(huì)調(diào)用watcher.getter方法,而其中某個(gè)watcher(vm._watcher)關(guān)聯(lián)的就是我們的vm,它的getter是可以更新vmupdateComponent方法:

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方法會(huì)重新執(zhí)行render函數(shù)生成vnode,然后vm._update方法會(huì)將vnode轉(zhuǎn)化為真實(shí)Dom,掛載到HTML中,并覆蓋vm.$el。
等以上操作全部完成,在flushSchedulerQueue函數(shù)的最后會(huì)執(zhí)行子組件的activated鉤子函數(shù)和vmupdated鉤子函數(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ù)中通過(guò)this.$el訪問(wèn)到的vm.$el屬性的值為更新后的真實(shí)Dom。
beforeUpdateupdated鉤子函數(shù)的執(zhí)行順序真好相反,因?yàn)樵?b>flushSchedulerQueue函數(shù)中是索引遞增處理queue中的watcher的,所以執(zhí)行beforeUpdate鉤子函數(shù)的順序和queuewatcher的順序相同;而在callUpdatedHooks函數(shù)中是按索引遞減的順序執(zhí)行_watcher關(guān)聯(lián)實(shí)例的updated鉤子的,和queue_watcher順序相反。
再加上父watcher排在子watcher前,所以如果父、子組件在同一個(gè)事件循環(huán)中更新,那么生命周期鉤子的執(zhí)行順序?yàn)椋?br>父beforeUpdate => 子beforeUpdate => 子updated => 父updated

beforeDestroy

調(diào)用vm.$destroy銷毀vm實(shí)例:

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鉤子前未進(jìn)行銷毀操作,所以在這一步,實(shí)例仍然完全可用。

destroyed

vm.$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)可以銷毀所有子實(shí)例。

Vue 生命周期流程圖

Vue 父子組件生命周期鉤子執(zhí)行順序

父子組件掛載過(guò)程:父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted

子組件被keep-alive組件包裹(忽視keep-alive組件),父子組件掛載過(guò)程:父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 子activated => 父mounted

只修改父組件或子組件的數(shù)據(jù):beforeUpdate => updated

在同一事件循環(huán)中修改父子組件的數(shù)據(jù)(無(wú)論先后):父beforeUpdate => 子beforeUpdate => 子updated => 父updated

父組件將數(shù)據(jù)傳給子組件的一個(gè) prop,且它們分別是父、子組件的依賴,在修改父組件的數(shù)據(jù)時(shí):父beforeUpdate => 子beforeUpdate => 子updated => 父updated

子組件的v-show指令綁定父組件的數(shù)據(jù),在修改父組件的數(shù)據(jù)時(shí):父beforeUpdate => 父updated,子組件保持mounted狀態(tài)不變;

子組件的v-show指令綁定父組件的數(shù)據(jù),子組件被keep-alive組件包裹,在修改父組件的數(shù)據(jù)時(shí):父beforeUpdate => 父updated,子組件保持activated狀態(tài)不變;

子組件的v-if指令綁定父組件的數(shù)據(jù),在修改父組件的數(shù)據(jù)時(shí):

true => false: 父beforeUpdate => 子beforeDestroy => 子destroyed => 父updated

false => true: 父beforeUpdate => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父updated

子組件的v-if指令綁定父組件的數(shù)據(jù),子組件被keep-alive組件包裹,在修改父組件的數(shù)據(jù)時(shí):

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

動(dòng)態(tài)組件觸發(fā)兩次父beforeUpdateupdated的原因:
在第一次事件循環(huán)只觸發(fā)了一次父組件的_watcher,在調(diào)用 render函數(shù)重新生成父組件vnode的過(guò)程中:

var render = function() {    //Vue 編譯 template 而來(lái)的 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 時(shí),第二次觸發(fā)了父組件的`_watcher`
      _c("keep-alive", [_c(_vm.now, { tag: "component" })], 1)
            //不被 keep-alive 組件包裹的情況,在生成子二組件的`vnode`時(shí),第二次觸發(fā)了父組件的`_watcher`
            _c(_vm.now, { tag: "component" })
        ],
    1
  )
}

其實(shí) keep-alive 組件情況更具體一點(diǎn),也是在生成 keep-alive 組件的孩子,子二組件的vnode時(shí)觸發(fā)的_watcher。
然后這個(gè)watcher會(huì)被插到queue中當(dāng)前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);
        }
    }
    ...
}

等當(dāng)前watcher.run執(zhí)行完,再執(zhí)行它。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/106940.html

相關(guān)文章

  • 詳解vue生命周期

    摘要:注意看下此時(shí)還是沒(méi)有選項(xiàng)鉤子函數(shù)和間的生命周期在這一階段發(fā)生的事情還是比較多的。鉤子函數(shù)和鉤子函數(shù)間的生命周期當(dāng)發(fā)現(xiàn)中的數(shù)據(jù)發(fā)生了改變,會(huì)觸發(fā)對(duì)應(yīng)組件的重新渲染,先后調(diào)用和鉤子函數(shù)。 首先,每個(gè)Vue實(shí)例在被創(chuàng)建之前都要經(jīng)過(guò)一系列的初始化過(guò)程,這個(gè)過(guò)程就是vue的生命周期。首先看一張圖吧~這是官方文檔上的圖片相信大家一定都會(huì)很熟悉: showImg(https://segmentfau...

    svtter 評(píng)論0 收藏0
  • Vue 實(shí)例中的生命周期鉤子詳解

    摘要:實(shí)例在文檔中經(jīng)常會(huì)使用這個(gè)變量名表示實(shí)例,在實(shí)例化時(shí),需要傳入一個(gè)選項(xiàng)對(duì)象,它可以包含數(shù)據(jù)模板掛載元素方法生命周期鉤子等選項(xiàng)。通俗說(shuō)就是實(shí)例從創(chuàng)建到銷毀的過(guò)程,就是生命周期。 Vue 實(shí)例中的生命周期鉤子 Vue 框架的入口就是 Vue 實(shí)例,其實(shí)就是框架中的 view model ,它包含頁(yè)面中的業(yè)務(wù)處理邏輯、數(shù)據(jù)模型等,它的生命周期中有多個(gè)事件鉤子,讓我們?cè)诳刂普麄€(gè)Vue實(shí)例的過(guò)程...

    gityuan 評(píng)論0 收藏0
  • 實(shí)例化vue發(fā)生了什么?(詳解vue生命周期)

    摘要:實(shí)例化發(fā)生了什么詳解生命周期本文將對(duì)的生命周期進(jìn)行詳細(xì)的講解讓你了解一個(gè)實(shí)例的誕生都經(jīng)歷了什么我在上建立了一個(gè)存放筆記的倉(cāng)庫(kù)以后會(huì)陸續(xù)更新一些知識(shí)和項(xiàng)目中遇到的坑有興趣的同學(xué)可以去看看哈歡迎傳送門實(shí)例化一個(gè)這是一個(gè)方法觸發(fā)鉤子函數(shù)組件實(shí)例剛 實(shí)例化vue發(fā)生了什么?(詳解vue生命周期) 本文將對(duì)vue的生命周期進(jìn)行詳細(xì)的講解,讓你了解一個(gè)vue實(shí)例的誕生都經(jīng)歷了什么~ 我在Githu...

    pcChao 評(píng)論0 收藏0
  • 詳解 mpvue 小程序框架 及和原生的差異

    摘要:在這一步,實(shí)例已完成以下的配置數(shù)據(jù)觀測(cè),屬性和方法的運(yùn)算,事件回調(diào)??梢灾苯訉懙葮?biāo)簽的寫法之前會(huì)的工程師上手框架的成本較低 簡(jiǎn)介 1.美團(tuán)工程師推出的基于Vue.js封裝的用于開(kāi)發(fā)小程序的框架2.融合了原生小程序和Vue.js的特點(diǎn)3.可完全組件化開(kāi)發(fā) 特點(diǎn) 1.組件化開(kāi)發(fā)2.完成的Vue.js開(kāi)發(fā)體驗(yàn)(前提是熟悉Vue)3.可使用Vuex管理狀態(tài)4.Webpack構(gòu)建項(xiàng)目5.最終H5...

    IamDLY 評(píng)論0 收藏0
  • Vue生命周期

    摘要:和下面手動(dòng)調(diào)用在控制臺(tái)中輸入在這個(gè)階段會(huì)銷毀實(shí)例,生命周期結(jié)束。外部實(shí)例中的函數(shù)顯示的效果參考鏈接組件的生命周期詳解生命周期 為什么要認(rèn)識(shí)Vue的生命周期 Vue的生命周期是一個(gè)非常重要的點(diǎn),如果不懂Vue的生命周期,那么很多時(shí)候,就不知道Vue的實(shí)際渲染時(shí)機(jī),程序中會(huì)出現(xiàn)各種bug。 因此,學(xué)習(xí)Vue的生命周期是非常用必要的。 showImg(https://segmentfault...

    y1chuan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<