原理
Vue 会遍历data
选项所有的 property,并使用 Object.defineProperty
把这些 property 全部转为getter/setter,在 property 被访问和修改时通知变更,以实现响应式。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
总结:
*对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
* 数组类型:通过重写数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
存在问题(部分可以通过Vue.set解决):
* 对象新增属性、删除属性, 界面不会更新。
* 直接通过下标修改数组, 界面不会自动更新。
实现
1.针对一般类型和对象类型
- 把data选项中的所有property转为getter/setter以实现响应式
- 如果property也是对象,使用递归方式为其添加响应式
- setter操作的新值为对象时,使用observe方法为其添加响应式
- 新增属性使用defineReactive方法添加响应式
- 使用Object.assign为已有对象添加多个property时,需要为已有对象重新赋值
// 判断是否object
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
};
// 1.为对象增加响应式
function observe(obj) {
if(isObject(obj)) {
for(let key in obj) {
defineReactive(obj, key, obj[key]);
// 2.递归式为深层对象添加响应式
observe(obj[key]);
};
}
};
// 实现为对象每个属性增加响应性
function defineReactive(obj, key, value) {
// 这里的value使用了闭包原理。由于get和set函数仍然在使用value变量,因此他并不会被销毁
Object.defineProperty(obj, key, {
get() {
console.log(`${key}取值: `, value);
return value;
},
set(newValue) {
if(value === newValue) return;
// 3.如果新值是对象,仍然为其添加响应式
observe(newValue);
console.log(`${key}存值:`, newValue);
value = newValue;
}
});
};
const data = {
mode: 'normal',
option: {
name: '123',
value: 'x',
}
};
observe(data);
// 4.为新增属性增加响应性,vue提供了Vue.set,我们只需调用defineReactive函数就能达到目的
defineReactive(data.option, 'mode', 'normal');
// 5.如果要用Object.assign方法为已有对象添加一个或多个新property,这样的property没有响应式,所以需要为已有对象重新赋值
const option2 = {
name: '789',
value: 'xxx',
};
data.option = Object.assign({}, data.option, { option2 } );
2.针对数组
重写数组的一系列方法
// 复制数组原型对象
const arrProperty = Object.create(Array.prototype);
// 重写部分数组原型方法
const methods = ['pop', 'push', 'shift', 'unshift', 'splice'];
for(let i of methods) {
arrProperty[i] = function() {
console.log(`调用了数组${i}方法`);
Array.prototype[i].call(this, ...arguments);
};
}
// 为对象增加响应式
function observe(obj) {
// 如果是数组则修改原型
if(Array.isArray(obj)) obj.__proto__ = arrProperty;
if(isObject(obj)) {
for(let key in obj) {
defineReactive(obj, key, obj[key]);
// 递归式为深层对象添加响应式
observe(obj[key]);
};
}
};