暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

快速了解ES6的代理与反射

海人为记 2021-12-17
379

ECMAScript 6 新增了 Proxy
Reflect
,让JavaScript也可以实现元编程。Proxy
代理类通过包含捕获器的 ProxyHandler
对象来拦截目标对象的基本操作和方法,来改变目标对象原本的逻辑。而 Reflect
通过其 API
可以操作JavaScript对象。

创建代理

Proxy
类提供了构造器来创建 Proxy
对象。

Proxy(target, handler)

其中的 target
参数为要拦截的目标对象,handler
参数为处理程序对象。当缺少任何一个参数都会抛出 TypeError

let target = {
    name"小王",
    age20,
    id2021
};

let proxy = new Proxy(target, {
    getfunction({
        return "ProxyHandler.get()";
    }
});
proxy.name    // ProxyHandler.get()
proxy.id      // ProxyHandler.get()

由上可知,handler
对象中定义了 get
捕获器来对目标对象中的读取操作进行捕获拦截,从而修改读取操作的行为。只有发生在代理对象上,才会触发捕获器,在目标对象上执行仍会产生正常行为。

如想要创建一个空代理,可以在创建 Proxy
对象时, handler
参数传入的值为 {}
即可。

let target = {};
let proxy = new Proxy(target, {});

捕获器

handler
中可以包含零个或多个捕获器,可直接或间接在代理对象上调用。handler
参数的类型为 ProxyHandler
类,除了上面介绍的 get()
方法外,还包含了其他的捕获器,如 getPrototypeOf()
has()
apply()
等 13 个捕获器。

has

has
捕获器会对 in
运算符劫持,用于判断某个属性是否存在。

let target = {
    name"小王",
    age20,
    id2021
};
let proxy = new Proxy(target, {
    has(target, p) {
        if (p.startsWith('a'))
            return false;
        return p in target;
    }
});
'name' in proxy    // true
'age' in proxy     // false
'id' in proxy      // true

捕获器参数

在使用捕获器时,可以访问相应的参数来重建被捕获方法的原始行为。如上面的例子中的 has()
捕获器会接收到目标对象、要查询的属性。可以通过调用封装了原始行为的 Reflect
对象上的方法来重建,几乎 ProxyHandler
中的捕获器在 Reflect
中,都能找到与之相对应的方法 。

let proxy = new Proxy(target, {
    has(target, p) {
        if (p.startsWith('a'))
            return false;
        return Reflect.has(...arguments);
    }
});

当然,如果创建的代理对象中,不对目标对象进行修改,但需要捕获所有方法,可以直接传 Reflect

let target = {
    name"小王",
    age20,
    id2021
};

let proxy = new Proxy(target, Reflect);

'age' in target    // true
'age' in proxy     // true

可撤销代理

使用 new Proxy()
来创建的代理,与目标对象之间的代理关系,会在其生命周期内一直存在。但有时也需要撤销代理对象与目标对象之间的联系,这时就可以使用 Proxy
提供的 revocable()
方法来创建一个支持可撤销的代理对象。

let target = {
    name: "小王"
};

const { proxy, revoke } = Proxy.revocable(target, {
    get() {
        return "小信";
    }
});
console.log(target.name); // 小王
console.log(proxy.name); // 小信
revoke();
console.log(target.name);  // 小王
console.log(proxy.name);  // TypeError: Cannot perform 'get' on a proxy that has been revoked

这里调用 revoke()
函数是幂等的,指的是无论调用多少次,其结果不变。当撤销后,在调用代理时,会抛出 TypeError
异常,如上所示。

反射 API

上面也介绍了 ProxyHandler
提供了 13 个方法来捕获目标对象中的操作,但若需要在捕获器中调用对象的默认行为,就可以使用 Reflect
提供的 API
,其提供的 API
基本都有捕获器相对应。下面就介绍 apply
defineProperty
两个方法。

apply

Reflect
中提供了 apply
方法通过指定的参数列表发起目标函数的调用,和 Function.prototype.apply()
功能类似。

Reflect.apply(Math.min, Math, args);  => Math.min.apply(Math, args);

Proxy
对象中的 ProxyHandler
使用 apply
捕获器来拦截函数调用时,可以使用 Reflect.apply()
来调用目标对象中函数的默认行为。

let target = function (a, b) {
    return a + b;
}

let proxy = new Proxy(target, {
    apply(target, thisArg, argArray) {
        return Reflect.apply(...arguments) * 2;
    }
});

target(12);    // 3
proxy.apply(null, [12]);    // 6

defineProperty

Reflect
提供的 defineProperty
方法与 Object.defineProperty()
基本相同,不同的是 Reflect
的方法返回的是 Boolean
值,该方法主要用于添加或修改对象上的属性。

let target = {
    name"小王",
    age20,
    id2021
};
target.address;    // undefined
Reflect.defineProperty(target, "address", {value"新疆"});
target.address;    // 新疆

总结

ECMAScript 6 新增的 Proxy
可以对对象提供了基本操作的拦截和改变原本的逻辑,从而丰富了开发的灵活性。而 Reflect
提供的 API
可以对绝大部分对象进行操作,如 Reflect.getPrototypeOf()
类似于 Object.getPrototypeOf()
。应用场景也非常大,如跟踪属性访问、隐藏属性、参数验证、数据绑定以及观察对象等。




文章转载自海人为记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论