var props = vm._props = {};
var loop = function ( key ) {
keys.push(key);
var value = validateProp(key, propsOptions, propsData, vm);
{
defineReactive(props, key, value, function () {
// ...
});
}
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop( key );
initData
initData 主要逻辑如下,先取到 data 的配置,因为一般 data 会写成一个函数,所以 getData 其实就是执行这个函数获取最终的返回值做为 data 值。
然后将 _data 代理到 vm 实例上,最后调用 observe 转为响应式对象。这里最主要的是 observe 。
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
var keys = Object.keys(data);
var i = keys.length;
while (i--) {
var key = keys[i];
// ...
proxy(vm, "_data", key);
}
// observe data
observe(data, true /* asRootData */);
数据的响应式起点在于
_init
函数中的initState
中,在这个方法里按props、methods、data、computed、watch
顺序初始化数据,并将他们转换为响应式对象。这里只看 props 和 data 的响应式过程,因为最终调用的响应式方法其实都一样只是步骤有所不同,所以先列出 props 和 data 初始化的步骤,最后分析具体的响应式方法实现。
initProps
initProps
主要逻辑如下,遍历组件的 props 配置,获取 prop 值,执行defineReactive
转为响应式对象,再将_props
代理到 vm 实例上。这里最主要的是defineReactive
。initData
initData
主要逻辑如下,先取到 data 的配置,因为一般 data 会写成一个函数,所以getData
其实就是执行这个函数获取最终的返回值做为 data 值。然后将
_data
代理到 vm 实例上,最后调用observe
转为响应式对象。这里最主要的是observe
。响应式关键方法
由上可知,
initProps
和initData
中响应式的关键方法分别是defineReactive
和observe
defineReactive(props, key, value, function () {});
observe(data, true /* asRootData */);
其实在
defineReactive
会引出Dep
类,Dep
类又会引出Watcher
类;observe
会引出了Observer
类。实现数据响应式的关键:
observe
、defineReactive
两个方法。Observer
、Dep
、Watcher
三个类。observe
从代码可以看出,
observe
函数的主要作用是对于一个引用类型对象新建一个Observer
类ob = new Observer(value);
,并且这个 ob 对象最后会保存在value.__ob__
下。defineReactive
defineReactive
函数里真正调用Object.defineProperty
劫持了getter / setter
。getter 中通过dep.depend()
收集依赖,setter 中通过dep.notify()
触发更新。这个函数中需要额外注意的地方有三点:
defineReactive
中生成了一个Dep
对象,在getter / setter
通过闭包访问。其实单纯只看
defineReactive
函数中的这个Dep
对象,倒是没有什么特别的,但是稍后会交代Observer
类中实际上也会生成一个Dep
对象,这两个Dep
对象的作用是一样的,为什么需要两个Dep
呢?接下来的第二点 / 第三点都于此有关。getter
中收集依赖部分不止是单纯dep.depend()
还有对于childOb
的判断。实际上
childOb
是这样获取的childOb = observe(val)
,从之前的observe
介绍可以知道,这里返回的就是Observer
实例,并且只有在val
是一个引用类型时才会有值。判断这个
childOb
的目的是为了拿到第一点中说的第二个Dep
对象执行依赖收集的操作。所以回到第一点中提出的问题「为什么需要两个Dep
?」这是因为Object.defineProperty
存在缺陷,无法劫持对象类型数据的属性增删和数组类型数据的子项增删,对于这种情况,Vue
提供了Vue.$set
方法,在该方法中需要手动触发更新,但是又拿不到通过闭包访问的Dep
,所以才需要在val.__ob__
上保存一个相同的Dep
对象,实际上如果通过其他方式让我们能够访问defineReactive
中的Dep
对象,那么这里是不需要两个的。至于
dependArray(value);
则是对于数组的特殊操作,数组的索引是非响应式的,数组中的每一项引用类型的属性都应该收集到当前的依赖,确保在使用$set
或Vue.set
时,数组中嵌套的对象能正常响应。setter
中会对新的值重新执行一次observe(newVal)
因为新的值有可能是一个引用类型,所以需要该操作。
Observer
Observer
的实例会保存在value.__ob__
上,接着对于数组和对象类型会执行不同的操作walk
函数,遍历对象的keys
,执行defineReactive
转为响应式属性。protoAugment
或copyAugment
劫持数组的变异方法,observeArray
函数,遍历属性,执行observe
转为响应式属性。Dep
Dep
是收集依赖的框,Dep.target
指向当前的watcher
,Dep.subs
存放依赖的所有watcher
。Watcher
老实说
Watcher
其实在响应式的流程中不太明显,不认真看都看不出来有用到。Watcher
根据传的参数不同可以分为render watcher、computed watcher、watch watcher
,这里暂时当成只有render watcher
即可。响应式的收集 / 更新过程如下:
收集:访问某个响应式数据 → 触发
getter
收集依赖更新:修改某个响应式数据 → 触发
setter
更新依赖这里的依赖其实就是指的
watcher
,而「访问响应式数据」这个操作最明显的显然是在template
中了吧,template
在$mount
过程中会被编译为render
函数,之后执行render
,这里就触发了收集依赖的过程了,不过实际上render
函数不是直接被调用,而是做为Watcher
中的getter
函数被调用的。这就是
render watcher
构建入口,结合Watcher
的构造函数看一下,删掉无关的代码,其实render watcher
我认为在新建实例的过程中最重要的就是把updateComponent
赋值给getter
,接着执行get
函数取了一次值,而get
函数的作用主要就是pushTarget(this)
把当前的watcher
赋值给Dep.target
,然后执行getter
这里也就是updateComponent
,然后pushTarget()
把Dep.target
置为空。这样一套下来,只要
render
函数中访问到的响应式数据显然收集到的就是这个render watcher
了。响应式数据更新时,实际上会遍历
Dep
的subs
执行subs[i].update();
也就是watcher
的update
方法,为了防止同一个watcher
重复更新,会先收集到一个队列中最后在nextTick
中遍历执行watcher.run
,对于render watcher
可以认为在run
方法中又执行了一次updateComponent
也就完成了视图的更新。