在正式说数据响应化之前,先让我们简单说下options的处理,其实对options
的处理过程也挺复杂,但是这些细节并不是本文关注的重点,所这里我们只挑它的主要代码讲,至于一些细节比如如何进行 normalize
等有兴趣的话可以自己看看源码。
正如上图所示,我们上一章讲到过,在 _init
函数中,有这么一段代码进行 options
的合并,生成一个新的 this.$options
。当然实际情况比这个图要复杂些。
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
这一段代码主要调用了两个函数 resolveConstructorOptions
和 mergeOptions
,让我们从调用顺序来分别看看这两个函数的作用。
resolveConstructorOptions
会递归向super
查找 options
,如果找到了,那么就把父类的options
和当前的options
进行合并。
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
// 把父类的options找出来,因为可能父类也有父类,因此这里是递归查找,把`parents`上的所有options都合并到当前。
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
//如果找到了,那么就把父类的 `options`和当前的 `options`进行一次合并。
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor //如果指定了`name`,那么在自己身上注册一下自己。这样就可以直接在模板中用了。
}
}
}
return options
}
可能大家会有疑问,哪里来的父类呢?我们一般有两种方式来创建一个Vue组件:
//最常见的方式是直接创建,这种情况下不需要处理父类
new Vue(options)
//如果我们有一些常用的默认配置,我们可以自己创建一个自定义的类,此时,我们就需要进行 `options`合并了,不然我们自己创建的这个类上就没有 `MyVue.options`了。
const MyVue = Vue.extend(options)
new MyVue()
那么我们再看第二个函数,也就是 mergeOptions
,顾名思义,就是把几个不同的 options
合并成同一个,mergeOptions
函数的定义在 core/util/options.js
中。因为options中有很多字段,不同字段的处理方式会不同,比如有些字段就会子类覆盖父类,有些字段可能就需要把值合并起来。我们先看看 mergeOptions
函数:
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
}
// normalize,归一化处理,这是什么意思呢?
// 因为vue为了方便使用,同样的定义可以以不同的形式写出来,比如 `props:[‘name’]` 和 `props: { name: { type: String}}` 都是可以的,在解析这些配置之前,就要先把配置的格式统一一下以方便处理
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
// 把孩子中的合并
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
// mixins一般也是一个 options,因此也要处理。
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
// 这里真的开始进行父子组件的merge操作了。注意,不同的filed是有不同方案的,比如到底是要覆盖还是要合并等。`mergeFiled`函数是通过调用 `strats`上定义好的一些函数规则来实现的,他对不同的 `key` 有不同的规则,比如 `listeners` 就要进行数组的合并,`data` 就要进行 `key` 的合并,而一些其他的就直接子类覆盖父类。
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
好了,我们已经大致知道了 Vue
是如何处理 options
的,下面让我们正式进入数据响应化吧。