ES6中的Proxy和Reflect

95 阅读4分钟

监听对象的操作

我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程。我们可以通过之前的属性描述符中的存储属性描述符来做到。如果不知道对象属性描述符的可以看这篇文章:对象属性描述符

使用Object.defineproperty的缺点:

  1. 首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的。我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强 行将它变成了数据属性描述符。
  2. 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty是无能为力的。

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

存取属性描述符的操作

监听一个属性,以obj中的name属性为例子

const obj = {
  name:'harry',
  age:21
}

Object.defineProperty(obj,"name",{
  get(){
    console.log('监听到Obj对象的name属性被访问了');
    return undefined ; //这是默认的操作
  },
  set(){
    console.log('监听到obj对象的name属性被设置了');
  }
})

console.log(obj.name);
obj.name = 'zhangsan'

如果要监听多个属性

const obj = {
  name:'harry',
  age:21
}

Object.keys(obj).forEach(item=>{
  let value = obj[item]
  Object.defineProperty(obj,item,{
    get(){
      console.log(`监听到obj对象的${item}属性被访问了`);
      return value
    },
    set(newValue){
      console.log(`监听到obj对象的${item}属性被设置值`);
      value = newValue
    }
  })
})

obj.name = 'kobe'
obj.age = 21
console.log(obj.name);
console.log(obj.age);

Proxy基本使用

在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的。也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。

采用Proxy的方式监听对象

注意,这里的set捕获器有四个参数:target:目标对象(侦听的对象),property:将被设置的属性key,value:新属性值,receiver:调用的代理对象。

get捕获器有三个参数:target:目标对象(侦听的对象)、property:被获取的属性key、receiver:调用的代理对象。

const obj = {
  name : 'harry',
  age:21
}

const objProxy = new Proxy(obj,{
  //获取值时候的捕获器,会替换掉它默认的捕获器
  get(target,key){ //target是当前的对象,key是属性
    console.log(`监听对象的${key}属性被访问了`,target);
    return target[key] 
  },
  //设置值的捕获器
  set(target,key,newValue){ 
    target[key] = newValue
    console.log(`监听对象的${key}属性被设置值了${newValue}`,target);
  }
  
})

console.log(objProxy.name);
objProxy.age = 30

console.log(obj.age); //30

如果不写捕获器,默认也是进行如上的操作。

const obj = {
  name : 'harry',
  age:21
}

const objProxy = new Proxy(obj,{

})

console.log(objProxy.name);
objProxy.age = 30

console.log(obj.age); //30

Proxy的捕获器

Proxy一共有13种捕获器。

image.png

比较常用的捕获器

const obj = {
  name:'harry', //数据属性描述符
  age:18
}


//变成一个访问属性描述符

//这里有13个捕获器
const objProxy = new Proxy(obj,{
  //获取值时候的捕获器,会替换掉它默认的捕获器
  get:function(target,key){ //这是会自动进行回调的(target,key)会传两个属性
    console.log(`监听对象的${key}属性被访问了`,target);
    return target[key] //target是当前的对象
  },

  //设置值的捕获器
  set:function(target,key,newValue){  
    target[key] = newValue
    console.log(`监听对象的${key}属性被设置值了,值为${newValue}`,target);
  }, 
  //监听in的捕获器
  has(target,key){
    console.log(`监听对象的${key}属性的in操作`,target);
    return key in target
  },
  //监听delete的捕获器
  deleteProperty(target,key){
    console.log(`监听对象的${key}属性的delete操作`,target);
    delete target[key]
  }
}) 

// in操作符
console.log("name" in objProxy); //true

//delete操作
delete objProxy.name

Proxy对函数对象的监听

function foo(){

}

const fooProxy = new Proxy(foo,{
  //针对apply的捕获器
  apply(target,thisArg,argArray){
    console.log("对foo函数进行了apply调用");
    return target.apply(thisArg,argArray)
  },
  //针对new的捕获器
  construct(target,argArray,newTarget){
    return new target(...argArray) //参数展开
  } 
})


// foo()
// foo.apply({},["abc","cba"])

fooProxy.apply({},["abc","cba"])
new fooProxy("abc","cba") 

Reflet的使用

Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。那么这个Reflect有什么用呢?

  • 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
  • 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
  • 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

  • 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面
  • 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适
  • 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪
  • 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上

Reflect常见的方法:

image.png

Reflect案例

const obj = {
  name : 'harry',
  age:18
}

const objProxy = new Proxy(obj,{
  get(target,key,receiver){
    console.log(`${target}对象的${key}属性被调用了`);
    return Reflect.get(target,key) //替换return target[key]
  },
  set(target,key,newValue,receiver){
    console.log('----------set-------------');

    //使用Reflect的好处
    const result = Reflect.set(target,key,newValue)
     //会返回结果,判断设置成功和设置失败,这是它的优势
    if(result){

    }else{

    }
  }
})

objProxy.name = "kobe"
console.log(objProxy.name);

关于Reciever参数的作用

const obj = {
  _name:'harry',
  get name(){
    return this._name //这个this指向哪?答案是Obj对象
                     //想办法让这个this指向Proxy对象而不是obj对象
                    //receiver就是做这个功能的           
  },
  set name(newValue){
    this._name = newValue
  } 
}

const objProxy = new Proxy(obj,{
  get(target,key,receiver){
    return Reflect.get(target,key,receiver) //这第三个参数可以改变obj对象中的this
  },
  set(target,key,newValue,receiver){
    Reflect.set(target,key,newValue,receiver)
  }

})

objProxy.name = '张三'
console.log(obj.name);

Reflect中construct的使用

这个在某些情况下会使用到,比如下面的代码,要求执行Student函数的内容,但是创建出来的是Teacher对象。

function Student(name,age){
  this.name = name
  this.age = age

}
function Teacher(){

}

const stu = new Student('harry','21')
console.log(stu); //Student类型
console.log(stu.__proto__ === Student.prototype);

//执行Student函数的内容,但是创建出来的对象是Teacher对象

//执行Student函数中的内容
const teacher = Reflect.construct(Student,["harry",18],Teacher) //第一个参数,执行的构造函数,第二个参数为属性,第三个为需要创建出来的对象
console.log(teacher);
console.log(teacher.__proto__ === Teacher.prototype);