Vue源码概述

Vue 2.x 的源码就是利用了 Flow 做静态类型检查。

timerFunc

Vue 在构建时,会初始化一个 timerFunc 函数,该函数会把 flushCallbacks 函数放入异步队列中。

flushCallbacks 函数负责清空在 Vue.nextTick 中定义的批量异步操作。

nextTick 负责收集包装异步函数,添加到 callbacks 数组中,并且触发 timerFunc 函数。

nextTick 就是把一个异步函数加入队列中,等同步方法执行完了立刻执行。

Vue

Vue 函数接收一个 options 参数,执行 _init() 函数。

1
2
3
function Vue (options) {
this._init(options)
}

初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mergeOptions()
// 生命周期:确定实例的父实例和根实例
initLifecycle(vm)
// 初始化实例上定义的自定义事件
initEvents(vm)
// 解析组件的插槽信息,得到 vm.$slot,
// 处理渲染函数(_render),得到 vm.$createElement 方法,即 h 函数
initRender(vm)
// 调用 beforeCreate 钩子函数
callHook(vm, 'beforeCreate')
// 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个 key 到 vm 实例
initInjections(vm) // resolve injections before data/props
// 数据响应式核心,处理 props、methods、data、computed、watch
initState(vm)
// 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
initProvide(vm) // resolve provide after data/props
// 调用 created 钩子函数
callHook(vm, 'created')
if (vm.$options.el) {
// 调用 $mount 方法,进入挂载阶段
vm.$mount(vm.$options.el)
}

创建之前的初始化

  • 合并options

  • 生命周期,绑定父组件和根组件

  • 绑定自定义事件:因为在初始化数据时可能触发

  • 初始化插槽:因为在插槽中的可能用到响应式数据

响应式数据的初始化

beforeCreate 和 created 之间主要是处理响应式数据和方法,流程

  • 初始化 inject

  • 绑定函数到组件

  • 初始化 data

  • 初始化 computed

  • 初始化 watch

  • 初始化 provide

实例挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function mountComponent(){
callHook(vm, 'beforeMount')
// 执行 vm._render() 函数,得到 虚拟 DOM,并将 vnode 传递给 _update 方法,
// 接下来就该到 patch 阶段了
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 观察
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)

}

updateComponent 的调用会执行 vm._update 和 vm._render

Watcher 在这里主要是初始化和数据变化时,执行回调函数。

把模版编译成render函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ast = parse(template.trim(), options)
// 优化,遍历 AST,为每个节点做静态标记
// 标记每个节点是否为静态节点,然后进一步标记出静态根节点
// 这样在后续更新中就可以跳过这些静态节点了
// 标记静态根,用于生成渲染函数阶段,生成静态根节点的渲染函数
if (options.optimize !== false) {
optimize(ast, options)
}
// 从 AST 生成渲染函数,,将 ast 转换成可执行的 render 函数的字符串形式
// code = {
// render: `with(this){return ${_c(tag, data, children, normalizationType)}}`,
// staticRenderFns: [_c(tag, data, children, normalizationType), ...]
// }
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.st
}
  • 解析字符串,生成树:涉及正则表达式,借鉴 simplehtmlparser.js,生成对象树

  • 优化

  • 生成render代码

响应式数据

Observer 类会被附加到被观察的对象上,也就是说,每一个响应式对象上都会有一个 __ob__;然后对数据类型进行了一个判断;若是数组,则判断是否存在 __proto__ 属性,因为要通过原型链覆盖数组的几个方法,

defineReactive 的作用就是利用 Object.defineProperty 对数据的读写进行劫持,给属性 key 添加 gettersetter ,用于依赖收集和通知更新。如果传进来的值依旧是一个对象,则递归调用 observe 方法,保证子属性都能变成响应式。

一个 key 一个 dep,在 key 被调用时,dep 收集依赖,在 key 被重新赋值时,dep 通知所有的依赖更新。

Dep 收集 Watcher,通知 Watcher 更新。

一个组件可以有多个 Watcher(组件至少一个,还有计算属性)

Watcher 与一个组件绑定,通知 Watcher 就是让组件重新渲染。

创建 Watcher 对象的情况:

  • 创建组件时

  • 创建计算属性时

  • 手动调用 $watcher

watcher 更新

如果是渲染 watcher 则执行 this.cb.call(this.vm, value, oldValue)。渲染 Wather 的实例化是在挂载时 mountComponent 方法中执行的:实际就是实例化时传入的第二个参数 updateComponent 。

渲染

Vue 1.x Watcher 时DOM元素级别,数据变化时可以精确到特定元素。

Vue 2.x 开始Watcher改为组件级,数据变化时通知组件更新,组件自己再通过diff算法确定更新具体的哪些元素。

vm._render()

createElement

创建虚拟节点VNode

Vue 的 _update 是实例上的一个私有方法,主要的作用就是把 VNode 渲染成真实的 DOM。

内部会调用 patch 方法,精细化修改真实DOM

diff

对比算法,判断DOM节点是否可以复用。

最终会生成真实DOM并进行挂载。

参考

Vue(v2.6.14)源码解毒(一):准备工作 - 掘金 (juejin.cn)