编程技术文章分享与教程

网站首页 > 技术文章 正文

快速了解ES6的代理与反射

hmc789 2024-11-21 15:55:11 技术文章 1 ℃

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

创建代理

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

Proxy(target, handler)

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

let target = {
    name: "小王",
    age: 20,
    id: 2021
};

let proxy = new Proxy(target, {
    get: function() {
        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: "小王",
    age: 20,
    id: 2021
};
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: "小王",
    age: 20,
    id: 2021
};

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 基本都有捕获器相对应。下面就介绍 applydefineProperty 两个方法。

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(1, 2);    // 3
proxy.apply(null, [1, 2]);    // 6

defineProperty

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

let target = {
    name: "小王",
    age: 20,
    id: 2021
};
target.address;    // undefined
Reflect.defineProperty(target, "address", {value: "新疆"});
target.address;    // 新疆

总结

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

标签列表
最新留言