v3 会使用带有 getter 和 setter 的处理程序遍历其所有 property 并将其转换为 Proxy 。这是 ES6 仅有的特性,在 v3 版本也使用了 Object.defineProperty 来支持 IE 浏览器。
v2 将 data 整个对象变为可观察对象,通过递归的方式给每个 Key 使用 Object.defineProperty 加上 getter 和 settter ,如果是数组就重写代理数组对象的七个方法。
Object.defineProperty
观察者例子
let data = { price: 5, quantity: 2 }let total = 0let target = null// 发布订阅器class Dep {constructor () {this.subscribers = []}depend () {if (target && !this.subscribers.includes(target)) {this.subscribers.push(target)}}notify () {this.subscribers.forEach(sub => sub())}}// 给每个data的属性挂载发布订阅器,消息代理Object.keys(data).forEach(key => {let internalValue = data[key]const dep = new Dep()Object.defineProperty(data, key, {get() {dep.depend()return internalValue},set(newVal) {internalValue = newValdep.notify()}})})// 处理添加订阅消息function watcher(myFun) {target = myFuntarget()target = null}watcher(() => {total = data.price * data.quantity})console.log("total = " + data.total)data.price = 20console.log("total = " + data.total)data.quantity = 10console.log("total = " + data.total)
缺点
不能监听数组的变化
- 数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.
- Vue 把会修改原来数组的方法定义为变异方法 (mutation method)
- 非变异方法 (non-mutating method):例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。
- Vue 的做法是把这些方法重写来实现数组的劫持。
必须遍历对象的每个属性
- Object.defineProperty() 多数要配合 Object.keys() 和遍历
- 必须深层遍历嵌套的对象
let ccobj = {cmm: {name: 'kskjkjkjdfg'}}
Proxy
例子
```javascript let data = { price: 5, quantity: 2 } let data_without_proxy = { price: 5, quantity: 2 }; // 保存源对象 let total = 0 let target = null let deps = new Map(); // 创建一个Map对象
Object.keys(data).forEach(key => { // 为每个属性都设置一个依赖实例 并放入 deps 中 deps.set(key, new Dep()); });
class Dep { constructor () { this.subscribers = [] } depend () { if (target && !this.subscribers.includes(target)) { this.subscribers.push(target) } } notify () { this.subscribers.forEach(sub => sub()) } }
data = new Proxy(data_without_proxy, { // 重写数据以在中间创建一个代理 get(obj, key) { deps.get(key).depend(); // <— 依旧为存储target return obj[key]; // 返回原始数据 }, set(obj, key, newVal) { obj[key] = newVal; // 将原始数据设置为新值 deps.get(key).notify(); // <— 依旧为重新运行已存储的targets return true; } });
function watcher(myFun) { target = myFun target() target = null }
watcher(() => { total = data.price * data.quantity; }); ```
优点
- 针对对象:针对整个对象,而不是对象的某个属性
- 支持数组:不需要对数组的方法进行重载,省去了众多 hack
- 嵌套支持: get 里面递归调用 Proxy 并返回
- 添加新的响应性属性要使用Vue.$set(),删除现有的响应性属性要使用Vue.$delete()。v3不需要了
其他
Proxy中经常用到Reflect.get 和 Reflect.set ,可以理解为类继承里的 super,即调用代理的原来对象的方法
