初始化阶段

为 Vue 实例上初始化一些属性,事件以及响应式数据

new Vue()

合并属性: mergeOptions

  • 把 parent 和 child 这两个对象根据一些合并策略,合并成一个新对象并返回
  • resolveConstructorOptions: 返回 vm.constructor.options,相当于 Vue.options。
  • initGlobalAPI 中定义了 Vue.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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);
}

export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
vm._self = vm;
initLifecycle(vm); // 初始化生命周期
initEvents(vm); // 初始化事件
initRender(vm); // 初始化渲染
callHook(vm, "beforeCreate"); // 调用生命周期钩子函数
initInjections(vm); //初始化injections, resolve injections before data/props
initState(vm); // 初始化props,methods,data,computed,watch
initProvide(vm); // 初始化 provide, resolve provide after data/props
callHook(vm, "created"); // 调用生命周期钩子函数

// 判断用户是否传入了el选项,如果传入了则调用$mount函数进入模板编译与挂载阶段,如果没有传入el选项,则不进入下一个生命周期阶段,需要用户手动执行vm.$mount方法才进入下一个生命周期阶段
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}

export function initGlobalAPI(Vue) {
// ...
Vue.options = Object.create(null);
ASSET_TYPES.forEach((type) => {
Vue.options[type + "s"] = Object.create(null);
});

// 内置组件扩展到 Vue.options.components
// Vue 的内置组件目前 有<keep-alive>、<transition> 和<transition-group> 组件,所以使用这些组件不需要注册
extend(Vue.options.components, builtInComponents);
// ...
}

export const ASSET_TYPES = ["component", "directive", "filter"];

export function mergeOptions(parent, child, vm) {
if (typeof child === "function") {
child = child.options;
}
// 1. 递归把 extends 和 mixins 合并到 parent 上
const extendsFrom = child.extends;
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm);
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
// 2. 创建一个空对象options,遍历 parent,把parent中的每一项通过调用 mergeField函数合并到空对象options里
const options = {};
let key;
for (key in parent) {
mergeField(key);
}
// 3. 接着再遍历 child,把存在于child里但又不在 parent中 的属性继续调用 mergeField函数合并到空对象options里
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
// 4. 策略模式: 针对不同的选项有不同的合并策略,比如针对 data: strats.data方法,针对 watch: strats.watch方法
function mergeField(key) {
const strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options;
}

// 生命周期钩子的合并策略
// 一旦 parent 和 child 都定义了相同的钩子函数,那么它们会把 2 个钩子函数合并成一个数组,并且 parentVal 先执行,用来支持 Vue.mixin
function mergeHook(parentVal, childVal) {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
}

export const LIFECYCLE_HOOKS = [
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"beforeDestroy",
"destroyed",
"activated",
"deactivated",
"errorCaptured",
];

LIFECYCLE_HOOKS.forEach((hook) => {
strats[hook] = mergeHook;
});

export function callHook(vm, hook) {
// 获取钩子名称对应的钩子函数数组,遍历执行
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm);
} catch (e) {
handleError(e, vm, `${hook} hook`);
}
}
}
}

initLifecycle

给实例初始化了一些属性: 以 $ 开头的供用户使用的外部属性,以 _ 开头的供内部使用的内部属性

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
export function initLifecycle(vm) {
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;
// 给实例上挂载$root属性
vm.$root = parent ? parent.$root : vm;

vm.$children = [];
vm.$refs = {};

vm._watcher = null;
vm._inactive = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}

initEvents

  • 父组件给子组件的注册事件中,把自定义事件传给子组件,在子组件实例化的时候进行初始化;而浏览器原生事件是在父组件中处理
  • 实初始化事件函数 initEvents 实际上初始化的是父组件在模板中使用 v-on 或 @ 注册的监听子组件内触发的事件
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// 解析事件
export const onRE = /^@|^v-on:/;
export const dirRE = /^v-|^@|^:/;

