new Vue后发生了什么

1,345 阅读3分钟

本文章为自己总结使用,仅供参考,同时本文是通过学习 黄轶 老师文章总结。如果问题欢迎指正

初始化流程

一.引入 vue 文件

会调用几个方法,初始化一些方法

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

二.new Vue 初始化准备挂载

new Vue 的时候会调用 initMixin 里面的_init 方法

function Vue(options) {
  if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}

1.init 的方法中会判断传入的是否是组件 由于第一次传入的不是组件所以会进入到 else 里面进行合并配置

if (options && options._isComponent) {
  // 优化内部组件实例化 因为动态选项合并非常慢,而且没有内部组件选项需要特殊处理。
  initInternalComponent(vm, options);
} else {
  vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}

2.Vue 初始化

  • 主要就干了几件事,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 props、methods、 data、computed、watcher 等
initLifecyle: // 初始化一些属性如$parent$children。根实例没有 $parent$children 开始是空数组,直到它的 子组件 实例进入到 initLifecycle 时,才会往父组件的 $children 里把自身放进去。所以 $children 里的一定是组件的实例。

initEvents // 初始事件$events,$on,$once,$off,$emit
initRender // 初始化渲染相关如 $createElement
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) //

初始化 state
    ·初始化 props
    ·初始化 methods
    ·初始化 data
    ·初始化 computed
    ·初始化 watch
所以在 data 中可以使用 props 上的值,反过来则不行。

initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

3.开始挂载

  • 如果监测到有 el 属性。则调用$mount 方法挂在 vue 实例,把模板渲染成最终的 DOM。然后调用 mountcomponent 方法 开始渲染

如果在 runtime-with-compiler 版本则会把你传入的 template 选项,或者 html 文本,通过一系列的编译生成 render 函数。在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法

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

三、开始挂载

1.beforeMount 钩子函数

在 mountcomponent 里面开始调用 beforeMount 钩子函数,然后实例化一个渲染的 watcher,里面调用一个 updateComponent 回调函数,在这个方法中调用_render 方法生成 Vnode,最后调用_update 方法更新生成 DOM

updateComponent = () => {
  vm._update(vm._render(), hydrating);
};
// 这个是个渲染的的watcher
new Watcher(
  vm,
  updateComponent,
  noop,
  {
    before() {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate');
      }
    }
  },
  true /* isRenderWatcher */
);
//
// 除了初始化 页面更新的时候也会调用这个方法渲染页面

1.new Watcher 会观察初始化里面有哪些响应式数据,如果数据发生了变化,会重新执行 updateComponent 函数重新更新渲染。

2.在调用_render 方法时,会通过执行 createElement 创建 VNode,VNode 里面有它的 children,而 children 下的元素也是 VNode,这样就生成了一个 VNode 的 tree。

3.在 render 的时候遇到了子组件,则会调用 createComponent 函数。 createComponent 函数内部,会通过 extend 方法为子组件生成一个属于自己的构造函数,这个 Ctor 继承了全局的一些属性和方法:同时对于这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造.

4.vm._update 方法则会对这个 vnode 进行 patch 操作,,帮我们把 vnode 通过 createElm 函数创建新节点并且渲染到 dom 节点中

// 在这个 patch 的时候会初始化 VNode 的生命周期函数,挂载在 VNode 的 data.hook 上,同时 patch 的时候也有 createComponent 函数会调用 VNode 的 init 方法

// createElm 中有 createChildren(vnode, children, insertedVnodeQueue)方法。createChildren 实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。

// 创建子组件
const child = createComponentInstanceForVnode(vnode);
// 挂载到 dom 上
child.$mount(vnode.elm);

createComponentInstanceForVnode 里面会调用子组件的构造函数

// baseCtor指向Vue
Ctor = baseCtor.extend(Ctor);

5.在 sub 实例化的时候会调用_init 方法又再次重复了 vue 实例化的逻辑,又会走到_render 方法

const Sub = function VueComponent(options) {
  this._init(options);
};

然后,mounted 生命周期被触发。

2.mounted 被调用完成

到此为止,组件的挂载就完成了,初始化的生命周期结束。

更新流程

当一个响应式属性被修改以后,会执行 渲染 watcher 的回调函数,updateComponent,也就是里面的 vm._update(vm._render);

在更新前会调用 before 里面的方法,也就是 callHook(vm,'beforeUpdate');

beforeUpdate 被调用完成

然后经历了一系列的 patch、diff 流程后,组件重新渲染完毕,调用 updated 钩子

这里是对 watcher 倒序 updated 调用的

function callUpdatedHooks(queue) {
  let i = queue.length;
  while (i--) {
    const watcher = queue[i];
    const vm = watcher.vm;
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated');
    }
  }
}

updated 被调用完成

至此,渲染更新流程完毕。

其它知识

initData 的时候做了什么

会把 vm._data 等于 data。

  • 首先会判断 data 是否是个 function。
  • 然后再判断 props 和 mehtods 上面是否会有和 data 里面的同样的变量名称
  • 通过 proxy 方法把 _data 里面的属性代理到 vm 实例上
  • 最后通过 observe 把属性变成双向绑定。
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};

proxy(vm, `_data`, key);

observe(data, true /* asRootData */);

function proxy(target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter() {
    return this[sourceKey][key];
  };
  sharedPropertyDefinition.set = function proxySetter(val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

全局组件注册

Vue.component('my-component', {
  // 选项
});
const ASSET_TYPES = [
  'component',
  'directive',
  'filter
]

Vue 初始化的时候会初始化这三个全局函数。在这个函数中

,局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 Vue.options 下,所以在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因