JS proxy

745 阅读3分钟

proxy

proxy 的定义是,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。其重点就是监听,vue3的响应式抛弃object.defineProperty,使用proxy也是看中了其在拦截响应上的优秀特质。

一、proxy的使用

const p = new Proxy(target, handler)

target:被代理的对象
handler:捕获器
尝试一下:

let obj = {
   name:"cici",
   age:18
}
const proxyObj = new Proxy(obj,{})

console.log(proxyObj)

//{ name: 'cici', age: 18 }

这里是可以出来obj的值的,所以Proxy对象具体源对象的属性。但是它还包括proxy其他的相对应的属性。

目标对象的内部的this指向会自动改为proxy代理对象

二、捕获器

handler捕获器,常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

this 上下文绑定在handler 对象上,并不是绑定在proxy对象上

1.handler.get()

handler.set() : 拦截对象的读取属性操作
只要读取对象的值就会进入 get方法

let getObj = {
    text:"hello"
}
let getProxy = new Proxy(getObj,{
    get(target, property, receiver){
          `target`:目标对象。
          `property`: 被获取的属性名。
          `receiver`: Proxy 或者继承 Proxy 的对象
    }
})
console.log(getObj.text)

2.handler.set()

handler.set() : 拦截设置属性值的操作

let setProxy = new Proxy({},{
    get(target, property, value,receiver){
          `target`:目标对象。
          `property`: 被获取的属性名。
          `value`:赋予的新值
          `receiver`: Proxy 或者继承 Proxy 的对象
    }
})
  • set() 方法应当返回一个布尔值。返回为true代表设置成功,如果为false,应该抛出一个异常
  • 如果目标属性没有配置存储方法,即 [[Set]] 属性的是 undefined,则不能设置它的值
let setProxy = new Proxy({}, {
    set: function(target, prop, value, receiver) {
        try {
            target[prop] = value;
            return true;
        }catch{
           console.error(`${prop} form source is not defined`)
        }
      
    }
  })
  
  p.a = 10;

3.has()

针对in 操作符的代理方法

let p = new Proxy(target, {
  has: function(target, prop) {
   `prop`:检查是否存在的属性
  }
});

has方法 会返回一个 boolean

let setProxy = new Proxy({},{
    set: function(target, prop, value, receiver) {
        try {
            target[prop] = value;
            console.log('property set: ' + prop + ' = ' + value);
            return true;
        }catch{
           console.error(`${prop} form source is not defined`)
        }
      
    },
    has(target,prop){
        console.log(`porperty has function`)
    }
  })
  console.log('name' in setProxy)
 //porperty has function
 //false

4.handler.construct()

用于拦截new操作符

let p = new Proxy(target, {
  construct: function(target, argumentsList, newTarget) {
     `argumentsList`:参数列表
     `newTarget`:最初被调用的 构造函数
  }
});

必须返回一个对象 Proxy 初始化时,传给它的 target 必须具有一个有效的 constructor 供 new 操作符调用。

  function animals(type){
         this.type = type
  }
  const animalsProxy = new Proxy(animals,{
    construct(target,args){
        console.log("this is animalsProxy constractFn")
        console.log(args)
        return new target(...args);
    }
  })
  let a1 = new animalsProxy("dogs")

5.handler.deleteProperty()

用于拦截对对象属性的delete操作

var p = new Proxy(target, {
  deleteProperty: function(target, property) {
     `property`:待删除的属性名
  }
});

与has类似,deleteProperty 必须返回一个Boolean 类型的值,表示了该属性是否被成功删除。

6.其他

方法作用返回值
handler.defineProperty()拦截对象的Object.defineProperty()boolean
handler.getPrototypeOf()读取代理对象的原型一个对象或者 null

三、使用

1.转发数据

  let t1 = Object.create(null)
  let t1Proxy = new Proxy(t1, {});
  t1Proxy.a = 37;   // 
  console.log(t1.a);    // 37

2.拦截验证

let validProxy = new Proxy({}, {
    set: function(obj, prop, value) {
        if (prop === 'age') {
          if (!Number.isInteger(value)) {
            throw new TypeError('The age is not an integer');
          }
          if (value > 200) {
            throw new RangeError('The age seems invalid');
          }
        }
    
        // The default behavior to store the value
        obj[prop] = value;
    
        // 表示成功
        return true;
      }
  });
  
  validProxy.age = 100;
  
  console.log(person.age);
  // 100
  
  validProxy.age = 'young';
  // 抛出异常:Uncaught TypeError: The age is not an integer
  
  validProxy.age = 300;
  // 抛出异常:Uncaught RangeError: The age seems invalid

四、总结

Object.defineProperty对比,对数据监听更加优秀与便利

      使用Object.defineProperty对象监听,其实并不能对一个对象的修改、删除等操作进行很好的监听。defineProperty只能对具体的属性值,在对象的属性层面进行响应式的监听。在vue2中,通过递归进行处理,使其性能并不很完美。在vue2中对数组的监听,是vue重写了数组的操作方法,例如 push、splice等,触发此方法后,进行数据更新。而对未进行数据绑定的属性也需要使用$set添加监听。

let obj = {
    name:"cici",
    age:18
}
for(let i in obj){
    let val = obj[i]
    Object.defineProperty(obj,i,{
        get(){
            console.log("this is getter Func")
            return val
        },
        set(v){
            console.log("this is setter Func")
            val = v * 2
        }
    })
}
console.log(obj.name)
obj.age = 6
console.log(obj.age)
/**
this is getter Func
cici
this is setter Func
this is getter Func
12
**/

      上述只是一个简单的例子来说明一下使用原理。在vue3中则是使用了Proxy&Reflect进行实现。