ES6_Proxy(十)

Proxy

  • Proxy从英文可以知道是“代理”的意思
1
const proxy = new Proxy(target, handler)
  • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
  • handler 一个通常以函数作为属性的对象,用来定制拦截行为
1
2
3
4
5
const origin = {}
const obj = new Proxy(origin, {});
obj.a = 1;
console.log(obj.a) // 1
console.log(origin.a) // 1

举个例子,二手房购买

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const 房东 = {}
const 代理中介 = new Proxy(房东, {
get: function (target, propKey, receiver) {
return '没问题'
}
});

代理中介.看房 // 没问题
代理中介.心理底价 // 没问题
代理中介.房屋安全隐患 // 没问题

origin.看房 // undefined 你说啥?
origin.心理底价 // undefined 你说啥?
origin.房屋安全隐患 // undefined 你说啥?
  • 这就好比给房东.get设置了一层代理,所有get操作都会返回中介定制的内部套路“没问题”
  • 代理只会对 proxy 有效
  • 如 目标对象 房东 就没有任何效果

handler 的一些方法

你可以对get操作做判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const origin = {
a: 1
}
const handler = {};
const obj = new Proxy(origin, {
get: function (target, key) {
if (key in target) {
return target[key]
} else {
return undefined
}
}
});
console.log(obj.a) // 1
console.log(origin.a) // 1
console.log(origin.b) // undefined

可撤销的 proxy

1
2
3
4
5
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, {})
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked

Vue3用 Proxy重写

为什么?用 Proxy

Proxy之前, JS中提供Object.defineProperty 允许对对象的 getter/setter 进行拦截

区别

  • Object.defineProperty是在 对象的“属性”上做处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ...
if (Dep.target) {
// 收集依赖
dep.depend()
}
return value
},
set: function reactiveSetter (newVal) {
// ...
// 通知视图更新
dep.notify()
}
})

对象新增属性为什么不更新

1
2
3
4
5
6
7
8
9
10
11
12
13
// vue2里
data () {
return {
obj: {
a: 1
}
}
}
methods: {
update () {
this.obj.b = 2
}
}

因为 data初始化是在 created 之前,并对 data 绑定一个 Observer,之后 data 的更新都会通知依赖收集器 Dep 触发UI更新

  • 而当你在 data 初始化完成后,在添加属性,不会帮你绑定 观察者
  • 这也是为啥 Vue 提供了 $set ,本质就是让你手动添加 observer

数组用索引不会触发更新问题

  • 这个和data 对象后续新增属性一样
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
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

methodsToPatch.forEach(function (method) {
// 缓存原生数组
const original = arrayProto[method]
// def使用Object.defineProperty重新定义属性
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args) // 调用原生数组的方法

const ob = this.__ob__ // ob就是observe实例observe才能响应式
let inserted
switch (method) {
// push和unshift方法会增加数组的索引,但是新增的索引位需要手动observe的
case 'push':
case 'unshift':
inserted = args
break
// 同理,splice的第三个参数,为新增的值,也需要手动observe
case 'splice':
inserted = args.slice(2)
break
}
// 其余的方法都是在原有的索引上更新,初始化的时候已经observe过了
if (inserted) ob.observeArray(inserted)
// dep通知所有的订阅者触发回调
ob.dep.notify()
return result
})
})

Proxy 的优势

  • ES6升级,提供了语言层级的支持
  • Object.defineProperty 监听的是对象身上的 “属性”
    • 当你新增属性你要手动绑定 observe
  • Proxy 劫持的是整个对象,能深度监听