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 = 0
let 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 = newVal
dep.notify()
}
})
})
// 处理添加订阅消息
function watcher(myFun) {
target = myFun
target()
target = null
}
watcher(() => {
total = data.price * data.quantity
})
console.log("total = " + data.total)
data.price = 20
console.log("total = " + data.total)
data.quantity = 10
console.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,即调用代理的原来对象的方法