序言
起源:使用Vue实际开发中,数据绑定会对一个对象属性的变化进行监听,并且通过依赖收集做出相应的视图更新等等。
问题:是否能监听所有类型的对象属性?
之前用Object.defineProperty
通过getter/setter
简单实现监听对象的属性变化。
但是,问题依旧存在。正如 Vue 文档 所说:
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
如何解决问题
常见方法
从 Vue 源码 中可以看到确实是对数组做了特殊处理的。
Vue 解决问题的方法是对数组的常用方法进行重写,包装之后的数组方法就能够在调用的时候被监听到。
以下是一种与它相类似的方法,实现监听数组变化。1
2
3
4
5
6
7
8
9
10
11
12
13const arrExtends = Object.create(Array.prototype);
const arrMethods = ['shift','unshift','slice','splice','push','pop'];
let arr = [];
for (let method of arrMethods) {
arrExtends[method] = function(...args) {
Array.prototype[method].call(this, args);
console.log('数组变化了!', args)
}
}
arr.__proto__ = arrExtends;
arr.push(1); // 数组变化了!
ES6方法
ES6 引入了一个新的机制 — Proxy
。先看看概念:
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。1
var proxy = new Proxy(target, handler);
其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。Proxy
的强大之处在于,我们可以直接拦截对代理对象的底层操作。这样我们相当于从一个对象的底层操作开始实现对它的监听。
使用 Proxy
方法监听数组:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53const proxy = (obj) => {
if (typeof obj === 'object' && obj.toString() === '[object Object]') {
for (let key in obj) {
if (typeof obj[key] === 'object') {
defineObjectReactive(obj, key, obj[key]);
} else {
defineBasicReactive(obj, key, obj[key]);
}
}
}
}
const defineObjectReactive = (obj, key, value) => {
proxy(value);
obj[key] = new Proxy(value, {
set(target, propKey, val, receiver) {
if (propKey !== 'length') { //过滤因为length变化而产生的信息
console.log('Set '+propKey+',', 'value: '+val);
}
return Reflect.set(target, propKey, val, receiver);
}
})
}
const defineBasicReactive = (obj, key, value) => {
Reflect.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
return value;
},
set(newValue) {
if (value === newValue) return
console.log(`属性 ${key} :从 ${value} 修改为 ${newValue}`);
value = newValue;
}
})
}
let data = {
name: 'Jiang',
userInfo: {
gender: 0,
movies: []
},
list: []
}
proxy(data)
data.name = 'Solo'
data.userInfo.gender = 0
data.userInfo.movies.push('星际穿越')
data.list.push(1)
最终输出:
输出结果正是我们想要的!
使用Proxy
实现了对对象属性的监听,至于Proxy
还有其他基础用法和骚操作需要读者发挥自己的想象力,未来Proxy
会有更广泛的应用场景。