回顾下上篇文章《Vue.js源码学习之依赖收集》中依赖收集的几个流程:
-
- 通过
defineReactive设置普通对象为响应式对象,具体是在Object.defineProperty中通过getter方法收集依赖,通过setter方法通知依赖发生变化;
- 通过
-
- 依赖收集的核心的
Dep构造函数,两个核心方法depend()添加依赖,notify()通知依赖变化,每个Dep实例都有唯一id及依赖订阅数组subs,subs[]数组保存的是Watcher实例对象。
- 依赖收集的核心的
-
- Dep是通过Wacher来完成依赖收集的。
Dep.prototype.depend()是将Watcher实例对象推入到Dep的sub数组中,使watcher与dep建立起关联。响应对象数据发生变化的时候会执行Watcher.prototype.update()方法,从而触发对应的变化操作。
- Dep是通过Wacher来完成依赖收集的。
在前面文章《Vue.js源码学习之Vue的初始化》提到new Vue()的时候会完成initState初始化,包括data,props,computed,watch等等,初始化的过程实际上是将普通对象转换响应式对象的过程,那么Vue.js具体是如何完成这个过程的呢?我们以initData()方法逐行分析为例。
initData()
function initData (vm) {
var data = vm.$options.data;//组件的data属性,可以是对象也可以是一个返回对象的函数,一般我们使用函数。
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) {//data必须是一个纯对象
data = {};
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
var keys = Object.keys(data);//获取data的key
var props = vm.$options.props;//获取组件props对象
var methods = vm.$options.methods;//获取组件中的方法
var i = keys.length;
while (i--) {
var key = keys[i];//遍历data中的key
{
if (methods && hasOwn(methods, key)) {//method中的key不能和data中的key重名,否则报错
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {//组件的props属性不能和data中的key重名,否则报错
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key);//通过代理,把props数据添加到vm对象上,使其也变成响应式
}
}
// 核心方法,设置组件data数据为响应式
observe(data, true /* asRootData */);
}
如果组件中data属性是一个函数,会执行如下方法:
function getData (data, vm) {
// #7573 disable dep collection when invoking data getters
pushTarget();//这里没有传target参数,初始化Dep的target为undefined
try {
return data.call(vm, vm);//此时data的this指向了vm对象实例,将data中的属性变为了当前vm实例的私有属性
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();//把dep.target恢复到上一个状态
}
}
observe
通过逐行分析initData()方法,我们发现最后执行了observe方法,那么该方法是做什么的呢?
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
observe的功能就是来监听数据的变化,它会给非Vnode对象添加一个Observer对象实例。
Observer
var Observer = function Observer (value) {
this.value = value;//当前数据
this.dep = new Dep();//创建新的依赖收集实例
this.vmCount = 0;
def(value, '__ob__', this);//执行 def 函数把自身实例添加到数据对象 value 的 __ob__ 属性上
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);//保留数组原型
} else {
copyAugment(value, arrayMethods, arrayKeys);//复制数组元素
}
this.observeArray(value);//针对数组单独监听
} else {
this.walk(value);//普通对象直接遍历,调用defineReactive$$1方法,具体看下面代码
}
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
observeArray
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
由于Object.defineProperty(obj,prop,descriptor)本身的局限性,参考Object.defineProperty(),所以对于数组,转为响应式数据的时候做了单独处理,相当于是针对数组元素递归调用observe()方法。
defineReactive?1
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
defineReactive 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 getter 和 setter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。
到此Vue.js把普通对象转为响应式对象的过程就分析完了,至于Dep和Watcher的实现,可参考我的另外一篇文章Vue.js源码学习之依赖收集