本篇实现的仅仅是对象监听,不包含数组。关于数组的下一篇会讲。
一、用什么来监听对象?
Vue给了我们很好的回答,那就是defineProperty。defineProperty方法有以下几个参数:
- obj: 你要设置属性的对象;
- propName: 你设置的属性的名称;
- descriptor: 描述符对象;
这个descriptor对象可以设置的属性就比较多了:
- value: 设置属性的值;
- writable: 默认为false,如果该值为false,则不能再修改属性的值;
- enumerable: 默认为false,如果该值为false,则不能被枚举;
- configurable: 默认为false,如果该值为false,那么descriptor不能再被修改,也不能使用delete;
- get与set:存取器方法,默认为undefined。
其他的descriptor对象理解应该不难,看了MDN上的教程,应该会发现对于set和get方法的使用,总是会在外部声明一个变量来维护值的变化,是不是很变扭,然后就会有人尝试用this来调用属性,这时就会报执行栈溢出,因为它递归的执行自己,导致栈溢出。
这里我们可以通过set和get内部维护的'_'开头的私有变量来解决:
const obj = {};
Object.defineProperty(obj,'name', {
get () {
console.log('读取name的值为 => ' + this._name);
return this._name;
},
set (newValue) {
this._name = newValue;
console.log('设置name的值为 => ' + this._name);
}
})
obj.name = 'xiaoming'; // '设置name的值为 => xiaoming'
obj.name; // '读取name的值为 => xiaoming'复制代码
两种方法看着喜好用吧。
二、实现对象的监听
首先我们得有一个对象:
const obj = {
a: 1,
b: {
c: 2,
d: 3
}
};复制代码
接下来我们要将它变成一个响应式对象,让它的取值与赋值操作多在我们的监听下,首先我们要通过一个Observer来包装我们需要响应化的对象:
function Observer(value) {
/**
* 构造函数防止没有使用'new'关键字创建
*/
if (!(this instanceof Observer)) {
return new Observer(value);
}
this.value = value;
// 这一篇仅仅讨论对象的监听
if (!Array.isArray(value)) {
this.walk(value);
}
}
Observer.prototype.walk = function (obj) {
const keys = Object.keys(obj);
for (let i = 0, max = keys.length; i < max; i++) {
const key = keys[i];
//将对象响应化
defineReactive(obj, key, obj[key]);
}
}复制代码
接下来核心就是采用defineProperty方法来监听set和get方法,但是这里我们要注意的是采用递归创建Observer对象的方式,解决对象嵌套的问题:
function defineReactive(obj, key, val) {
//获取对象的描述器对象
const property = Object.getOwnPropertyDescriptor(obj, key);
//这里上面强调了configurable属性的重要性
if (property && property.configurable === false) {
return;
}
const setter = property && property.set;
const getter = property && property.get;
//解决对象嵌套的问题
observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
const value = getter ? getter.call(obj) : val;
log('读取',obj,key,value);
return value;
},
set (newValue) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newValue);
} else {
val = newValue;
}
log('设置',obj,key,value);
}
})
}
function observe(obj) {
if (!isObject(obj)) {
return;
}
return new Observer(obj);
}复制代码
这里基本上对象的监听已经完成了,下面来试试:
Observer(obj);
obj.b.d = 10;复制代码
但是这里还存在一个问题,因为我们还有这种操作:
obj.f = 10;复制代码
这时obj.f不再被我们监听,对于这种情况在Vue中我们要通过Vue.set方法设置,这里我们也可以写了set方法:
function set(target, key, val) {
if(!target.hasOwnProperty(key)) {
target[key] = val;
defineReactive(target, key, val);
}
}复制代码
这里是个简易的set方法,主要思想就是使新属性响应化。
三、总结
通过源码的学习,使你在使用Vue的一些api更加得心应手。
PS:源码用的flow.js,看起来有点费劲,但是我应该会坚持下去。(下一篇会讲数组的监听)