new Vue 发生了什么
写在前面:本文基于vue2.6进行源码分析
初始化 vue
我们在实例化一个 vue
实例,也即 new Vue()
时,实际上是执行 src/core/instance/index.js 中定义的 Function
函数。
1function Vue(options) {
2 if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue))
3 warn('Vue is a constructor and should be called with the `new` keyword')
4
5 this._init(options)
6}
通过查看 Vue
的 function
,我们知道 Vue
只能通过 new
关键字初始化,然后调用 this._init
方法,该方法在 src/core/instance/init.js 中定义。
1Vue.prototype._init = function (options?: Object) {
2 const vm: Component = this;
3
4 // 省略一系列其它初始化的代码
5
6 if (vm.$options.el) {
7 console.log('vm.$options.el:', vm.$options.el);
8 vm.$mount(vm.$options.el);
9 }
10};
Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher
等等。
Vue 实例挂载
Vue 中是通过 $mount
实例方法去挂载 dom
的,下面我们通过分析 compiler
版本的 mount
实现,相关源码在目录 src/platforms/web/entry-runtime-with-compiler.js 文件中定义:。
1const mount = Vue.prototype.$mount;
2Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
3 el = el && query(el);
4
5 // 省略一系列初始化以及逻辑判断代码
6
7 return mount.call(this, el, hydrating);
8};
我们发现最终还是调用用原先原型上的 $mount
方法挂载 ,原先原型上的 $mount
方法在 src/platforms/web/runtime/index.js 中定义 。
1Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
2 el = el && inBrowser ? query(el) : undefined;
3 return mountComponent(this, el, hydrating);
4};
我们发现$mount
方法实际上会去调用 mountComponent
方法,这个方法定义在 src/core/instance/lifecycle.js 文件中
1export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
2 vm.$el = el;
3 // 省略一系列其它代码
4 let updateComponent;
5 /* istanbul ignore if */
6 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
7 updateComponent = () => {
8 // 生成虚拟 vnode
9 const vnode = vm._render();
10 // 更新 DOM
11 vm._update(vnode, hydrating);
12 };
13 } else {
14 updateComponent = () => {
15 vm._update(vm._render(), hydrating);
16 };
17 }
18
19 // 实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法
20 new Watcher(
21 vm,
22 updateComponent,
23 noop,
24 {
25 before() {
26 if (vm._isMounted && !vm._isDestroyed) {
27 callHook(vm, 'beforeUpdate');
28 }
29 },
30 },
31 true /* isRenderWatcher */
32 );
33 hydrating = false;
34
35 return vm;
36}
从上面的代码可以看到,mountComponent
核心就是先实例化一个渲染Watcher
,在它的回调函数中会调用 updateComponent
方法,在此方法中调用 vm._render
方法先生成虚拟 Node
,最终调用 vm._update
更新 DOM。
创建虚拟 Node
Vue 的 _render
方法是实例的一个私有方法,它用来把实例渲染成一个虚拟Node
。它的定义在 src/core/instance/render.js 文件中:
1Vue.prototype._render = function (): VNode {
2 const vm: Component = this
3 const { render, _parentVnode } = vm.$options
4 let vnode
5 try {
6 // 省略一系列代码
7 currentRenderingInstance = vm
8 // 调用 createElement 方法来返回 vnode
9 vnode = render.call(vm._renderProxy, vm.$createElement)
10 } catch (e) {
11 handleError(e, vm, `render`){}
12 }
13 // set parent
14 vnode.parent = _parentVnode
15 console.log("vnode...:",vnode);
16 return vnode
17 }
vm._render
最终是通过执行 createElement
方法并返回的是 vnode
,它是一个虚拟 Node
Vue.js 利用 _createElement
方法创建 VNode
,它定义在 src/core/vdom/create-elemenet.js 中:
1export function _createElement(
2 context: Component,
3 tag?: string | Class<Component> | Function | Object,
4 data?: VNodeData,
5 children?: any,
6 normalizationType?: number
7): VNode | Array<VNode> {
8 // 省略一系列非主线代码
9
10 if (normalizationType === ALWAYS_NORMALIZE) {
11 // 场景是 render 函数不是编译生成的
12 children = normalizeChildren(children);
13 } else if (normalizationType === SIMPLE_NORMALIZE) {
14 // 场景是 render 函数是编译生成的
15 children = simpleNormalizeChildren(children);
16 }
17 let vnode, ns;
18 if (typeof tag === 'string') {
19 let Ctor;
20 ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
21 if (config.isReservedTag(tag)) {
22 // 创建虚拟 vnode
23 vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context);
24 } else if ((!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) {
25 // component
26 vnode = createComponent(Ctor, data, context, children, tag);
27 } else {
28 vnode = new VNode(tag, data, children, undefined, undefined, context);
29 }
30 } else {
31 vnode = createComponent(tag, data, context, children);
32 }
33 if (Array.isArray(vnode)) {
34 return vnode;
35 } else if (isDef(vnode)) {
36 if (isDef(ns)) applyNS(vnode, ns);
37 if (isDef(data)) registerDeepBindings(data);
38 return vnode;
39 } else {
40 return createEmptyVNode();
41 }
42}
_createElement 方法有
5 个参数,context
表示 VNode
的上下文环境,它是 Component
类型;tag
表示标签,它可以是一个字符串,也可以是一个 Component
;data
表示 VNode
的数据,它是一个 VNodeData
类型,可以在 flow/vnode.js 中找到它的定义;children
表示当前 VNode
的子节点,它是任意类型的,需要被规范为标准的 VNode
数组;
更新视图
完成视图的更新工作事实上就是调用了vm._update
方法,这个方法接收的第一个参数是刚生成的Vnode
,调用的vm._update
方法定义在 src/core/instance/lifecycle.js 中。
1Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
2 const vm: Component = this;
3 const prevEl = vm.$el;
4 const prevVnode = vm._vnode;
5 const restoreActiveInstance = setActiveInstance(vm);
6 vm._vnode = vnode;
7 if (!prevVnode) {
8 // 第一个参数为真实的node节点,则为初始化
9 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
10 } else {
11 // 如果需要diff的prevVnode存在,那么对prevVnode和vnode进行diff
12 vm.$el = vm.__patch__(prevVnode, vnode);
13 }
14 restoreActiveInstance();
15 // update __vue__ reference
16 if (prevEl) {
17 prevEl.__vue__ = null;
18 }
19 if (vm.$el) {
20 vm.$el.__vue__ = vm;
21 }
22 // if parent is an HOC, update its $el as well
23 if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
24 vm.$parent.$el = vm.$el;
25 }
26};
总结
下图更直观地看到从初始化 Vue 到最终渲染的整个过程。
