# 存储属性描述符监听对象
我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
通过我们前面所学的知识,能不能做到这一点呢?
- 其实是可以的,我们可以通过之前的属性描述符中的存储属性描述符来做到;
obj = { name: "okarin", age: 18, address: "chengdu" }
Object.keys(obj).forEach((key)=>{
let value = obj[key]
Object.defineProperty(obj,key,{
get:function(){
console.log(`监听到obj的${key}被访问了`)
return value
},
set:function(newValue){
console.log(`监听到obj的${key}被设置了`)
value = newValue
}
})
})
obj.name = "Retr0"
obj.age = 19
console.log(obj.name,obj.age)
//监听到obj的name被设置了
//监听到obj的age被设置了
//监听到obj的name被访问了
//监听到obj的age被访问了
//Retr0 19
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
但是这样做有什么缺点呢?
首先, Object.defineProperty设计的初衷。不是为了去监听截止一个对象中所有的属性的。
- 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。
其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。
所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。
# Proxy 的基本使用
在ES6中,新增了一个 Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:
- 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象) ;
- 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作;
const obj = {
name:"okarin",
age:18
}
const objProxy = new Proxy(obj,{})
console.log(objProxy.name)
//okarin
2
3
4
5
6
7
8
# Proxy的set和get捕获器
如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)
set 和 get 分别对应的是函数类型;
- set 函数有四个参数:
- target:目标对象(侦听的对象) ;
- property :将被设置的属性key;
- value :新属性值;
- receiver:调用的代理对象;
- get 函数有三个参数:
- target:目标对象(侦听的对象) ;
- property :被获取的属性key ;
- receiver :调用的代理对象;
const obj = {
name:"okarin",
age:18
}
const objProxy = new Proxy(obj,{
//获取值时的捕获器
get:function(target,key){
console.log(`${key}被访问了`)
return target[key]
},
set:function(target,key,newValue){
console.log(`${key}被设置了`)
target[key] = newValue
}
})
console.log(objProxy.name)
objProxy.name = "retr0"
console.log(objProxy.name)
//name被访问了
//okarin
//name被设置了
//name被访问了
//retr0
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
# Proxy的其他捕获器
# 监听 in 的捕获器
const obj = {
name:"okarin",
age:18
}
const objProxy = new Proxy(obj,{
//获取值时的捕获器
has:function(target,key){
console.log(`${key}的in操作`,target)
return key in target
}
})
console.log("name" in objProxy)
//name的in操作 { name: 'retr0', age: 18 }
//true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 监听 delete 的捕获器
const obj = {
name:"okarin",
age:18
}
const objProxy = new Proxy(obj,{
//捕获器
deleteProperty:function(target,key){
console.log(`${key}被删除了`,target)
delete target[key]
}
})
delete objProxy.name
console.log(objProxy)
//name被删除了 { name: 'retr0', age: 18 }
//{ age: 18 }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Proxy所有捕获器
handler.getPrototypeOf()
Object.getPrototypeOf
方法的捕获器handler.setPrototypeOf()
Object.setPrototypeOf
方法的捕获器。handler.isExtensible()
Object.isExtensible
方法的捕获器。handler.preventExtensionso
Object.preventExtensions
方法的捕获器。handler.getOwnPropertyDescriptor)
Object.getOwnPropertyDescriptor
方法的捕捉器。اhandler.defineProperty()
Object.defineProperty
方法的捕捉器。handler.ownKeys
Object.getOwnPropertyNames
方法和Object.getOwnPropertySymbols
方法的捕捉器。handler.has()
in
操作符的捕捉器。handler.get() 属性读取操作的捕捉器。
handler.set() 属性设置操作的捕捉器。
handler.deleteProperty()
delete
操作符的捕捉器。handler.apply() 函数调用操作的捕捉器。
handler.construct()
new
操作符的捕捉器。
# Reflect的作用
Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
# Reflect的作用
Reflect 主要提供了很多操作 JavaScript 对象的方法,有点像 Object 中操作对象的方法;
比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf() ;
比如 Reflect.defineProperty(target, propertyKey, attributes) 类似于Object.defineProperty():
# 为什么需要Reflect
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
另外还包含一些类似于in,delete操作符,让JS看起来是会有一些奇怪的;
所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;
# Reflect常见方法
它和 Proxy 是一一对应的也是13个:
Reflect.getPrototypeOf(target) 类似于
Object.getPrototypeOf()
.Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数.返回一个
Boolean
,如果更新成功,则返 回true
.Reflect.isExtensible(target) 类似于
Object.isExtensible()
Reflect.preventExtensions(target) 类似于
Object.preventExtensions()
,返回一个Boolean
.Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于
Object.getOwnPropertyDescriptor()
.如果对象中存在 该属性,则返回对应的属性描述符,否则返回undefined
.Reflect.defineProperty(target, propertyKey, attributes) 和
Object.defineProperty()
类似。如果设置成功就会返回true
.Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于
Object.keys()
,但不会受enumerable
影响).Reflect.has(target,propertyKey) 判断一个对象是否存在某个属性,和
in
运算符的功能完全相同。Reflect get(target,propertyKey,receiver]) 获取对象身上某个属性的值,类似于
target[name]
Reflect.set(target,propertyKey,value,receiver]) 将值分配给属性的函数。返回一个
Boolean
,如果更新成功,则返回true
Reflect.deleteProperty(target,propertyKey) 作为函数的
delete
操作符,相当于执行delete target[name]
。Reflect.apply(target,thisArgument,argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和
Function.prototype.apply()
功能类似。Reflect.construct(target,argumentsList[,newTarget])
对构造函数进行new 操作,相当于执行
new target(...args)
。
# Reflect receiver参数
使用receiver
参数改变this,让对象所有的操作都经过Proxy代理对象,都能被捕获器捕获。
obj = {
_name: "okarin",
name: "fake",
get name() {
return this._name
},
set name(newValue) {
this._name = newValue
},
}
const objProxy = new Proxy(obj, {
get: (target, key, receiver) => {
console.log(`${key}被访问了`)
return Reflect.get(target, key, receiver)
},
set: (target, key, newValue, receiver) => {
console.log(`${key}被设置了`)
Reflect.set(target, key, newValue, receiver)
},
})
console.log(objProxy.name)
objProxy.name = "retr0"
console.log(objProxy.name)
// name被访问了
// _name被访问了
// okarin
// name被设置了
// _name被设置了
// name被访问了
// _name被访问了
// retr0
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
# Reflect construct补充
使用 construct 改变对象的原型
class Person {
//类的构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
//类的实例方法
running() {
console.log(this.name + 'running~');
}
}
class Student{}
const person = Reflect.construct(Person,["okarin",18],Student)
console.log(person)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Proxy + Reflect
const obj = {
name:"okarin",
age:18
}
const objProxy = new Proxy(obj,{
//获取值时的捕获器
get:function(target,key){
console.log(`${key}被访问了`,target)
return Reflect.get(target,key)
},
set:function(target,key,newValue){
console.log(`${key}被设置了`,target)
return Reflect.set(target,key,newValue)
}
})
console.log(objProxy.name)
objProxy.name = "retr0"
console.log(objProxy.name)
// name被访问了 { name: 'okarin', age: 18 }
// okarin
// name被设置了 { name: 'okarin', age: 18 }
// name被访问了 { name: 'retr0', age: 18 }
// retr0
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