Proxy-Reflect

2022/2/24 JavaScript

# 存储属性描述符监听对象

我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程

通过我们前面所学的知识,能不能做到这一点呢?

  • 其实是可以的,我们可以通过之前的属性描述符中的存储属性描述符来做到;
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
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

但是这样做有什么缺点呢?

  • 首先, Object.defineProperty设计的初衷。不是为了去监听截止一个对象中所有的属性的。

    • 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。
  • 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。

  • 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。

# Proxy 的基本使用

在ES6中,新增了一个 Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:

  • 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象)
  • 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作
const obj = {
    name:"okarin",
    age:18
}

const objProxy = new Proxy(obj,{})
console.log(objProxy.name)
//okarin
1
2
3
4
5
6
7
8

# Proxy的set和get捕获器

如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)

set 和 get 分别对应的是函数类型;

  • set 函数有四个参数:
    1. target:目标对象(侦听的对象) ;
    2. property :将被设置的属性key;
    3. value :新属性值;
    4. receiver:调用的代理对象;
  • get 函数有三个参数:
    1. target:目标对象(侦听的对象) ;
    2. property :被获取的属性key ;
    3. 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
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

# 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
1
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 }
1
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
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

# 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)
1
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
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
Last Updated: 2022/3/5上午12:43:44