【Vue源碼探究二】從 $mount 講起,一塊兒探究Vue的渲染機制

mount, 意思爲掛載。能夠理解爲將vue實例(邏輯應用),掛靠在某個dom元素(載體)上的一個過程。html

1、建立Vue實例時的渲染過程

上一篇文章咱們講到, 在建立一個vue實例的時候(var vm = new Vue(options))。Vue的構造函數將自動運行 this._init(啓動函數)。啓動函數的最後一步爲initRender(vm),vue

// Vue.prototype._init
    ...
    initLifecycle(vm);
    initEvents(vm);
    callHook(vm, 'beforeCreate');
    initState(vm);
    callHook(vm, 'created');
    initRender(vm);

initRender中調用vm.$mount(vm.$options.el),將實例掛載到dom上,至此啓動函數完成。node

//initRender
  ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }

能夠看出,vm.$mount爲vue渲染的主要函數算法

2、Vue的渲染機制

clipboard.png

上圖,展現的是獨立構建時的一個渲染流程圖瀏覽器

模板字符串服務器

//模板字符串
<div id = "app">
  {{message}}
</div>

render函數app

//render函數
function anonymous() {
with(this){return _h('div',{attrs:{"id":"app"}},["\n  "+_s(message)+"\n"])}
}

vnodedom

clipboard.png

真實dom節點($el)ide

clipboard.png

獨立構建 與 運行時構建

咱們先看一下官方文檔 獨立構建和運行時構建函數

clipboard.png

這兩個概念,我在初學的時候是一頭霧水。如今對照着渲染的流程圖,咱們能夠知道

獨立構建:包含模板編譯器
渲染過程: html字符串 → render函數 → vnode → 真實dom節點

運行時構建: 不包含模板編譯器
渲染過程: render函數 → vnode → 真實dom節點

運行時構建經過砍掉模板編譯器,讓整個包少了30%(官方數據)。我在閱讀源碼的過程當中,發現vue源碼7000餘行,而和模板編譯相關的代碼,則約有1000行左右。看起來確實是輕便了。這是在鼓勵咱們多用render函數嗎?

3、$mount函數

上面咱們說到,運行時構建的包,會比獨立構建少一個模板編譯器。在$mount函數上也不一樣

運行時構建的 $mount函數

clipboard.png

而獨立構建的 $mount函數,會先用一個臨時變量mount保存上面的$mount方法

var mount = Vue$2.prototype.$mount;  //此處mount即爲運行時版的 $mount

而後重寫$mount函數,這時,調用$mount就會包括模板編譯功能了

var mount = Vue$2.prototype.$mount;
Vue$2.prototype.$mount = function (el, hydrating) {
  ...省略代碼(裏面爲模板編譯器入口)...
  return mount.call(this, el, hydrating)
};

咱們能夠看到,無論獨立構建仍是運行時構建,都會調用 vm._mount方法咱們來看看源碼

Vue.prototype._mount = function(el, hydrating) {
    ...一些防止運行時的包,卻用了template的報錯代碼...


    callHook(vm, 'beforeMount');

    vm._watcher = new Watcher(vm, function () {
      vm._update(vm._render(), hydrating);
    }, noop);
    
    hydrating = false;

    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm    
    
}

使用過的vue的人,都會很敏銳地發現, 在調用beforeMount生命週期,和mounted生命週期中間的關鍵代碼爲

clipboard.png

鑑於大牛已經講過不少次這裏的數據監聽了,咱們只講其中渲染部分

vm._update(vm._render(), hydrating);

vm._render函數返回一個vnode做爲 vm._update的參數。 hydrating是與服務器渲染(SSR)相關的,瀏覽器端能夠不用管。

vm._render (將render函數轉化成vnode)

最核心代碼爲

var render = vm.$options.render
try{
  vnode = render.call(vm._renderProxy, vm.$createElement);
}catch{
  ...
}

此處,使用call方法, 將this指向 vm.renderProxy js功底差的同窗要去補補知識了。
vm.renderProxy是個代理,代理vm,主要用來報錯,若是render函數上使用了vm上沒有的屬性或方法,就會報錯。
vm.$createElement 這個是建立vnode的方法,做爲第一個參數傳入。

clipboard.png

render函數
這裏的h便是, vm.$createElement ,即是在vm._render這個階段被傳入。

vm._update (將vnode生成真實dom節點)

最關鍵一句話爲

vm.$el = vm.__patch__(prevVnode, vnode);

vm.__patch__也是個你們夥,我以後會再去研究。
裏面的方法,將新舊vnode使用 diff算法進行比對,找出要替換的地方,這樣更新dom的性能會有較大優化。
最後會返回一個dom節點。
這個時候將vm.$el 賦值爲這個dom節點,掛載完成!

相關文章
相關標籤/搜索