function processAttrs(el) {
const list = el.attrsList;
let i, l, name, value, modifiers;
for (i = 0, l = list.length; i < l; i++) {
name = list[i].name;
value = list[i].value;
// 如果是指令
if (dirRE.test(name)) {
// 解析修饰符
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, "");
}
if (onRE.test(name)) {
// v-on 事件指令
name = name.replace(onRE, "");
addHandler(el, name, value, modifiers, false, warn);
}
}
}
}

export function addHandler(el, name, value, modifiers) {
modifiers = modifiers || emptyObject;

// 1. 首先根据 modifier 修饰符对事件名 name 做处理
// check capture modifier 判断是否有capture修饰符
if (modifiers.capture) {
delete modifiers.capture;
name = "!" + name; // 给事件名前加'!'用以标记capture修饰符
}
// 判断是否有once修饰符
if (modifiers.once) {
delete modifiers.once;
name = "~" + name; // 给事件名前加'~'用以标记once修饰符
}
// 判断是否有passive修饰符
if (modifiers.passive) {
delete modifiers.passive;
name = "&" + name; // 给事件名前加'&'用以标记passive修饰符
}

// 2. 根据 modifier.native 判断事件是一个浏览器原生事件还是自定义事件
// 对应生成的data: 自定义 on,原生事件 nativeOn
// {
// on: {"select": selectHandler},
// nativeOn: {"click": function($event) {
// return clickHandler($event)
// }
// }
// }
let events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}

// 3. 按照 name 对事件做归类,并把回调函数的字符串保留到对应的事件中
const newHandler = {
value: value.trim(),
};
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}

const handlers = events[name];
if (Array.isArray(handlers)) {
handlers.push(newHandler);
} else if (handlers) {
events[name] = [handlers, newHandler];
} else {
events[name] = newHandler;
}

el.plain = false;
}

// 创建组件节点 vnode
export function createComponent(Ctor, data, context, children, tag) {
// ...
const listeners = data.on; // 自定义事件data.on 赋值给了 listeners,作为 vnode 的 componentOptions 传入,放在子组件初始化阶段中处理

data.on = data.nativeOn; // 浏览器原生事件 data.nativeOn 赋值给了 data.on,在当前父组件环境中处理的

// ...
const name = Ctor.options.name || tag;
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
);

return vnode;
}

export function initEvents(vm) {
// 存储事件
vm._events = Object.create(null);
// 获取父组件注册的事件赋给listeners,如果listeners不为空,则调用updateComponentListeners函数,将父组件向子组件注册的事件注册到子组件的实例中
const listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}

export function updateComponentListeners(vm, listeners, oldListeners) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove, vm);
target = undefined;
}

function add(event, fn, once) {
if (once) {
target.$once(event, fn);
} else {
target.$on(event, fn);
}
}

function remove(event, fn) {
target.$off(event, fn);
}

// 对比listeners和oldListeners的不同,并调用参数中提供的add和remove进行相应的注册事件和卸载事件
export function updateListeners(
on, // listeners
oldOn, // oldListeners
add,
remove,
vm
) {
let name, def, cur, old, event;
for (name in on) {
def = cur = on[name];
old = oldOn[name];
event = normalizeEvent(name);
if (isUndef(cur)) {
process.env.NODE_ENV !== "production" &&
warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
);
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur);
}
add(
event.name,
cur,
event.once,
event.capture,
event.passive,
event.params
);
} else if (cur !== old) {
// 保证事件回调只添加一次,之后仅仅去修改它的回调函数的引用
old.fns = cur;
on[name] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove(event.name, oldOn[name], event.capture);
}
}
}

