【Vue原理】從模板到DOM的簡要流程

寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧html

【Vue原理】從模板到DOM的簡要流程 node

今天的計劃是,探索Vue模板掛載到頁面是怎麼樣的一個流程,內容是指 正常 HTML 標籤的模板掛載,這部份內容很重要。app

而這部份內容也是爲了 講解 Component 做爲鋪墊,由於到最後 Component 必然也是做爲一個正常標籤去掛載,因此先把這部分抽出來說dom

首先,這個流程,我的認爲能夠分爲兩大部分,分別是 init 和 mount函數

顧名思義,init 一定是和初始化有關,mount 和 掛載DOM 有關學習


Init

首先,當你開始調用 Vue 的時候,好比這樣this

// js

new Vue({    

    el: document.getElementsByTagName("div")[0],

})


// html,夠簡潔了吧

<div></div>

那麼,先進入的確定是 Vue 這個構造函數,呈上來!spa

function Vue(options) {    

    this._init(options);

}

Vue.prototype._init = function(options) {    

    // 初始化 選項,computed,data 之類的

    // 初始化實例,給實例綁定些方法
    // 觸發 beforeCreated,created 鉤子

}

這個 _init 方法,是構建Vue 實例的時候調用的,而建立Vue 實例,並不是只有經過 new Vue 建立,有多是 Vue 內部建立的,好比 componentprototype

因此,才須要提取出一個 init 方法code

而後,init 到這裏就結束了,下面就到了另外一個流程 mount


Mount

init 結束,就開始解析模板啦,生成DOM 啦,掛載DOM 啦 之類的

開始正文,首先,從何時開始?此時須要亮出 _init 方法,沒錯,就是上面出現的方法

其實在這個方法的最後,有一個調用執行掛載DOM 的方法,以下

Vue.prototype._init = function(options) {
    .....
    if (vm.$options.el) {
        vm.$mount(vm.$options.el);
    }
}

能夠看到一句代碼,vm.$mount ,沒錯,就在這裏開啓了 DOM 掛載的 里程碑

可是,等等,有限制條件 vm.$options.el,也就是,必須有傳入 el 纔會在 最後調用 掛載DOM

因此,並非全部的 Vue 實例新建都會在 init 結尾調用 vm.$mount 去掛載DOM,好比 component 兩個過程就是分開的

咱們仍是先來看看 vm.$mount 吧

Vue.prototype.$mount = function(el) {    

    return mountComponent(this, query(el))

};

var mount = Vue.prototype.$mount;

Vue.prototype.$mount = function(el) {

    ...解析模板,生成模板渲染函數,保存渲染函數到 options    

    return mount.call(this, el)

}
原樣呈現了,Vue 中有兩個 $mount 函數,第一個的做用是給第二個 調用......若是你們看源碼,不要搞混了喂

公衆號

其中涉及到一個函數,mountComponent,速看

function mountComponent(vm, el) {    

    new Watcher(vm, function() {
        vm._update(vm._render()
    })    
    return v
}

function Watcher(vm, expOrFn) {    

    this.getter = expOrFn;    

    this.get();

}

Watcher.prototype.get = function() {
    value = this.getter(vm);
}

上面代碼的做用能夠說是,爲 Vue 實例新建監聽者 watcher,並設置一個更新函數

而這個更新函數,會在新建 watcher後 立刻執行,就是立刻執行了一遍這行代碼

vm._update(vm._render())

一、vm._render

這個函數的做用是,執行以前解析獲得的【渲染函數】,渲染函數執行完會返回一個 模板對應的 【VNode】

vm._render 再把這個 vnode 返回

因而就把這個 vnode,傳給了 vm._update 中當作了第一個參數

render 函數的內容其實很是的多,可是這裏一筆帶過,只用知道是用來生成Vnode 就行了,具體的內容會有具體的文章講解

Vue.prototype._render = function() {
    vnode = render();    

    return vnode
}

二、vm._update

這個函數的做用是,對比 vnode,掛載更新DOM

一、若是存在舊 vnode,那麼會對比舊 vnode 和 剛傳入的新 vnode,不斷地 patch 獲得最小變化單位,從而只更新這部分DOM

二、若是不存在舊 vnode,那麼就直接把 vnode 轉換爲 dom 掛載到頁面

其中,生成DOM 和 掛載DOM 用到的方法是 createElm

方法很簡單,無非就是經過 標籤名建立DOM,而後插入到頁面中

function createElm(vnode, parentElm, refElm) {  

    var children = vnode.children;    

    var tag = vnode.tag;

    vnode.elm = document.createElement(tag);   


    // 不斷遞歸遍歷子節點

    createChildren(vnode, children);    

    // 插入DOM 節點

    insert(parentElm, vnode.elm, refElm);
}

function createChildren(vnode, children) {    

    if (Array.isArray(children)) {        

        for (var i = 0; i < children.length; ++i) {

            createElm(children[i], vnode.elm, null);
        }
    }
}

function insert(parent, elm, ref) { 

    if (parent) {   

        // 若是存在兄弟節點,就查到兄弟前面

        if (ref) {       

            // 兄弟節點的父節點和 本節點父節點相同

            if (ref.parentNode === parent) {

                parent.insertBefore(elm, ref);
            }
        } 

        // 若是沒有兄弟節點,就直接查到父節點最後
        else {
            parent.appendChild(elm);
        }
    }
}

總結

兩個過程以下

init

一、初始化選項

二、初始化實例

mount

一、解析模板,生成並保存渲染函數

二、新建 watcher 並當即執行更新函數 vm._update(vm._render)

三、vm._render 調用渲染函數生成 VNode,傳給 vm._update

四、調用 vm._update,根據 VNode 生成 DOM 並掛載

公衆號

相關文章
相關標籤/搜索