概述
在Vue2.x版本中,实现响应式的原理是使用了Object.defineProperty将属性转为getter/setter。这样使得Vue能追踪依赖,当属性被访问或修改的时候,通知并重新渲染视图。
Proxy有什么优势?
proxyAPI能够创建一个虚拟的对象来表示原始数据,并且在handler参数里提供了set(),get()和deleteProperty等方法。当原始数据被访问和修改的时候,会拦截到这些操作。它可以让我们从这些限制中解放:
- 使用
Vue.$set()去添加新的属性和使用Vue.$delete删除属性。 - 监听数组的改变。
实现一个ES5版本的响应式系统
模拟Vue里的data选项
// 原始数据
let data = {
price: 10,
quantity: 2,
};
// 在Vue用来存储Watcher,在这里存储一个回调函数
let target = null;
定义一个依赖收集器,存储所有的订阅者。
// 依赖收集器
class Dep {
constructor() {
this.subsribers = [];
}
// 添加依赖
depend() {
if (target && !this.subsribers.includes(target)) {
this.subsribers.push(target);
}
}
// 通知所有订阅者
notify() {
this.subsribers.forEach((sub) => {
sub();
});
}
}
使用defineProperty将属性转为getter/setter
// 把传入的data转为getter/setter
Object.keys(data).forEach((key) => {
const dep = new Dep();
let intervalValue = data[key];
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: () => {
// 添加target到Dep
dep.depend();
return intervalValue;
},
set: (value) => {
intervalValue = value;
// 重新运行存储的方法
dep.notify();
},
});
});
再定义一个watcher,触发依赖收集
function watcher(func) {
target = func;
target(); // 执行回调函数
target = null;
}
watcher(() => {
data.total = data.price * data.quantity;
});
测试一下结果
console.log('total = ' + data.total); // 20
data.price = 20;
console.log('total = ' + data.total); // 40
data.quantity = 10;
console.log('total = ' + data.total); // 200
使用Proxy实现一个响应式系统
先来了解一下Proxy如何来使用。
const observedData = new Proxy(data, {
get(target, key) {
// 此处添加依赖
},
set(target, key, value) {
// 通知订阅者
},
deleteProperty(target, key) {
// 删除属性
}
});
在set()方法中,target指的是就是被代理的那个对象,也就是data。key就是当前被访问属性的key。那么value就是被setter的时候传入的那个值。set方法的神奇之处就是它能感知到属性的添加。
给对象设置deleteProperty就能拦截到某个属性被删除的操作。结合set()和deleteProperty(),完全可以将Vue.set()方法给抛弃掉。
实现响应式系统
首先,修改之前写的Object.keys(data).forEach循环,为每个响应式属性添加一个new Dep
// 存储在一个Map中
const deps = new Map();
Object.keys(data).forEach((key) => {
// 每个属性设置一个Dep实例
deps.set(key, new Dep());
});
接着给data设置代理
// 存储源数据
let data_without_proxy = data;
data = new Proxy(data_without_proxy, {
get(target, key) {
deps.get(key).depend(); // 添加到依赖
return target[key];
},
set(target, key, value) {
target[key] = value;
deps.get(key).notify(); // 重新运行存储的方法
},
deleteProperty(target, key) {
Reflect.deleteProperty(target, key); // 使用Reflect API删除属性
deps.get(key).notify(); // 重新运行存储的方法
},
});
修改一下原始数据和测试数据
// 原始数据
let data = {
price: 10,
quantity: 2,
discount: 5, // 新增一个discount
};
...
let total;
watcher(() => {
total = (data.price - (data.discount || 0)) * data.quantity;
});
console.log('total = ' + total); // 10
data.price = 20;
console.log('total = ' + total); // 30
data.quantity = 10;
console.log('total = ' + total); // 150
Reflect.deleteProperty(data, 'discount');
// 删除掉discount属性,total立即变为了200
console.log('total = ' + total); // 200
再来试试添加一个属性
deps.set('discount', new Dep()); // 给属性设置一个新的依赖实例
data['discount'] = 5; // 添加属性
let salePrice;
watcher(() => {
salePrice = data.price - data.discount;
});
console.log('salePrice = ' + salePrice); // 15
data.discount = 7.5;
console.log('salePrice = ' + salePrice); // 12.5
salePrice被自动更新了。
到此使用Proxy实现一个简易的响应式系统就完成了。