initInjections

  • provide 和 inject 选项绑定的数据不是响应式的
  • 先调用 initInjections,后调用 initProvide: 需要保证 inject 能在 data 等 state 中获取到,initInjections -> initState -> initProvide
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
export function initInjections(vm) {
// 将 inject 选项中的数据转化成键值对的形式赋给result
const result = resolveInject(vm.$options.inject, vm);
if (result) {
// 内部把 shouldObserve = false,为了告诉 defineReactive 函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式
toggleObserving(false);
// 遍历键值对,调用defineReactive函数将其添加当前实例上
Object.keys(result).forEach((key) => {
defineReactive(vm, key, result[key]);
});
toggleObserving(true);
}
}

export let shouldObserve = true;
export function toggleObserving(value) {
shouldObserve = value;
}

export function resolveInject(inject, vm) {
if (inject) {
const result = Object.create(null);
const keys = Object.keys(inject);

// 1. 获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个key的from属性记作provideKey,provideKey是上游父级组件提供的源属性
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const provideKey = inject[key].from;
let source = vm;
// 2. 向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break;
}
source = source.$parent;
}
// 3. 如果没有找到,那么就看inject 选项中当前的数据key是否设置了默认值,即是否有default属性,如果有的话,则拿到这个默认值
if (!source) {
if ("default" in inject[key]) {
const provideDefault = inject[key].default;
result[key] =
typeof provideDefault === "function"
? provideDefault.call(vm)
: provideDefault;
} else if (process.env.NODE_ENV !== "production") {
warn(`Injection "${key}" not found`, vm);
}
}
}
return result;
}
}

// 规范化 inject 选项数据,添加 from 属性
// 写法一
var Child = {
inject: ["foo"],
};
// 写法二
const Child = {
inject: {
foo: { default: "xxx" },
},
};
// 写法三
const Child = {
inject: {
foo,
},
};

// 统一格式
const Child = {
inject: {
foo: {
from: "foo",
default: "xxx", //如果有默认的值就有default属性
},
},
};

function normalizeInject(options, vm) {
const inject = options.inject;
if (!inject) return;
const normalized = (options.inject = {});
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] };
}
} else if (isPlainObject(inject)) {
for (const key in inject) {
const val = inject[key];
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val };
}
} else if (process.env.NODE_ENV !== "production") {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
);
}
}

initState (处理 options)

处理 props、methods、data、computed、watch

初始化 props
  • 规范化数据: normalizeProps
  • initProps
  • validateProp
  • getPropDefaultValue
  • assertProp
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
export function initState(vm) {
vm._watchers = []; // 组件级别的侦测
const opts = vm.$options;
if (opts.props) initProps(vm, opts.props);
if (opts.methods) initMethods(vm, opts.methods);
if (opts.data) {
initData(vm);
} else {
observe((vm._data = {}), true /* asRootData */);
}
if (opts.computed) initComputed(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}

function initProps(vm, propsOptions) {
const propsData = vm.$options.propsData || {}; // 父组件传入的真实props数据
const props = (vm._props = {}); // 指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中
const keys = (vm.$options._propKeys = []); // 指向vm.$options._propKeys的指针,缓存props对象中的key,将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的key
const isRoot = !vm.$parent; // 当前组件是否为根组件
// root instance props should be converted
if (!isRoot) {
// 非根组件,则不需要将props数组转换为响应式的
toggleObserving(false);
}
// 历props选项拿到每一对键值,先将键名添加到keys中,
// 然后调用validateProp函数(关于该函数下面会介绍)校验父组件传入的props数据类型是否匹配并获取到传入的值value,
// 然后将键和值通过defineReactive函数添加到props(即vm._props)中
for (const key in propsOptions) {
keys.push(key);
const value = validateProp(key, propsOptions, propsData, vm);
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {
const hyphenatedKey = hyphenate(key);
if (
isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)
) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
);
}
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
);
}
});
} else {
defineReactive(props, key, value);
}

// 判断这个key在当前实例vm中是否存在,如果不存在,则调用proxy函数在vm上设置一个以key为属性的代码,当使用vm[key]访问数据时,其实访问的是vm._props[key]
if (!(key in vm)) {
proxy(vm, `_props`, key);
}
}
toggleObserving(true);
}

