Proxy(代理)和reflection(反射) - 1

前言

/ES5和ES6致力与为开发者提供js已有却不可调用的功能。例如在ES5出现以前,JS环境的对象包含许多不可枚举和不可写的属性,但是 开发者不能定义自己的不可枚举或不可写的属性,
于是ES5引入了Object.defineProperty()方法来支持开发者去做引擎早就可以实现的事情,ES6添加了一些内建对象,赋予开发者更多访问JS引擎的能力。
代理(proxy)是一种可以拦截并改变底层JS引擎操作的包装器,在新语言中通过它暴漏的内部运作对象,从而让开发者可以创建内建的对象。

引入

【数组问题】

在ES6之前, 开发者不能通过自己定义对象模仿数组对象的行为。当给数组的特定元素赋值时,影响到该数组的length属性,也可以通过length修改数组元素

let colors = ["red", "green", "blue"];
console.log(colors.length); // 3
colors[3] = "black";
console.log(colors.length); // 4
console.log(colors[3]); // "black"
colors.length = 2;
console.log(colors.length); // 2
console.log(colors[3]); // undefined
console.log(colors[2]); // undefined
console.log(colors[1]); // "green"

colors数组一开始有3个元素,将colors[3]赋值为"black"时,length属性会自动增加到4,
将length属性设置为2时,会移除数组的后两个元素而只保留前两个。在ES5之前开发者无法自己实现这些行为,现在通过代理可以实现。
下表总结了代理陷阱的特性。
在这里插入图片描述
每个陷阱覆写JS对象的一些内建特性,可以用他们拦截并修改这些特性,如果仍然需要使用相应的内建特性,则可以使用相应的反射API方法。

创建 简单代理
用Proxy构造函数创建代理需要传入两个参数:目标(target)和处理程序(handle). 处理程序用于定义一个或者多个陷阱函数
带代理中,除了专门操作定义的陷井外,其余操作均使用默认特性,不适用任何陷阱的处理程序等价于简单的代理转发

let target = {};
let proxy = new Proxy(target, {});
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
target.name = "target";
console.log(proxy.name); // "target"
console.log(target.name); // "target"

这个示例中代理将所有的操作都转发到目标,将“proxy”赋值给proxy.name属性上时会在目标上创建name,代理只是简单的将操作转发给目标,
它不会存这个属性,由于proxy.name和target.name引用的都是target.name。 因此二者的值相同,从而为target.name设置新值后,proxy.name也一同变化。

代理陷阱

【使用set陷阱验证属性】
假设创建一个属性值是数字的对象,对象中没增加一个属性都要加以验证,如果不是数字必须抛出错误,为了实现这个任务,可以定义个set陷阱来覆写设置值的默认特性。

set陷阱接受4个参数

trapTarget 用于接受属性(代理的目标)的对象
key 要 写入的属性键(String or Symbol)
value 被写入属性的值
receiver 操作发生的对象(通常是代理) (可选)
let target = {
    name: "target"
};
let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {
        // 忽略已有属性,避免影响它们
        if (!trapTarget.hasOwnProperty(key)) {
            if (isNaN(value)) {
                throw new TypeError("Property must be a number.");
            }
        }
        // 添加属性
        return Reflect.set(trapTarget, key, value, receiver);
    }
});
// 添加一个新属性
proxy.count = 1;
console.log(proxy.count); // 1
console.log(target.count); // 1
// 你可以为 name 赋一个非数值类型的值,因为该属性已经存在
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
// 抛出错误
proxy.anotherName = "proxy";

set 代理陷阱可以拦截写入的属性。get陷阱可以拦截读取的属性操作
犹豫get陷阱不写入值 所以它复制了set陷阱中除value的其他3个参数, Reflect.get()也接受3个参数并返回属性默认的值,如果属性在目标上不存在,则
使用get陷阱和Reflect.get()时会抛出错误

let proxy = new Proxy({}, {
    get (trapTarget, key, receiver) {
        if (!(key in receiver)) {
            throw new TypeError(key + "  does not exist");
        }
        return Reflect.get(trapTarget, key, receiver);
    }
});
 
proxy.name = "arthur";
proxy.nickname // nickname  does not exist

此例中的get陷阱可以拦截属性的读取,并通过in操作符来判断recevier上是否具有被读取的属性,这里之所以用in操作符检查receiver而不是检查trapTarget,
是为了防止receiver代理含有has陷阱
从而得到错误的结果。属性如果不存在就会抛出一个错误,否则就使用默认行为。

使用has陷阱影藏已有属性

可用in操作符来检测给定的对象是否具有某个属性,如果自有属性或者圆形属性匹配到这个名称或者Symbol返回true;

let target = {
    value: 42
}
console.log("value" in target) // true
console.log("toString" in target) // true

// value是自有谁能够 toString 是继承Object的属性,二者再对象上都存在,用in 检测都返回
// 在代理中可以使用has陷阱可以拦截这些in操作并返回一个不同的值
// 每当使用in 操作符时都会调用has陷阱,并传入2个参数
// trapTarget 读取属性的对象
// key 要检查的属性


let target = {
    name: "target",
    value: 42
};
let proxy = new Proxy(target, {
    has(trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.has(trapTarget, key);
        }
    }
});
console.log("value" in proxy); // false
console.log("name" in proxy); // true
console.log("toString" in proxy); // true

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值