fszer

陷阱: 不是所有的的对象都可以透明的进行代理

fszer · 2016-11-22翻译 · 867阅读 原文链接

新的 ES6 proxy 代理属性让你能拦截并定制一个对象的操作, 这一般也叫做 target. 拦截与定制是通过一个 handler object 来处理(想象一个监听器)。操作会被处理方法拦截。如果没有这个处理方法, 相应的操作就会被简单的派发给目标

因此, 如果处理对象是个空对象, 这个代理应当透明的包裹着目标。嗨, 但是就像这篇博客文章解释的那样,这不总会正常工作。

啥问题?

下面的代码示例就是一个 Date 无法被代理的例子:

 const target = new Date();
    const handler = {};
    const proxy = new Proxy(target, handler);

    proxy.getDate();
        // TypeError: this is not a Date object.

这个问题在于大多数的内置构建函数都有一个称之为 internal slots(内部插槽) 的实例。这些插槽是与实例相关的属性类的存储。在规范的处理方法中,会把这些插槽当作是名称被包裹在方括号的属性。例如:

 `O.[[GetPrototypeOf]]()`

然而,对它们的访问无法通过通常的 “get” 和 “set” 进行操作,这也就是为什么它不可以被代理。举个例子,当一个方法 proxy.foo() 被代理调用,会被转发到内置的方法 target.foo()。这个方法会检查它的 this ,但无法找到那些内置的插槽,因为 this === proxy

对于 Date 方法,在 JavaScript 中的定义是:

除了特别说明以外,这个 Number 的原型对象下面定义的方法不是通用的,并且把 this 的值传递给他们必须是处于以下两种情况之一,一个数字值,或者一个有着 [[NumberData]] 内部插槽从而可以被初始化为 Number 值的对象。

其他那些不能被透明代理的对象

每当一个对象与 this 关联信息的机制不受到代理所控制,你就有了同样的问题。

举个例子,下面这个类 Person 将私有的信息存储的在 WeakMap _name (更多的信息请看这篇文章):

 const _name = new WeakMap();
    class Person {
        constructor(name) {
            _name.set(this, name);
        }
        get name() {
            return _name.get(this);
        }
    }

Person 的实例就无法被透明的代理:

const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined

数组可以被代理

相比起其他的内置函数,数组是可以被代理的:

 > const p = new Proxy(new Array(), {});
    > p.push('a');
    > p.length
    1
    > p.length = 0;
    > p.length
    0

数组之所以可以被代理,是因为虽然属性的访问为了响应 length 而被定制过,但是它们并没有使用内部插槽。

解决方案

其中一个解决方案, 你可以改变转派方法的调用方法,选择性的将 this 指向目标而不是代理:

 const handler = {
        get(target, propKey, receiver) {
            if (propKey === 'getDate') {
                return target.getDate.bind(target);
            }
            return Reflect.get(target, propKey, receiver);
        },
    };
    const proxy = new Proxy(new Date('2020-12-24'), handler);
    proxy.getDate(); // 24

这种方法的缺点是 this 执行的的方法都不会经过代理。

深入阅读

综合介绍 ES6 代理:“探索 ES6” 的“Metaprogramming with proxies” 章节.

感谢: 感谢Allen Wirfs-Brock指出这篇博文中的错误。

译者fszer尚未开通打赏功能

相关文章