vue源码阅读 (一) 核心函数

代码结构

主要看src里面的代码:

-src

​ -compile (编译)

​ -core(核心)

​ -components 组件

​ -global-api 全局api

​ -instance 实例

​ -observer 观察者

​ -util 使用

​ -vdom 虚拟DOM

​ -platforms 平台

​ -server 服务

​ -sfc 网络

​ -shared 共享

主要部分为:

src
|— compile 模板编译的代码,1.0 和 2.0 版本在模板编译这一块改动非常大
|— core/instance 生命周期,初始化入口
|— core/vdom 虚拟DOM
|— entries 编译入口文件

现在来看core里的代码

index.js

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
// Vue 核心方法
import Vue from './instance/index' // 得到vue示例
//初始化一些全局API
import { initGlobalAPI } from './global-api/index' //全局api
// 获取一个Boolean类型的变量,来判断是不是ssr
import { isServerRendering } from 'core/util/env'
// 功能呈现上下文
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

// 初始化全局变量
initGlobalAPI(Vue)
// 为Vue原型定义属性$isServer
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
// 为Vue原型定义属性$ssrContext
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
return this.$vnode && this.$vnode.ssrContext
}
})
// 为SSR运行时帮助程序安装公开FunctionalRenderContext
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue

处理 Vue option选项

🍃 当我们实例化一个Vue时发生了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

initMixin(Vue)

首先看看initMixin方法

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
// 如果不是生产环境, 开启了监控, 且有对应方法 => 开启性能监控
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
// 标记开始标签
mark(startTag)
}
// 一个避免被观察到的标志
// 如果是Vue的实例,则不需要被observe
vm._isVue = true
// 合并 options参数
// 第一步: options参数的处理
if (options && options._isComponent) { // 如果传入参数是一个组件
initInternalComponent(vm, options) //初始内部组件 将option相关参数赋值给vm.$option
} else { //如果不是组件
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
// 第二步: renderProxy
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 第三步: vm的生命周期相关变量初始化
initLifecycle(vm)
// 第四步: vm的事件监听初始化
initEvents(vm)
//初始化呈现
initRender(vm)
// 调用生命周期钩子
callHook(vm, 'beforeCreate')
// 初始注入
initInjections(vm)
// 第五步: vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化,因此也是Vue实例create的关键。
initState(vm)
initProvide(vm)
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}

// 第六步:render & mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}

主要是为Vue创建了一个_init方法, 该方法会在执行new Vue(options)的时候调用.下面来看每一步都做了什么:

第一步 options参数的处理

1
2
3
4
5
6
7
8
9
if (options && options._isComponent) { // 如果传入参数是一个组件
initInternalComponent(vm, options) //初始内部组件 将option相关参数赋值给vm.$option
} else { //如果不是组件
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}

此处调用了3个函数:

  1. initInternalComponent
  2. resolveConstructorOptions
  3. mergeOptions

让我们依次看看, 到底做了什么

initInternalComponent 初始化vm.$option, 并将入参options中的值赋值给$option

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
// vm.constructor.options 作为__proto__初始化$options
const opts = vm.$options = Object.create(vm.constructor.options)
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode

const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag

if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}

object.constructor (返回对创建此object的数组函数的引用。)

Object.create(a) 方法创建一个新对象,使用现有的对象a来提供新创建的对象的__proto__。