export function validateProp(key, propOptions, propsData, vm) {
const prop = propOptions[key]; // 当前key在propOptions中对应的值
const absent = !hasOwn(propsData, key); // 当前key是否在propsData中存在,即父组件是否传入了该属性
let value = propsData[key]; // 当前key在propsData中对应的值,即父组件对于该属性传入的真实值
const booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) {
// 如果absent为true,即父组件没有传入该prop属性并且该属性也没有默认值的时候,将该属性值设置为false
if (absent && !hasOwn(prop, "default")) {
value = false;
}
// 如果父组件传入了该prop属性,则需满足
// 1. 该属性值为空字符串或者 属性值与属性名相等(先将属性名由驼峰式转换成用-连接的字符串,比如 <Child userName="user-name"></Child> 为 true)
// 2. prop 的 type 属性中不存在 String 类型
// 3. 如果 prop 的 type 属性中存在 String 类型,那么 Boolean 类型在 type 属性中的索引必须小于 String 类型的索引,即 Boolean 类型的优先级更高
else if (value === "" || value === hyphenate(key)) {
const stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true;
}
}
}
// 如果不是布尔类型,是其它类型的话,那就只需判断父组件是否传入该属性即可,如果没有传入,则该属性值为undefined
// 此时调用 getPropDefaultValue 函数获取该属性的默认值,并将其转换成响应式
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key);
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve;
toggleObserving(true);
observe(value);
toggleObserving(prevShouldObserve);
}
// 如果父组件传入了该属性并且也有对应的真实值,那么在非生产环境下会调用 assertProp 函数校验该属性值是否与要求的类型相匹配
if (process.env.NODE_ENV !== "production") {
assertProp(prop, key, value, vm, absent);
}
// 将父组件传入的该属性的真实值返回
return value;
}

// 根据子组件 props 选项中的 key 获取其对应的默认值
function getPropDefaultValue(vm, prop, key) {
// no default, return undefined
if (!hasOwn(prop, "default")) {
return undefined;
}
const def = prop.default;
// 对象或数组默认值必须从一个工厂函数获取,注意点
if (process.env.NODE_ENV !== "production" && isObject(def)) {
warn(
'Invalid default value for prop "' +
key +
'": ' +
"Props with type Object/Array must use a factory function " +
"to return the default value.",
vm
);
}
// 如果父组件没有传入该props属性,但是在vm._props中有该属性值,这说明vm._props中的该属性值就是默认值
if (
vm &&
vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key];
}
// 判断def是否为函数并且prop.type不为Function,如果是的话表明def是一个返回对象或数组的工厂函数,那么将函数的返回值作为默认值返回;如果def不是函数,那么则将def作为默认值返回
return typeof def === "function" && getType(prop.type) !== "Function"
? def.call(vm)
: def;
}

// 校验父组件传来的真实值是否与prop的type类型相匹配,如果不匹配则在非生产环境下抛出警告
function assertProp(prop, name, value, vm, absent) {
// 判断prop中如果设置了必填项(即prop.required为true)并且父组件又没有传入该属性,此时则抛出警告: 提示该项必填
if (prop.required && absent) {
warn('Missing required prop: "' + name + '"', vm);
return;
}
// 判断如果该项不是必填的并且该项的值value不存在,那么此时是合法的,直接返回
if (value == null && !prop.required) {
return;
}
let type = prop.type; // prop中的type类型(可以是一个原生构造函数,也可以是一个包含多种类型的数组,还可以不设置该属性)
let valid = !type || type === true; // 校验是否成功(如果用户设置的是原生构造函数或数组,那么此时vaild默认为false(!type),如果用户没有设置该属性,表示不需要校验,那么此时vaild默认为true,即校验成功)
const expectedTypes = []; // 保存期望类型的数组,当校验失败抛出警告时,会提示用户该属性所期望的类型是什么
// 判断该属性是不是数组,如果不是,则统一转化为数组
if (type) {
if (!Array.isArray(type)) {
type = [type];
}
// 遍历type数组,并调用assertType函数校验value。assertType函数校验后会返回一个对象: { vaild:true, // 表示是否校验成功 expectedType: 'Boolean' // 表示被校验的类型 }
// 判断条件 !valid,即type数组中还要有一个校验成功,循环立即结束,表示校验通过
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i]);
expectedTypes.push(assertedType.expectedType || "");
valid = assertedType.valid;
}
}
// 如果循环完毕后vaild为false,即表示校验未通过,则抛出警告
if (!valid) {
warn(
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(", ")}` +
`, got ${toRawType(value)}.`,
vm
);
return;
}
// 此外,props 选项支持自定义校验函数。获取校验函数并检验
// props:{
// propF: {
// validator: function (value) {
// // 这个值必须匹配下列字符串中的一个
// return ['success', 'warning', 'danger'].indexOf(value) !== -1
// }
// }
// }
const validator = prop.validator;
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
);
}
}
}
初始化 methods
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
function initMethods(vm, methods) {
const props = vm.$options.props;
for (const key in methods) {
if (process.env.NODE_ENV !== "production") {
// 只有方法名没有方法体时,抛出异常
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
);
}
// 如果methods中某个方法名与props中某个属性名重复了,就抛出异常
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm);
}
// 如果methods中某个方法名如果在实例vm中已经存在并且方法名是以_或$开头的,就抛出异常: 提示用户方法名命名不规范。
if (key in vm && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
);
}
}
// 如果上述判断都没问题,那就method绑定到实例vm上,通过this.xxx来访问
vm[key] = methods[key] == null ? noop : bind(methods[key], vm);
}
}
初始化 data
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
function initData(vm) {
// 1. 获取到用户传入的data选项,赋给变量data,同时将变量data作为指针指向vm._data
let data = vm.$options.data;
// 2. 判断data是不是一个函数,如果是就调用getData函数获取其返回值,将其保存到vm._data中。如果不是,就将其本身保存到vm._data中
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
// 3. 无论传入的data选项是不是一个函数,它最终的值都应该是一个对象,如果不是对象的话,就抛出警告: 提示用户data应该是一个对象
if (!isPlainObject(data)) {
data = {};
process.env.NODE_ENV !== "production" &&
warn(
"data functions should return an object:\n" +
"https://vuejs.org/v2/guide/components.html##data-Must-Be-a-Function",
vm
);
}
// proxy data on instance
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
// 4. 判断data对象中是否存在某一项的key与methods中某个属性名重复,如果存在重复,就抛出警告
if (process.env.NODE_ENV !== "production") {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
);
}
}
// 5. 再判断是否存在某一项的key与prop中某个属性名重复,如果存在重复,就抛出警告: 提示用户属性名重复
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== "production" &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
);
}
// 6. 如果都没有重复,则调用proxy函数将data对象中key不以_或$开头的属性代理到实例vm上
else if (!isReserved(key)) {
proxy(vm, `_data`, key);
}
}
// 7. 调用observe函数将data中的数据转化成响应式
observe(data, true /* asRootData */);
}
初始化 computed
  • initComputed
  • defineComputed
  • createComputedGetter
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 完整用法
var vm = new Vue({
data: { a: 1 },
computed: {
// 仅读取
aDouble: function () {
return this.a * 2;
},
// 读取和设置
aPlus: {
get: function () {
return this.a + 1;
},
set: function (v) {
this.a = v - 1;
},
},
},
});
vm.aPlus; // => 2
vm.aPlus = 3;
vm.a; // => 2
vm.aDouble; // => 4

function initComputed(vm, computed) {
// 1. 定义了一个变量watchers并将其赋值为空对象,同时将其作为指针指向vm._computedWatchers
const watchers = (vm._computedWatchers = Object.create(null));
const isSSR = isServerRendering();

for (const key in computed) {
// 2. 如果是函数,则该函数默认为取值器getter,将其赋值给变量getter;如果不是函数,则说明是一个对象,则取对象中的get属性作为取值器赋给变量getter
const userDef = computed[key];
const getter = typeof userDef === "function" ? userDef : userDef.get;
// 3. 如果上面两种情况取到的取值器不存在,则抛出警告
if (process.env.NODE_ENV !== "production" && getter == null) {
warn(`Getter is missing for computed property "${key}".`, vm);
}
// 4. 如果不是在服务端渲染环境下,则创建一个watcher实例,并将当前循环到的的属性名作为键,创建的watcher实例作为值存入watchers对象中
if (!isSSR) {
// create internal watcher for the computed property.
const computedWatcherOptions = { computed: true };
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}

// 5. 判断当前循环到的的属性名是否存在于当前实例vm上,如果存在,则在非生产环境下抛出警告;如果不存在,则调用defineComputed函数为实例vm上设置计算属性
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== "production") {
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
);
}
}
}
}

const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
};

export function defineComputed(target, key, userDef) {
const shouldCache = !isServerRendering(); // 标识计算属性是否应该有缓存。该变量的值是当前环境是否为非服务端渲染环境,如果是非服务端渲染环境则该变量为true。也就是说,只有在非服务端渲染环境下计算属性才应该有缓存
// 如果userDef是一个函数,则该函数默认为取值器getter
// 调用createComputedGetter函数(关于该函数下面会介绍)创建了一个getter,
// 这是因为userDef只是一个普通的getter,它并没有缓存功能,所以我们需要额外创建一个具有缓存功能的getter,
// 而在服务端渲染环境下可以直接使用userDef作为getter,因为在服务端渲染环境下计算属性不需要缓存。
// 由于用户没有设置setter函数,所以将sharedPropertyDefinition.set设置为noop
if (typeof userDef === "function") {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef;
sharedPropertyDefinition.set = noop;
} else {
// 如果userDef不是一个函数,那么就将它当作对象处理。
// 在设置sharedPropertyDefinition.get的时候先判断userDef.get是否存在,如果不存在,则将其设置为noop
// 如果存在,则同上面一样
// 设置sharedPropertyDefinition.set为userDef.set函数
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop;
sharedPropertyDefinition.set = userDef.set ? userDef.set : noop;
}
// 判断在非生产环境下如果用户没有设置setter的话,那么就给setter一个默认函数,这是为了防止用户在没有设置setter的情况下修改计算属性,从而为其抛出警告
if (
process.env.NODE_ENV !== "production" &&
sharedPropertyDefinition.set === noop
) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
);
};
}
// 调用Object.defineProperty方法将属性key绑定到target上,其中的属性描述符就是上面设置的sharedPropertyDefinition。如此以来,就将计算属性绑定到实例vm上了
Object.defineProperty(target, key, sharedPropertyDefinition);
}

function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
watcher.depend();
return watcher.evaluate();
}
};
}
初始化 watch
  • initWatch
  • createWatcher
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 完整用法
var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5,
},
},
},
watch: {
a: function (val, oldVal) {
console.log("new: %s, old: %s", val, oldVal);
},
// methods选项中的方法名
b: "someMethod",
// 深度侦听,该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) {
/* ... */
},
deep: true,
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: "someMethod",
immediate: true,
},
// 调用多个回调
e: [
"handle1",
function handle2(val, oldVal) {
/* ... */
},
{
handler: function handle3(val, oldVal) {
/* ... */
},
},
],
// 侦听表达式
"e.f": function (val, oldVal) {
/* ... */
},
},
});
vm.a = 2; // => new: 2, old: 1

function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key];
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}

function createWatcher(vm, expOrFn, handler, options) {
// 写法 c d e
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
// 写法 b
if (typeof handler === "string") {
handler = vm[handler];
}
// 其他情况,认为它是一个函数,就不做任何处理
return vm.$watch(expOrFn, handler, options);
}

模板编译阶段

将模板编译成渲染函数,vm.$mount 方法开始
模板编译阶段并不是存在于 Vue 的所有构建版本中,它只存在于完整版 (即 vue.js) 中,在只包含运行时版本 (即 vue.runtime.js) 中并不存在该阶段。vue 基于源码构建的有两个版本

  • 当使用 vue-loader 或 vueify 时,*.vue 文件内部的模板会在构建时预编译成渲染函数,所以是不需要编译的,从而不存在模板编译阶段,由上一步的初始化阶段直接进入下一阶段的挂载阶段
  • 运行时版本相比完整版体积要小大约 30%,实际开发中,需要借助像 webpack 的 vue-loader 这类工具进行编译,将 Vue 对模板的编译阶段合并到 webpack 的构建流程中,不仅减少了生产环境代码的体积,也大大提高了运行时的性能

runtime only

包含运行时的版本,创建 Vue 实例、渲染并处理 Virtual DOM 等功能,有两种场景

  • 通过手写 render 函数去定义渲染过程,这个时候并不需要包含编译器的版本便可完整执行,new Vue({ render (h) { return h('div', this.hi) }})
  • 借助 vue-loader 等编译工具进行编译,尽管也是利用 template 模板标签去书写代码,但是此时的 Vue 已经不需要利用编译器去负责模板的编译工作了,这个过程交给了插件去实现
1
2
3
4
Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating);
};

runtime + compiler

同时包含编译器和运行时的完整版本
对 template 进行编译,new Vue({ template: '<div>{{ hi }}</div>' })

  • 先将 Vue 原型上的 $mount 方法先缓存起来,记作变量 mount
  • 在源码中,是先定义只包含运行时版本的 $mount 方法,再定义完整版本的 $mount 方法,所以此时缓存的 mount 变量就是只包含运行时版本的 $mount 方法
  • 在完整版本的 $mount 方法中将模板编译完成后需要回头去调只包含运行时版本的 $mount 方法以进入挂载阶段
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el, hydrating) {
// 省略获取模板及编译代码

return mount.call(this, el, hydrating);
};

// 完整实现
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el, hydrating) {
// 1. 根据传入的el参数获取DOM元素
el = el && query(el);
// 2. 获取到el对应的DOM元素如果是body或html元素时,将会抛出警告
// 因为Vue会将模板中的内容替换el对应的DOM元素,如果是body或html元素时,替换之后将会破坏整个DOM文档,所以不允许el是body或html
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this;
}

var options = this.$options;
// 3. 没有手写render函数的情况下获取传入的模板template
if (!options.render) {
var template = options.template;
// 4. 获取用户传入的template选项赋给变量template,如果变量template存在,则接着判断如果template是字符串并且以##开头,则认为template是id选择符,则调用idToTemplate函数获取到选择符对应的DOM元素的innerHTML作为模板
if (template) {
if (typeof template === "string") {
if (template.charAt(0) === "#") {
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
"Template element not found or is empty: " + options.template,
this
);
}
}
}
// 5. 如果template不是字符串,那就判断它是不是一个DOM元素,如果是,则使用该DOM元素的innerHTML作为模板
else if (template.nodeType) {
template = template.innerHTML;
}
// 6. 如果既不是字符串,也不是DOM元素,此时会抛出警告: 提示用户template选项无效
else {
{
warn("invalid template option:" + template, this);
}
return this;
}
}
// 7. 如果变量template不存在,表明用户没有传入template选项,则根据传入的el参数调用getOuterHTML函数获取外部模板
else if (el) {
template = getOuterHTML(el);
}

function getOuterHTML(el) {
if (el.outerHTML) {
return el.outerHTML;
} else {
var container = document.createElement("div");
container.appendChild(el.cloneNode(true));
return container.innerHTML;
}
}

// 8. 将template编译成渲染函数,并设置到$options上
if (template) {
if (config.performance && mark) {
mark("compile");
}

var ref = compileToFunctions(
template,
{
outputSourceRange: "development" !== "production",
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments,
},
this
);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;

if (config.performance && mark) {
mark("compile end");
measure("vue " + this._name + " compile", "compile", "compile end");
}
}
}
return mount.call(this, el, hydrating);
};

挂载阶段

将实例挂载到指定的 DOM 上,即将模板渲染到真实 DOM 中

  • 创建 Vue 实例并用其替换 el 选项对应的 DOM 元素
  • 开启对模板中数据 (状态) 的监控,当数据 (状态) 发生变化时通知其依赖进行视图更新
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
export function mountComponent(vm, el, hydrating) {
vm.$el = el;
// 1. 不存在则设置一个默认的渲染函数createEmptyVNode,该渲染函数会创建一个注释类型的VNode节点
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
}
// 2. 触发beforeMount生命周期,触发后标志着正式开始执行挂载操作
callHook(vm, "beforeMount");

let updateComponent;

// 3. 定义 updateComponent 函数
// 首先执行渲染函数vm._render()得到一份最新的VNode节点树,
// 然后执行vm._update()方法对最新的VNode节点树与上一次渲染的旧VNode节点树进行对比并更新DOM节点(即patch操作),完成一次渲染
// --> 如果调用了updateComponent函数,就会将最新的模板内容渲染到视图页面中,这样就完成了挂载操作的一半工作
// --> 剩下一半工作为开启对模板中数据(状态)的监控,当数据(状态)发生变化时通知其依赖进行视图更新
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
new Watcher(
vm,
// 触发数据或者函数内数据的getter方法,在getter方法中会将watcher实例添加到该数据的依赖列表中,当该数据发生变化时会通知依赖列表中所有的依赖,依赖接收到通知后就会调用第四个参数回调函数去更新视图
// updateComponent函数中读取的所有数据都将被watcher所监控,这些数据中只要有任何一个发生了变化,那么watcher都将会得到通知,从而会去调用第四个参数回调函数去更新视图,如此反复,直到实例被销毁
updateComponent,
noop,
{
before() {
if (vm._isMounted) {
callHook(vm, "beforeUpdate");
}
},
},
true /* isRenderWatcher */
);
hydrating = false;

// 调用 mounted
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, "mounted");
}
return vm;
}

销毁阶段

将当前的 Vue 实例从其父级实例中删除,取消当前实例上的所有依赖追踪并且移除实例上的所有事件监听器

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
Vue.prototype.$destroy = function () {
const vm = this;
// 1. 判断当前实例是否处于正在被销毁的状态
if (vm._isBeingDestroyed) {
return;
}
// 2. 触发生命周期钩子函数beforeDestroy,标志当前实例正式开始销毁
callHook(vm, "beforeDestroy");
vm._isBeingDestroyed = true;
// 3. 将当前的Vue实例从其父级实例中删除
const parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// 4. 移除依赖追踪和事件监听,实例身上的依赖包含两部分
// - 实例自身依赖其他数据,需要将实例自身从其他数据的依赖列表中删除
// - 实例内的数据对其他数据的依赖(如用户使用$watch创建的依赖),也需要从其他数据的依赖列表中删除实例内数据
if (vm._watcher) {
// 将实例自身从其他数据的依赖列表中删除
vm._watcher.teardown();
}
// 遍历 _watchers 列表,移除实例内数据对其他数据的依赖
let i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// 5. 移除实例内响应式数据的引用、给当前实例上添加_isDestroyed属性来表示当前实例已经被销毁,同时将实例的VNode树设置为null
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// 6. 触发生命周期钩子函数destroyed
// fire destroyed hook
callHook(vm, "destroyed");
// 7. 调用实例的vm.$off方法,移除实例上的所有事件监听器
vm.$off();
// 8. 移除一些相关属性的引用
if (vm.$el) {
vm.$el.__vue__ = null;
}
// release circular reference (##6759)
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};