JS进阶 | Proxy代理对象

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

JS进阶系列文章

什么是Proxy

Proxy对象用于创建一个对象的代理,是用于监听一个对象的相关操作。代理对象可以监听我们对原对象的操作。

接下来我们将通过一个监听对象的属性操作来认识学习下什么是Proxy

Proxy对象需要传入两个参数,分别是需要被Proxy代理的对象和一系列的捕获器(PS:下面会讲)。

const obj={
  name:'_island'
}

const objProxy=new Proxy(obj,{});

console.log(objProxy);
复制代码

image-20220204200341915

打印出来可以看到的是一个Proxy对象。下面我们开始看看Proxy中的捕获器对象。

Proxy捕获器

在实例化Proxy对象时,第二个参数传入的是捕获器集合,我们在其对象内定义一个get捕获器,用于监听获取对象值的操作。

// 定义一个普通的对象obj
const obj = {
  name: "_island"
};

// 代理obj这个对象,并传入get捕获器
const objProxy = new Proxy(obj, {
  // get捕获器
  get: function (target, key) {
    console.log(`捕获到对象获取${key}属性的值操作`);
    return target[key];
  },
});

// 通过代理对象操作obj对象
console.log(objProxy.name);
// 捕获到对象获取name属性的值操作
// _island
复制代码

objProxy对象的拦截器中新增一个捕获器set,用于监听对象的某个属性被设置时触发。

// set捕获器
set: function (target, key, val) {
  console.log(`捕获到对象设置${key}属性的值操作,新值为${val}`);
  target[key] = val;
}

console.log(objProxy.name = "QC2125");
// 捕获到对象设置name属性的值操作,新值为QC2125
console.log(objProxy.name);
// 捕获到对象获取name属性的值操作
// QC2125
复制代码

如果不想这个属性被设定这个值,你可以抛出异常告诉开发者,该值不能被设定。

set: function (target, key, val) {
  if (key==='age' && typeof val === "number") {
    target[key] = val;
  } else {
    throw new TypeError("该属性的值必须是Number类型");
  }
}
复制代码

我们也可以监听对象是否调用了getPrototypeOf操作,使用getPrototypeOf捕获器即可。

// 监听getPrototypeOf
getPrototypeOf: function () {
  console.log(`监听到对象getPrototypeOf操作`);
},
复制代码

Proxy中共有13个捕获器,它们用于我们对对象、函数的方法调用监听。下面是Proxy捕获器以及它们的触发条件。

对象中的方法 对应触发条件
handler.getPrototypeOf() Object.getPrototypeOf 方法的捕捉器
handler.setPrototypeOf() Object.setPrototypeOf 方法的捕捉器
handler.isExtensible() Object.isExtensible 方法的捕捉器
handler.preventExtensions() Object.preventExtensions 方法的捕捉器
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty() Object.defineProperty 方法的捕捉器
handler.has() in 操作符的捕捉器
handler.get() 属性读取操作的捕捉器
handler.set() 属性设置操作的捕捉器
handler.deleteProperty() delete 操作符的捕捉器
handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
handler.apply() 函数被apply调用操作的捕捉器
handler.construct() new 操作符的捕捉器

this指向的问题

Proxy对象可以对我们的目标对象进行访问,但没有做任何拦截时,也不能保证与目标对象的行为一致,因为目标对象内部的this会自动改变为Proxy代理对象。我们看下面这个例子就知道了。

const obj={
 name:'_island',
 foo:function(){
   return this === objProxy
 }
}

const objProxy=new Proxy(obj,{})
console.log(obj.foo()); // false
console.log(objProxy.foo()); // true
复制代码

对象监听案例

某些场景下,需要监听一个对象的操作,当这个操作触发时执行另外的一个函数,就像vue2中的watchApi,它可以监听data数据中某个属性的改变并操作指定的函数。

我们看看下面这份代码,在ES5中使用Object.defineProperty(对象属性描述符)对对象的监听,将一个对象进行遍历,并设定gettersetter方法进行监听和拦截。

// 定义一个Object对象
const obj = {
  name: "_island",
  age: 18
};

Object.keys(obj).forEach((key) => {
  let val = obj[key];
  Object.defineProperty(obj, key, {
    get: function () {
      console.log(key + "调用了get方法");
      return val;
    },
    set: function (newVal) {
      console.log(key + "调用了set方法");
      val = newVal;
    }
  });
});

// 操作obj对象
obj.name = "QC2125";
// name调用了set方法
obj.age = 30;
// age调用了set方法
console.log(obj.name); 
// name调用了get方法
// QC2125
复制代码

Object.defineProperty的设计初衷并不是为了去监听拦截一个对象中的属性,且他也实现不了更加丰富的操作,例如添加、删除属性等操作。所以在ES6中新增了Proxy对象,用于监听ObjectFunction的操作。

我们将上面通过Object.defineProperty实现对象监听的方法修改成Proxy方案。在Vue3框架中的响应式原理也是用到了Proxy对象进行对属性的监听操作。

const obj = {
  name: "_island",
  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);
// 监听到了name被获取值
// _island
console.log(objProxy.age);
// 监听到了age被获取值
// 18
objProxy.name = "QC2125";
// 监听到了name被设置值
console.log(objProxy.name);
// 监听到了name被获取值
// QC2125
复制代码

总结

proxy是一个的代理对象,它可以代理我们对原目标的操作。相比Object.defineProperty方法,Proxy监听的事件更加方法。

猜你喜欢

转载自juejin.im/post/7060864025373966343