resolveConstructorOptions 解析vm.constructor的options

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
export function resolveConstructorOptions(Ctor: Class<Component>) {
let options = Ctor.options
// 判断该类是否是Vue的子类
if (Ctor.super) {
// 递归的获取根super的Option
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
// 判断父类中的options 有没有发生变化,主要考虑到 Vue.extend(options) ,Vue.mixin(options)
if (superOptions !== cachedSuperOptions) {
// 当为Vue混入一些options时,superOptions会发生变化,
// 此时与之前子类中存储的cachedSuperOptions已经不等,
// 所以下面的操作主要就是更新sub.superOption
Ctor.superOptions = superOptions
// 获取被修改过的option
const modifiedOptions = resolveModifiedOptions(Ctor)
// 如果option被修改过 更新 extendOptions
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// merge自己的options与父类的options属性
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}

super关键字用于访问和调用一个对象的父对象上的函数。

其中使用了resolveModifiedOptions方法, 我们看下他干了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
// 返回Ctor.options与Ctor.sealedOptions 不一致的的属性构成的对象
function resolveModifiedOptions(Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}

这样看下来, 其实对Ctor类里面每个属性到底代表了没有概念(比如: options , sealedOptions 和 extendOptions), 所以我们可以先看下实现Vue 子类功能的 Vue.extend 方法initExtend方法中被定义, initExtend在initGlobalAPI方法中执行

Vue.extend

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}

const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}

const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super

//对于props和computed属性,我们在扩展原型上定义扩展时Vue实例上的代理getter。
// 这样可以避免为每个创建的实例调用object.defineproperty。
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}

// 允许 以后的函数使用 extension/mixin/plugin
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use

// 创建资产寄存器,这样扩展类也可以拥有它们的私有资产。
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// 启用递归自查找
if (name) {
Sub.options.components[name] = Sub
}

// 在扩展时保留对super选项的引用。
// 稍后在实例化时,我们可以检查super的选项是否已更新。
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)

// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}

我们可以看到这个函数,实现了一个JS的经典继承方法,最后返回了一个继承自Super(允许开始时的this) 的子类Sub

mergeOptions 将两个option对象合并为新对象。用于实例化和继承的核心实用程序。

我们来看下mergeOptions的实现:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}

if (typeof child === 'function') {
child = child.options
}

// 统一props格式
normalizeProps(child, vm)
// 统一Inject的格式
normalizeInject(child, vm)
// 统一directives的格式
normalizeDirectives(child)

// 在子选项上应用扩展和混合,
// 但仅当它是一个原始选项对象而不是另一个mergeoptions调用的结果。
// 只有合并的选项具有_base属性。
if (!child._base) {
// 如果存在child.extends
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
// 如果存在child.mixins
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
// 针对不同的键值,采用不同的merge策略
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// Vue提供了一个strats对象,其本身就是一个hook,如果strats有提供特殊的逻辑,就走strats,否则走默认merge逻辑
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}

默认的merge逻辑

1
2
3
4
5
6
// 默认的merge逻辑
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}

strats方法

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
// 选项覆盖策略是处理如何将父选项值和子选项值合并到最终值的函数。
const strats = config.optionMergeStrategies
// 有限制的选项
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
`option "${key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
}
}

strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)

return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}

接下来看看mergeDataOrFn操作,如果options.data是个函数,主要是执行函数后,再进行data的merge

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
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// 在vue.extend合并中,两者都应该是函数
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// 当parentval和childval都存在时,我们需要返回一个函数,
// 该函数返回两个函数的合并结果…这里不需要检查parentval是否是函数,
// 因为它必须是一个函数才能传递以前的合并。
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}

mergeData

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
// 将两个数据对象递归合并在一起的帮助程序。
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal

const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)

for (let i = 0; i < keys.length; i++) {
key = keys[i]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal)
}
}
return to
}

到这里我们知道了,options.data经过merge之后,实际上是一个function, 在真正调用function才会进行真正的merge。

走到这一步,我们终于把业务逻辑以及组件的一些特性全都放到了vm.$options中了,后续的操作我们都可以从vm.$options拿到可用的信息。框架基本上都是对输入宽松,对输出严格,vue也是如此,不管使用者添加了什么代码,最后都规范的收入vm.$options中。

第二步: renderProxy

定义了vm._renderProxy,这是后期为render做准备的,作用是在render中将this指向vm._renderProxy

一般而言,vm._renderProxy是等于vm的,但在开发环境,Vue动用了Proxy这个新API

1
2
3
4
5
6
7
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm