Proxy(新一代的属性代理)

972 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:   8月更文挑战 ​

Proxy 简介

Proxy 翻译过来叫做代理,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

基本语法

const p = new Proxy(target, handler)
  1. target要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  1. handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 proxy 的行为,如果没有定义,则会保留源对象的默认行为。

简单实例

我们先看一个空代理,也就是不定义 handler 进行额外操作,代理对象执行的所有操作都会传播到目标对象,同样目标对象任何改变也会传到代理对象,我们看下面实例。

const target = { id: "target" };
const handler = {};
const targetProxy = new Proxy(target, handler);
​
//1.  id属性会访问同一个值
console.log(target.id, targetProxy.id); // target  target//2.  给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
target.id = "foo";
console.log(target.id, targetProxy.id); // foo foo//3.  给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象
targetProxy.id = "bar";
console.log(target.id, targetProxy.id); // bar   bar//4.  hasOwnProperty()方法在两个地方 都会应用到目标对象
console.log(target.hasOwnProperty("id"), targetProxy.hasOwnProperty("id"));  // true  true//5.  Proxy.prototype是undefined

// 因此不能使用instanceof操作符
//instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链

// TypeError: Function has non-object prototype

console.log(target instanceof Proxy,proxy instanceof Proxy); 

'undefined' in instanceof check
​
//6.  严格相等可以用来区分代理和目标
console.log(target === proxy); // false

小结:

我们可以发现,目标对象,和代理他们访问的值相同,任何一方做出改变,另外一方也会做出同样改变,他们就像镜像一样,拥有相同的内容和行为。

Proxy要点详解

我们现在大致知道了何为代理,以及如何创建一个基本的代理,那么新的疑问出现了,我们为什么要使用代理,使用代理可以给我们带来什么,我们接着往下看。

使用代理的目的

可以定义捕获器(trap),捕获器可以直接或者间接的在代理对象上调用,每次在代理对象上调用这些基本操作的时候,代理可以在这些操作传播到目标对象之前先调用捕获函数,从而拦截,并且做出自己定义的行为。

通俗的说就是,捕获器在被代理的对象被调用前先执行,从而拦截并且做出相应的行为,类似加了一层 if 判断,或者说是弹框确认。

我们来看个例图 :

image.png

小结:我们可以看到,一个对象经过代理后,代理身上的操作会相应的执行到对象本身,在代理中有一个 handler 捕获器,可以捕获到操作类型。 例如当用户操作时候,会经过 handler 里的捕获器, 如果用户的操作符合捕获类型,就会进入该捕获器,然后再捕获器中可以规范用户的行为。

如何定义捕获器

在处理程序对象 handler 中,我们可以定义一个或者多个捕获器(trap)去处理代理触发的行为,如果没有定义,则使用默认行为。

一些常用的捕获器

首先我们来看一下最常用的 handle 对象的方法

handler.get()属性读取操作的捕捉器。 属性读取操作的捕捉器。

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};
​
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
​
console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

小结:我们可以看到,我们在 handler 中定义了一个属性读取捕获器,当代理对象收到属性读取操作的时候,会进入该捕获器中,我们看到,当我们读取 p代理时,进入了捕获器 get ,然后发现里面没有 c 属性,然后返回了一个37。

handler.set()属性设置操作的捕捉器。

let validator = {
  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;
  }
};
​
let person = new Proxy({}, validator);
​
person.age = 100;
​
console.log(person.age);
// 100
​
person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer
​
person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

小结:我们可以看到我们定义了一个属性设置捕获器 ,当对象属性被设置时会进入该代理,我们可以看到他做了几层限制,不是 age 属性不要,不是数字类型报错,值大于 200 也报错,然后我们看到相应结果果然如此。

handler.deleteProperty() delete操作符的捕捉器。

var p = new Proxy({}, {
  deleteProperty: function(target, prop) {
    console.log('called: ' + prop);
    return true;
  }
});
​
delete p.a; // "called: a"

小结:我们可以看到,当 Proxy 对象 p 的属性 a 被删除的时候,会进入该捕获器

其余捕获器概述(点击跳转至文档详情)

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()函数调用操作的捕捉器。

handler.construct()new 操作符的捕捉器。

一些不标准的捕捉器已经被废弃并且移除了。