ES6 一月 05, 2020

对ES6-proxy的理解

文章字数 5.9k 阅读约需 5 mins. 阅读次数 0

ES6(ECMAScript2015) 是 JavaScript 一个版本标准,作为前端爱好者是必须要掌握的,本篇记录我对ES6中,proxy 的理解。

字面理解

Proxy 在英语中是代理的意思,那我们就引发了几个疑问:

  1. 它代理了什么?
  2. 为什么要代理?
  3. 如何使用代理?

首先我们引用阮老师的 《ECMAScript 6 入门》 书籍中的概述:

Proxy 可以理解成,在目标对象之前架设一层“拦截器”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

那么通过阮老师的这句话,我们可以得出第一个问题的答案:

它代理了对象

为什么要代理

第二个问题是为什么要使用 proxy?我总结了以下几点:

  • 拦截和监视外部对象的访问
  • 降低函数或类的复杂度
  • 在复杂操作前对操作进行校验或对所需资源进行管理

先来一个不使用 proxy 的栗子:

let user = {
    name: 'John',
    surname: 'Doe'
};

let printUser = (property) => {
    let value = user[property];
    if (!value) {
        throw new Error(`The property [${property}] does not exist`);
    } else {
        console.log(`The user ${property} is ${value}`);
    }
}

printUser('name'); // 输出: 'The user name is John'
printUser('email'); // 抛出错误: The property [email] does not exist

这是一个简单的例子,我们有一个带有几个属性的用户对象,如果属性存在,我们想要打印用户信息,如果不存在,则抛出异常。通过上面的代码,你会发现:将条件和异常移到其他地方,而 printUser 中仅关注显示用户信息的实际逻辑会更好。这是我们可以使用代理对象的地方,让我们更新下这个例子。

let user = {
    name: 'John',
    surname: 'Doe'
};

let proxy = new Proxy(user, {
    get(target, property) {
        let value = target[property];
        if (!value) {
            throw new Error(`The property [${property}] does not exist`);
        }
        return value;
    }
});

let printUser = (property) => {
    console.log(`The user ${property} is ${proxy[property]}`);
};

printUser('name'); // 输出: 'The user name is John'
printUser('email'); // 抛出错误: The property [email] does not exist

在上面的示例中,我们包装了 user 对象,并设置了一个 get 方法。此方法充当拦截器,在返回值之前,会首先对属性值进行检查,如果不存在,则抛出异常。

输出与第一种情况相同,但此时 printUser 函数专注于逻辑,只处理消息。

实例方法

proxy除了代理get,set操作,还能代理其他的操作,如下:

handler.getPrototypeOf()
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。

handler.setPrototypeOf()
// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。

handler.isExtensible()
// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。

handler.preventExtensions()
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。

handler.getOwnPropertyDescriptor()
// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。

handler.defineProperty()
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。

handler.has()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。

handler.get()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。

handler.set()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。

handler.deleteProperty()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。

handler.ownKeys()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。

handler.apply()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。

handler.construct()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。

proxy栗子

通过属性查找数组中的特定对象

以下代理为数组扩展了一些实用工具,可一看到,你可以灵活地“定义”属性,而不需要使用 Object.defineProperties 方法。

let products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
],
{
  get: function(obj, prop) {
    // 缺省行为是返回属性值, prop ?通常是一个整数
    if (prop in obj) {
      return obj[prop];
    }

    // 获取 products 的 number; 它是 products.length 的别名
    if (prop === 'number') {
      return obj.length;
    }

    let result, types = {};

    for (let product of obj) {
      if (product.name === prop) {
        result = product;
      }
      if (types[product.type]) {
        types[product.type].push(product);
      } else {
        types[product.type] = [product];
      }
    }

    // 通过 name 获取 product
    if (result) {
      return result;
    }

    // 通过 type 获取 products
    if (prop in types) {
      return types[prop];
    }

    // 获取 product type
    if (prop === 'types') {
      return Object.keys(types);
    }

    return undefined;
  }
});

console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3

参考文章

0%