#来源
#前置知识
Flow
-Vue2.x类型检查工具- 类型检查的两种方式:类型推断和类型注释
Runtime Only 和Runtime + Compiler
- Runtime Only需要借助webpack把.vue文件编译成JavaScript
- Runtime + Compiler,没有做预编译时需要渲染时编译
#Virtual DOM
问题:频繁操作DOM会有一定的性能问题
用一个原生的JS对象去描述一个DOM节点比创建一个DOM代价小,Vue中用VNode描述Virtual DOM
映射到真实DOM还要经历VNode
的create、diff、patch等过程
#数据驱动
目的:弄清楚模板和数据如何渲染成最终的 DOM。
所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作DOM,而是通过修改数据。
new Vue发生的事情
- 调用
this._init
方法,其中干了几件事:合并配置mergeOptions
、初始化生命周期initLifecycle
、初始化事件initEvents
、初始化渲染initRender
、初始化data、props、computed、watcher。
调用$mount方法
vm.$mount(vm.$options.el)
1
- $mount方法实现功能
- 对
el
做了限制,不能挂载在body、html根节点(由于会对原有元素进行替换,缺少了body或者html标签造成结构体不完整,不符合规范) - 如果没有定义
render
方法,则会把el
或者template
字符串转成render
- 调用原先原型上的
$mount
方法挂载,调用mountComponent
mountComponent
- 先实例化一个渲染
Watcher
:初始化时执行回调函数、当vm实例中数据变化时执行回调函数。回调函数为:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
1
2
3
2
3
- _render()
执行createElement方法并返回vnode
- createElement
对_createElement的封装
- _createElement
函数参数:
- context: VNode的上下文
- tag: 标签
- data: VNode数据
- children: 当前VNode的子节点
- normalizationType: 子节点规范的类型(编译生成还是用户手写)
children的规范化:normalizeChildren(children)
(render函数编译生成)和simpleNormalizeChildren(children)
simpleNormalizeChildren: 由于functional component
返回的是一个数组
normalizeChildren: render函数用户手写同时children只有一个节点,则创建createTextVNode;当编译slot
、v-for
时调用normalizeArrayChildren
VNode创建
- 判断tag是否为内置节点,如果是,则创建一个普通的VNode
- 如果是已注册的组件名,则通过
createComponent
创建一个组件类型的VNode
- update
- 首次渲染调用或数据更新调用
- 核心是调用
vm.__patch__
方法,浏览器端把VNode转成DOM使用了patch
方法
// params: 旧的VNode节点(可以不存在或者一个DOM对象)、_render后返回的VNode节点、hydrating-是否是服务端渲染、给transition-group使用
function patch (oldVnode, vnode, hydrating, removeOnly) {
}
1
2
3
2
3
patch
方法调用了createPathFunction
,createPathFunction
最终返回一个patch方法
// backend中的两个属性值nodeOps和modules
// nodeOps表示对平台DOM的一些操作方法
// modules表示平台的一些模块
export function createPatchFunction (backend) {
}
1
2
3
4
5
2
3
4
5
createElm
的作用是通过虚拟节点创建真实的DOM并插入到它的父节点中,该函数中最重要的三步是createChildren
、invokeCreateHooks
、insert
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// ...
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// ...
// 创建子元素
createChildren(vnode, children, insertedVnodeQueue)
// ...
// 执行所有的create的钩子并把`vnode`push到insertedVnodeQueue
invokeCreateHooks(vnode, insertedVnodeQueue)
// ...
//
insert(parentElm, vnode.elm, refElm)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#总结
#组件化
import Vue from 'vue'
import App from './App.vue'
var app = new Vue({
el: '#app',
// 这里的 h 是 createElement 方法
render: h => h(App)
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
在之前的createElement
调用_createElement
方法,如果是一个组件,则会通过createComponent
方法创建一个VNode
vnode = createComponent(tag, data, context, children)
1
createComponent
中包含三个关键步骤:
- 构造子类构造函数
- 安装组件钩子函数
installComponentHooks(data)
1
- 实例化VNode
_createElement() -> vnode = createComponent(tag, data, context, children) -> 1. Ctor = baseCtor.extend(Ctor) -> Vue.extend -> Sub(Vue子类) 2. installComponentHooks(data) -> mergeHook 3. new VNode
1
#patch
#computed原理
源码:
function initComputed (vm, computed) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
const isSSR = isServerRendering();
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
if (getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
);
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40