寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧html
【Vue原理】從模板到DOM的簡要流程 node
今天的計劃是,探索Vue模板掛載到頁面是怎麼樣的一個流程,內容是指 正常 HTML 標籤的模板掛載,這部份內容很重要。app
而這部份內容也是爲了 講解 Component 做爲鋪墊,由於到最後 Component 必然也是做爲一個正常標籤去掛載,因此先把這部分抽出來說dom
首先,這個流程,我的認爲能夠分爲兩大部分,分別是 init 和 mount函數
顧名思義,init 一定是和初始化有關,mount 和 掛載DOM 有關學習
首先,當你開始調用 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
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())
這個函數的做用是,執行以前解析獲得的【渲染函數】,渲染函數執行完會返回一個 模板對應的 【VNode】
vm._render 再把這個 vnode 返回
因而就把這個 vnode,傳給了 vm._update 中當作了第一個參數
render 函數的內容其實很是的多,可是這裏一筆帶過,只用知道是用來生成Vnode 就行了,具體的內容會有具體的文章講解
Vue.prototype._render = function() { vnode = render(); return vnode }
這個函數的做用是,對比 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); } } }
兩個過程以下
一、初始化選項
二、初始化實例
一、解析模板,生成並保存渲染函數
二、新建 watcher 並當即執行更新函數 vm._update(vm._render)
三、vm._render 調用渲染函數生成 VNode,傳給 vm._update
四、調用 vm._update,根據 VNode 生成 DOM 並掛載