vue源码分析——从实例化到渲染流程__Vue.js
发布于 4 年前 作者 banyungong 1858 次浏览 来自 分享
粉丝福利 : 关注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.js,然后具体做断点debugger调试。

<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 中

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编译为虚拟dom

// 构建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并挂载到页面

// 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函数

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, 注入到页面的同时并销毁页面模版。

定义 _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组件化和生命周期过程。

❤️ 感谢大家

1.❤️如果喜欢本文,就点个赞支持下吧,你的「赞」是我创作的动力!

2.❤️欢迎各位大佬指导意见评论!

3.❤️可以收藏下转发到您的朋友圈空间等!

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 晨曦大前端 原文链接:https://juejin.im/post/6861943138102018061

回到顶部