vue源碼分析——從實例化到渲染流程

本文vue 版本爲 2.5.17, 分析的是 Runtime + Compiler 構建出來的 Vue.js。 在 Vue.js 2.0 中,最終都是經過 render 函數渲染,若是含有 template 屬性,則須要將 template 編譯成 render 函數,那麼這個編譯過程會發⽣運⾏時,因此須要帶有Compiler編譯器的版本。本文爲vue源碼介紹系列的第一篇,主要概括整合vue實例化,將render函數轉爲vnode到生成掛載真實dom主要流程,具體細節請查看源碼。第二篇將介紹組件化過程。vue

vue源碼相對比較複雜,須要耐心反覆理解及調試,不懂就多調試問百度,羅馬不是一日建成的,相信堅持就會有收穫哈~

具體調試能夠下載vue.js,而後具體作斷點debugger調試。node

<script src="./vue.js"></script>
  <div id="app"></div>
  <script>
    var vm = new Vue({
      el: '#app',
      render(h) {
        return h(
          'div', { attr: { class: 'classname' } },
          [
            'first item',
            h('h2', { style: {color: 'orange' } }, 'second item')
          ]
        )
      },
      data: {
        message: 'Hello',
      }
    })
  </script>

先上圖分析流程瀏覽器

初始化流程圖

1. 定義Vue

function Vue (options) {
  if ("development" !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}
initMixin(Vue);  // 定義 _init
stateMixin(Vue);  // 定義 $set $get $delete $watch 等
eventsMixin(Vue);   // 定義事件  $on  $once $off $emit
lifecycleMixin(Vue); // 定義 _update  $forceUpdate  $destroy
renderMixin(Vue); // 定義 _render 返回虛擬dom

2. initMixin

實例化Vue時,執行 _init, _init 定義在 initMixin 中app

Vue.prototype._init = function (options) {
    // 合併 options
    if (options && options._isComponent) {
      initInternalComponent(vm, options); // 組件合併
    } else {
      // 非組件合併
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor), 
        options || {},
        vm
      );
    }
    initLifecycle(vm); // 定義 vm.$parent vm.$root vm.$children  vm.$refs 等
    initEvents(vm);   // 定義 vm._events  vm._hasHookEvent 等
    initRender(vm); // 定義 $createElement $c
    callHook(vm, 'beforeCreate'); // 掛載 beforeCreate 鉤子函數
    initInjections(vm); // resolve injections before data/props
    initState(vm);  // 初始化 props methods data computed watch 等方法
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created'); // 掛載 created 鉤子函數
    if (vm.$options.el) {
      vm.$mount(vm.$options.el); // 實例掛載渲染dom
    }
  };

3. $mount

vue最終都是經過render函數將dom編譯爲虛擬domdom

// 構建render函數
if (!options.render) {
  // 若是沒有render屬性,那麼將template模版編譯轉爲render
}

// 最後調用 mount
return mount.call(this, el, hydrating)

// mount 調用 mountComponent
return mountComponent(this, el, hydrating)

4. mountComponent

經過 new Watcher 調用執行 updateComponent, vm._render獲取虛擬dom, vm._update將虛擬dom轉爲真實的dom並掛載到頁面ide

// hydrating 表明服務端渲染 hydrating => false
updateComponent = function () {
  vm._update(vm._render(), hydrating); // 關鍵點
};

5. _render

_render執行render函數 返回vnoe函數

Vue.prototype._render = function () {
    // 此處的 vm._renderProxy 等價於 vm
    vnode = render.call(vm._renderProxy, vm.$createElement);
}

$createElement 主要是參數重載,整合爲統一格式後調用 _createElement函數oop

function createElement ( context, tag, data,  children,  normalizationType, alwaysNormalize) {
  // 參數重載
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children;
    children = data;
    data = undefined;
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE;
  }
  return _createElement(context, tag, data, children, normalizationType)
}

_createElement 主要是根據 tag 標籤判斷是組件仍是普通node標籤,返回對應的vnode虛擬dom組件化

function _createElement ( context, tag, data, children, normalizationType) {
  if (typeof tag === 'string') {
    // platform built-in elements
    vnode = new VNode(
      config.parsePlatformTagName(tag), data, children,
      undefined, undefined, context
    );
  }else{
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children);
  }
}

6. _update

_update 主要實現 vnode 轉化爲實際的dom, 注入到頁面的同時並銷燬頁面模版。ui

定義 _update

//  _update => __patch__
Vue.prototype._update = function (vnode, hydrating) {
  if (!prevVnode) {
    // 初始化時
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // 更新時
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
}

定義 __patch__

//   __patch__ => patch
Vue.prototype.__patch__ = inBrowser ? patch : noop;

定義 patch,

// 利用函數柯里化,將服務端和瀏覽器的差別集成到modules, nodeOps爲dom元素操做方法集合
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });

定義 createPatchFunction

function createPatchFunction (backend) {
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 建立新節點
    createElm(
      vnode,
      insertedVnodeQueue,
      oldElm._leaveCb ? null : parentElm,
      nodeOps.nextSibling(oldElm)
    );
    // 銷燬節點
    if (isDef(parentElm)) {
      removeVnodes(parentElm, [oldVnode], 0, 0);
    } else if (isDef(oldVnode.tag)) {
      invokeDestroyHook(oldVnode);
    }
  }
}

定義 createElm, 根據vnode建立真實dom

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
  // createChildren函數由子到父,深序遞歸調用createElm
  createChildren(vnode, children, insertedVnodeQueue);
  if (isDef(data)) {
    invokeCreateHooks(vnode, insertedVnodeQueue);
  }
  // 子節點插入到父節點
  insert(parentElm, vnode.elm, refElm);
}

以上就是vue從實例化,到調用render函數生成vnode,vnode經過patch轉爲真實dom節點,並掛載到頁面的流程狀況。接下來將第二篇將接掃vue組件化和生命週期過程。

相關文章
相關標籤/搜索