vue2 源码解析,vue2源码流程图
- | UMD | CommonJS | ESModule | ESModule (Browser) |
---|---|---|---|---|
完整版 | vue.js | vue.common.js | vue.esm.js | vue.esm.browser.js |
只包含运行时版 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js | - |
完整版 (生产环境) | vue.min.js | - | - | vue.esm.browser.min.js |
只包含运行时版 (生产环境) | vue.runtime.min.js | - | - | - |
- 完整版 (Full Build)
- 同时包含编译器和运行时
- 可以直接在浏览器中编译模板
- 体积较大
- 运行时版本 (Runtime Only)
- 只包含运行时,不包含编译器
- 体积小,比完整版轻约 30%
- 不能在浏览器中编译模板
- ESM 构建版本
- 基于 ES modules
- 支持现代打包工具
注意:如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版。
通过观察package.json可以看到
"scripts": {
"build": "node scripts/build.js",
},
vue2使用 scripts/build.js 脚本进行打包。构建过程通过build(builds)对builds数组进行打包,builds数组包含了所有的打包配置信息。在scripts/config.js中可以查看详细的打包配置信息。我们源码分析主要关注带编译器的版本,入口文件是entry-runtime-with-compiler.ts。
在这个文件中
import Vue from './runtime-with-compiler'
说明了vue引自runtime-with-compiler.ts 同样的,我们查看这个文件就能发现
import Vue from './runtime/index'
其引入的路径在instance/index.ts
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
这样我们终于找到了vue2的本体,也就是这个构造函数。
-
entry-runtime-with-compiler.ts
- 入口文件,添加编译器
-
runtime-with-compiler.ts
- 运行时编译器实现
-
runtime/index.ts
- 运行时核心功能
-
instance/index.ts
- Vue 构造函数定义
Vue的本体就是一个构造函数,而且极其简单,只有一行代码就是调用了_init方法,这个方法甚至在Vue构造函数中找不到。Vue为了跨平台的特性,通过不同的platform入口,给Vue扩展了不同的能力。 文件:entry-runtime-with-compiler.ts。
const mount = Vue.prototype.$mount
Vue.prototype.$mount= function (
el?: string | Element,
hydrating?: boolean
): Component {
...
// 如果render函数不存在
if (!options.render) {
let template = options.template
if (template) {
// template是字符串
if (typeof template === 'string') {
// template开头是# 说明传入的是节点ID
if (template.charAt(0) === '#') {
// 通过idToTemplate函数将节点ID转换为模板
template = idToTemplate(template)
}
// 如果传入的是dom节点,有nodeType说明是dom节点
} else if (template.nodeType) {
// 获取DOM节点上的模版
template = template.innerHTML
} else {
return this
}
// 没有template的情况下如果有el参数则使用el getOuterHTML作为模板
} else if (el) {
// @ts-expect-error
template = getOuterHTML(el)
}
// 如果template模版存在
if (template) {
// 模版编译为render函数
const { render, staticRenderFns } = compileToFunctions(
template,
{
outputSourceRange: __DEV__,
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
},
this
)
// render函数挂载到options上
options.render = render
// 静态渲染函数
// vue会在编译时把静态部分抽离出来生成一个静态渲染函数数组,并对执行后的渲染结果进行缓存
options.staticRenderFns = staticRenderFns
}
}
...
}
在这个带编译器版本的vue中,它通过重写原型链上的$mount函数,给$mount添加了编译模版的能力。最后又调用了原始的$mout 让这个函数具有挂载的能力。
-
模板字符串
<div>{{ message }}</div>
-
解析器(Parser)
- 将模板解析为 AST
- 标记静态节点
-
优化器(Optimizer)
- 对 AST 进行静态分析
- 标记静态子树
-
代码生成器(Code Generator)
- 生成 render 函数
- 生成 staticRenderFns
-
虚拟 DOM
- render 函数执行
- 生成 VNode 树
import { initGlobalAPI } from './global-api/index'
// 通过initGloablAPI函数扩展全局API
initGlobalAPI(Vue)
在initGlobalAPI中添加了mergeOptions、defineReactive、use、mixin、extend、component、filter、directive等工具函数。
在Vue本体的文件中,通过5个函数对Vue本体进行了扩展,分别是 文件:core/index.ts
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
// 给Vue类 添加能力
//@ts-expect-error Vue has function type
// 添加初始化函数_init
initMixin(Vue)
//@ts-expect-error Vue has function type
// Vue类上添加$props $data 的代理添加$watch方法
stateMixin(Vue)
//@ts-expect-error Vue has function type
// 给Vue类上添加$on $off $once $emit方法
eventsMixin(Vue)
//@ts-expect-error Vue has function type
// Vue类上挂载_update、$forceUpdate、$destroy方法
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
// 挂载生成虚拟节点的方法_render和$nextTick,以及执行render函数需要的各种工具函数
renderMixin(Vue)
他们分别为Vue添加了_init函数,添加了$props、$data代理,给Vue类添加$on、$off $once $emit方法,给Vue类添加_update、$forceUpdate、$destroy方法,给Vue类添加_render和$nextTick,以及执行render函数需要的各种工具函数。
Vue 的实例化是从执行 new Vue()
开始的,这个过程主要通过 _init 方法完成初始化。让我们详细了解这个过程。
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
// 设置实例的唯一标识
vm._uid = uid++
// 标记为 Vue 实例
vm._isVue = true
// 避免被响应式系统观察
vm.__v_skip = true
// 创建效果作用域
vm._scope = new EffectScope(true /* detached */)
}
这个阶段主要完成:
- 设置实例唯一标识 _uid
- 标记 Vue 实例,防止被响应式系统观察
- 创建独立的效果作用域
// 合并选项
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
其中mergeOptions的实现包含了合并策略:
- 生命周期钩子函数合并
export function mergeLifecycleHook( parentVal: Array<Function> | null, childVal: Function | Array<Function> | null ): Array<Function> | null { const res = childVal ? parentVal ? parentVal.concat(childVal) : isArray(childVal) ? childVal : [childVal] : parentVal return res ? dedupeHooks(res) : res }
- 子组件和父组件的同名钩子函数将合并为一个数组
- 父组件的钩子函数在子组件之前调用
- 去重
- data 选项
strats.data = function ( parentVal: any, childVal: any, vm?: Component ): Function | null { if (!vm) { return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) } function mergeDataOrFn( parentVal: any, childVal: any, vm?: Component ): Function | null { if (!vm) { if (!childVal) { return parentVal } // 复选项没有,直接返回子选项 if (!parentVal) { return childVal } return function mergedDataFn() { // 如果选项是函数,则先执行这个函数,返回值作为需要merge的data return mergeData( isFunction(childVal) ? childVal.call(this, this) : childVal, isFunction(parentVal) ? parentVal.call(this, this) : parentVal ) } } else { return function mergedInstanceDataFn() { // instance merge const instanceData = isFunction(childVal) ? childVal.call(vm, vm) : childVal const defaultData = isFunction(parentVal) ? parentVal.call(vm, vm) : parentVal if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } }
- 组件定义时 data 必须是函数
- 通过 mergeDataOrFn 递归合并对象的属性
- 资源选项合并
function mergeAssets(
parentVal: Object | null,
childVal: Object | null,
vm: Component | null,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
__DEV__ && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
处理了 components
directives
filters
- 创建一个原型指向父选项的空对象
- 将子选项复制到这个对象上
- 这样可以通过原型链访问父选项中的资源
- watch 选项
strats.watch = function (
parentVal: Record<string, any> | null,
childVal: Record<string, any> | null,
vm: Component | null,
key: string
): Object | null {
// work around Firefox's Object.prototype.watch...
//@ts-expect-error work around
if (parentVal === nativeWatch) parentVal = undefined
//@ts-expect-error work around
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
if (!childVal) return Object.create(parentVal || null)
if (__DEV__) {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret: Record<string, any> = {}
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
if (parent && !isArray(parent)) {
parent = [parent]
}
ret[key] = parent ? parent.concat(child) : isArray(child) ? child : [child]
}
return ret
}
- watch 选项中的函数会被合并为数组
- 支持多个监听器监听同一个属性
- props、methods、inject、computed
strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: Object | null, childVal: Object | null, vm: Component | null, key: string ): Object | null { if (childVal && __DEV__) { assertObjectType(key, childVal, vm) } if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret }
- 子选项优先级高于父选项
- 同名的子选项会覆盖父选项
- provide 选项:
strats.provide = function ( parentVal: Object | null, childVal: Object | null) { if (!parentVal) return childVal return function () { const ret = Object.create(null) mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal) if (childVal) { mergeData( ret, isFunction(childVal) ? childVal.call(this) : childVal, false // non-recursive ) } return ret } }
- 与 data 选项使用相同的合并策略
- 默认策略
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal }
- 子选项存在则使用子选项
- 子选项不存在则使用父选项
- 对于mixin
if (child.mixins) { // 如果具有mixin,对每一个mixin进行合并选项 for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } }
- mixin 循环向父选项上合并
总结:
- 生命周期都会执行,父组件(全局配置、mixin)的钩子函数在子组件(本组件)之前调用。多个mixin,后注册的声明周期后执行。
- 对于数据、方法、filter,本组件覆盖父组件(mixin),多个mixin,后注册的mixin覆盖先注册的mixin。
在 _init
方法中,Vue 按照特定的顺序执行了一系列初始化方法:
// 初始化生命周期
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 初始化渲染
initRender(vm)
// 调用beforeCreate钩子
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// 初始化注入
initInjections(vm)
// 初始化状态
initState(vm)
// 初始化provide
initProvide(vm)
// 调用created钩子
callHook(vm, 'created')
这些初始化方法各自完成特定的任务:
- initLifecycle: 建立父子组件关系,初始化生命周期相关属性
export function initLifecycle(vm: Component) { const options = vm.$options // 找到第一个非抽象的父组件 let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} }
- initEvents: 初始化事件
- 处理父组件传递的事件监听器
- 将事件添加到实例的 _events 属性上
- initRender: 初始化渲染相关功能
- 处理插槽,将其放入 $slots
- 处理作用域插槽,将其放入 $scopedSlots
- 定义 $createElement 方法用于生成虚拟 DOM
- 对 $attrs 和 $listeners 做响应式处理
- initInjections: 初始化注入
- 处理 inject 配置
- 将注入的属性代理到实例上
- initState: 按顺序初始化各种状态,并且会将props、data进行响应式处理
export function initState(vm: Component) { const opts = vm.$options // 初始化props if (opts.props) initProps(vm, opts.props) // Composition API initSetup(vm) // 初始化methods if (opts.methods) initMethods(vm, opts.methods) // 初始化data if (opts.data) { initData(vm) } else { const ob = observe((vm._data = {})) ob && ob.vmCount++ } // 初始化computed if (opts.computed) initComputed(vm, opts.computed) // 初始化watch if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } function initProps(vm: Component, propsOptions: Object) { defineReactive(props, key, value, undefined, true /* shallow */) } function initMethods(vm: Component, methods: Object) { // 函数绑定到vue实例上,this指向当前实例 vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } function initComputed(vm: Component, computed: Object) { ... const watchers = (vm._computedWatchers = Object.create(null)) ... watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) ... } function initWatch(vm: Component, watch: Object) { ... createWatcher(vm, key, handler) ... } function initData(vm: Component) { ... const ob = observe(data) ... }
- initProvide: 初始化 provide
- 将 provide 选项挂载到实例的 _provided 属性上
initState中对数据进行了响应式处理。Vue 的响应式系统主要由三个核心部分组成:Observer(数据劫持)、Dep(依赖收集)和 Watcher(订阅者)。
- observe 函数
observe(data)export function observe(value: any): Observer | void { // 已经是响应式对象,直接返回 if (value && hasOwn(value, '__ob__')) { return value.__ob__ } // 满足以下条件才会创建 Observer: // 1. 是数组或普通对象 // 2. 对象可扩展 // 3. 不是被跳过的对象 // 4. 不是 ref(因为已经是响应式) // 5. 不是 VNode if (shouldObserve && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value.__v_skip && !isRef(value) && !(value instanceof VNode) ) { return new Observer(value) } }
- Observer 类:负责将对象转换为响应式
export class Observer { constructor(value: any) { this.dep = new Dep() // 在值上定义 __ob__ 属性,指向 Observer 实例 def(value, '__ob__', this) if (isArray(value)) { // 数组的特殊处理 if (hasProto) { value.__proto__ = arrayMethods // 覆盖数组原型方法 } else { // 降级处理:直接在对象上定义方法 for (let i = 0; i < arrayKeys.length; i++) { def(value, arrayKeys[i], arrayMethods[arrayKeys[i]]) } } this.observeArray(value) // 递归处理数组元素 } else { // 对象的处理:遍历属性转换为 getter/setter const keys = Object.keys(value) for (let i = 0; i < keys.length; i++) { defineReactive(value, keys[i]) } } } }
- defineReactive 函数:核心响应式处理
export function defineReactive(obj: object, key: string, val?: any) { // 每个属性都有自己的 dep 实例 const dep = new Dep() // 获取属性描述符 const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set // 递归子属性,对子属性进行响应式处理 let childOb = observe(val) // 响应式处理的核心方法 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val // 依赖收集 if (Dep.target) { dep.depend() // 子对象依赖收集 if (childOb) { childOb.dep.depend() } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val if (value === newVal) return val = newVal // 新值可能是对象,继续做响应式处理 childOb = observe(newVal) // 通知更新 dep.notify() } }) }
- 每一个key都会创建一个Dep实例
- 当数据被读取时,也就是get操作时,会依次调用每个Dep实例的depend方法
- 当数据被修改时,也就是set操作时,会依次调用每个Dep实例的notify方法
- 数组的特殊处理
const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function(method) { const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args) const ob = this.__ob__ // 对新增的元素进行响应式处理 let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 通知更新 ob.dep.notify() return result }) })
- 由于 Object.defineProperty 无法监听数组的变化,Vue 通过重写数组方法来实现响应式
- Dep 类:用于收集依赖
export class Dep { constructor() { this.subs = [] } depend() { if (Dep.target) { Dep.target.addDep(this) } } notify() { for (let i = 0, l = subs.length; i < l; i++) { const sub = subs[i] sub.update() } } }
注意:现在只是进行了数据的响应式,但数据既没有被读取,也没有被修改,所以没有触发依赖更新,所以没有触发视图更新。在后面每一个vue实例都会new 一个渲染Watcher,它在第一个创建的时候会执行render函数,读取数据,触发响应式的依赖收集。
如果在实例化时提供了 el 选项,Vue 会自动调用 $mount 方法进行挂载
- 编译模版,如果不存在render函数则使用template生成render函数
runtime-with-compiler.ts
if (!options.render) { ... if (template) { ... const { render, staticRenderFns } = compileToFunctions( template, { outputSourceRange: __DEV__, shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this ) // render函数挂载到options上 options.render = render ... } ... }
- 获取模板:优先使用 template 选项,其次是 el 的 outerHTML
- 将模板编译为 render 函数
- 将编译后的 render 和 staticRenderFns 挂载到 options 上
- 调用mountComponent进行挂载runtime/idnex.ts
挂载的实现在instance/lifecycle.ts
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 查找到对应的挂载节点 el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
if (!vm.$options.render) { vm.$options.render = createEmptyVNode } callHook(vm, 'beforeMount') updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher( // 当前vue实例 vm, // 更新函数 updateComponent, // 空函数作为回调函数 noop, // watcher选项 watcherOptions, true /* isRenderWatcher */ ) if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') }
- 检查 render 函数,不存在则使用空的 VNode
- 调用 beforeMount 钩子
- 创建渲染 Watcher,负责组件的更新,使用 updateComponent作为更新函数,更新函数会通过执行render函数获取虚拟节点,然后传递给_update方法渲染成真是DOM节点
- Watcher 会立即执行一次更新函数,进行首次渲染
- 设置 _isMounted 标志并调用 mounted 钩子
在 Vue 中,组件的更新是由 Watcher 来控制的。每个组件实例都会创建一个渲染 Watcher,它负责在数据变化时重新渲染组件。
-
Watcher 的初始化过程
// 初始化watcher constructor(vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm if (isRenderWatcher) { vm._watcher = this // 保存到组件实例上 } this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before this.getter = expOrFn // 更新函数 // 首次执行 getter 进行依赖收集 this.value = this.lazy ? undefined : this.get() } get() { // 将当前 Watcher 设置为全局的活动 Watcher pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e: any) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } addDep(dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } update() { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } run() { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value } } }
- 创建 Watcher 时,会调用this.get()进行首次渲染,并触发依赖收集
- 将当前 Watcher 设置为全局的活动 Watcher
- 执行 getter 函数(即 updateComponent)
-
Watcher 的依赖收集
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val // 依赖收集 if (Dep.target) { dep.depend() // 子对象依赖收集 if (childOb) { childOb.dep.depend() } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val if (value === newVal) return val = newVal // 新值可能是对象,继续做响应式处理 childOb = observe(newVal) // 通知更新 dep.notify() } }) class Dep { constructor() { this.subs = [] } depend() { if (Dep.target) { Dep.target.addDep(this) } } notify() { for (let i = 0, l = subs.length; i < l; i++) { const sub = subs[i] sub.update() } } }
我们再次将响应式核心代码和Dep的实现贴在这里,方便查看。
- 当数据被读取,也就是get操作时,会依次调用每个Dep实例的depend方法
- Dep.target就是当前正在进行依赖收集的watcher,他会执行addDep方法
- 当Watcher执行addDep的时候,会使用
dep.addSub(this)
将当前的watcher添加到的的dep实例的subs数组中,完成依赖收集
-
Watcher 的更新
- 当数据被修改,也就是set操作时,会依次调用每个Dep实例的notify方法,notify方法会通知当前 Dep 实例的所有sub,也就是所依赖的所有watcher进行更新,执行Watcher的update方法
- update方法会直接使用
this.run()
或者使用queueWatcher队列来使用this.run()
this.run()
会直接调用this.get()
方法,也就是执行getter,这个getter就是updateComponent,会执行render函数生成虚拟节点,然后使用update方法生成真实DOM进行视图更新- 在队列执行run之前会执行watcher.before(),会触发beforeUpdate钩子
- 在队列执行run之后会触发updated钩子
-
批量更新
- queueWatcher 函数中,会将更新函数放入到nextTick队列中,且添加waiting标志位,在flushSchedulerQueue执行更新函数的时候waiting为true,更新函数只会进入队列而不执行。当flushSchedulerQueue执行完成后,waiting标志位会变为false,队列中的更新函数会执行
- vue组件中的nextTick回调会写在数据更新之后,这个回调也会放在nextTick的队列中,且顺序在更新函数之后
- 等更新函数执行完成后,才会执行组件中的nextTick回调,保证了能够拿到最新的DOM