vue2响应式原理
Object.defineProperty()
要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法。下面这些概念引自MDN。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
该方法允许精确地添加或修改对象的属性
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。
存取描述符还具有以下可选键值:> get> 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined。> set> 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined。
划重点
1.Object.defineProperty() 操作的目标始终是对象的属性。
2.对象有存(set)取(get)描述符,使用 Object.defineProperty() 方法可以实现对对象属性进行存取拦截。
3.Object.defineProperty() 可以给对象添加一个属性,并且可以给属性设置一些数据描述符(是否可枚举,是否可写···)。 这里要记住哦,后边会考!这里要记住哦,后边会考!这里要记住哦,后边会考!
设置a属性值为3,不可以修改值,不可以被枚举
例1:
Object.defineProperty(obj, 'a', { value: 3,// 是否可写writable: false// 是否可以被枚举enumerable: false
});
拦截对象属性的存取操作
接下来我们尝试使用存取描述符拦截对象属性的存取操作。
使用 Object.defineProperty 给属性设置了 getter 和 setter,那么每次读取该属性,就会自动调用 getter 函数,且读取的值就是 getter 函数返回的值,每次修改设置该属性的值就会自动调用 setter 函数。
例2:
Object.defineProperty(obj, 'a', {// getter get() {return 4;},// setter set(newValue) {console.log(newValue);}
});
console.log(obj.a) // 4
obj.a = 5 // 5 这里会输出5,因为调用了 set 函数
console.log(obj.a) // 4,这里还是会输出 4,因为读取的值就是 get 函数返回的值
相信大家从 例2 就能看出由于 get 函数返回的是一个常数 4,所以无论何时读取 obj 的 a 属性得到的值都为 4 ;并且给 a 属性赋新值后,新值也没有地方保存。所以不得不使用一个变量作为返回值返回和保存新值。
例3:
var temp;
Object.defineProperty(obj, 'a', {// getter get() {return temp;},// setter set(newValue) {temp = newValue; }
});
console.log(obj.a) // undefined 初始并没有给属性 a 赋值。
obj.a = 5
console.log(obj.a) // 5
这样写看似已经完美了,但是会造成新问题,temp 是全局变量,有可能会造成变量污染。所以最好的方法应该是在 Object.defineProperty() 方法外边再封装一层函数达成闭包(这里的 val 就相当于上面代码的 temp 变量)。
例4:
function defineReactive(data, key, val) {Object.defineProperty(data, key, {// getter get() {return val;},// setterset(newValue) {// 若新值没有变化if (val === newValue) { return; } val = newValue; }});
}
封装一个简单的 defineReactive 函数
好了我们已经实现了一个函数能够对对象属性进行简单的拦截存取操作。可以试试下面的例子(这里稍微改造一下 defineReactive 函数,增加两条输出以证明拦截成功):
function defineReactive(data, key, val) {Object.defineProperty(data, key, {// getter get() {console.log('触发读值拦截');return val;},// setterset(newValue) {console.log('触发存值拦截');// 若新值没有变化if (val === newValue) { return; } val = newValue; }});
}
var obj = {};
defineReactive(obj,'a',4);
console.log(obj.a) // 触发读值拦截 4
obj.a = 5; // 触发存值拦截
console.log(obj.a) // 触发读值拦截 5
数据响应式
我们先来聊聊什么