【JavaScript】Proxy 代理 & Reflect 反射

Proxy 代理

  • Proxy 用于修改某些操作的默认行为,属于一种 “元编程”,即对编程语言进行编程

  • 通俗地讲,就是通过 Proxy 代理扩展了对象、方法的一些功能

  • ES6 提供 Proxy 构造函数,用来生成 Proxy 实例

let proxyObj = new Proxy(target, handle)

接收 2 个参数:
target:代理对象(目标对象)
handler:配置对象(拦截对象),用来定制拦截行为
如果handler没有设置任何拦截,那就相当于直接操作原对象

let target = {}; // 目标对象
let handler = {}; // 拦截对象
let proxy = new Proxy(target, handler);
proxy.name = 'superman';
console.log("proxy - ", proxy); // proxy -  Proxy {name: 'superman'}
console.log("target - ", target); // target -  {name: 'superman'}
  • 上面代码中,handler 是一个空对象,没有任何拦截效果,∴ 访问 proxy 就等同于访问 target

拦截 Object

get(target, property[, receiver]) - 用于拦截对属性的 [获取] 操作

接收 3 个参数:
target:代理对象(目标对象),就是构造函数 Proxy() 的第 1 个参数
property:访问的属性名
receiver:Proxy 对象 / 继承 Proxy 的对象

  • 需要手动 return 返回值:为 [获取] 操作得到的值; 可以返回任何值
let obj = { name: 'superman' };
let proxyObj = new Proxy(obj, { // 设置拦截对象
    get(target, property) { // getter
        if (property in target) {
            return target[property];
        } else {
            return `你访问的属性 ${property} 不存在!`;
        }
    }
});

console.log(proxyObj); // Proxy {name: 'superman'}
console.log(proxyObj.name); // superman  ——  (通过 . 获取)
console.log(proxyObj["age"]); // 你访问的属性 age 不存在!  ——  (通过 [] 获取)

上述方法中,getter 的参数 target 就是代理对象 obj;参数 property 就是访问的属性名 nameage

set(target, property, value[, receiver]) - 用于拦截对属性的 [添加]、[修改] 操作

接受 4 个参数:
target:目标对象,就是构造函数 Proxy() 的第 1 个参数
property:操作的属性
value:被写入的属性值
receiver:Proxy 对象 / 继承 Proxy 的对象

  • 应当手动 return 返回值:true-执行成功、false-执行失败
    [应当] 的意思是,可以不写
let obj = { name: "superwoman" }
let proxyObj = new Proxy(obj, {
    set(target, property, value) {
        if (property == 'age') {
            if (!Number.isInteger(value))
                throw new TypeError(`${property} 必须是整数`);
            if (value > 200)
                throw new RangeError(`${property} 得小于 200`);
        }
        target[property] = value;
    }
});

proxyObj.name = 'superman'; // 修改属性值
proxyObj.age = 18; // 添加属性
console.log(obj); // { name: 'superman', age: 18 }

// 以下会报错
// proxyObj.age = 12.6; // TypeError: age 必须是整数
// proxyObj.age = 201; // RangeError: age 得小于 200
deleteProperty(target, property) - 用于拦截对属性的 [删除] 操作

接受 2 个参数:
target:目标对象,就是构造函数 Proxy() 的第 1 个参数
property:待删除的属性名

  • 需要手动 return 返回值:true 表示删除成功;false 表示删除失败
let obj = { name: 'superman', age: 18 };
let proxyObj = new Proxy(obj, {
    deleteProperty(target, prop) {
        console.log(`你要删除 ${prop} 属性`); // 你要删除 age 属性
        return delete target[prop];
    }
});

console.log(obj); // { name: 'superman', age: 18 }
console.log(delete proxyObj.age); // false
console.log(obj); // { name: 'superman' }

上述代码中,deleteProperty 方法的参数 target 就是 Proxy() 的第 1 个参数 obj;参数 prop 就是 age

has(target, prop) 方法 - 用于拦截对属性的 [in] 操作

接受 2 个参数:
target:目标对象
prop:需要检查是否存在的属性

  • 需要手动 return 返回值:true-目标对象存在该属性;false-目标对象不存在该属性
let obj = { name: 'superman', age: 18 };
let proxyObj = new Proxy(obj, {
    has(target, prop) {
        console.log(`判断属性 ${prop} 是否在此对象中`); // 判断属性 name 是否在此对象中
        return prop in target;
    }
});
console.log('name' in proxyObj); // true

拦截 Function

  • 所有的代理拦截中,只有 applyconstruct 的代理目标是一个函数 Function
  • applyconstruct 拦截方法覆写函数内部的 [[Call]][[Construct]] 方法
apply(target, thisArg, argumentsList)- 用于拦截 [直接调用] - 作用于普通函数

接受 3 个参数:
target:目标函数,就是构造函数 Proxy() 的第 1 个参数
thisArg:被调用时的上下文对象 ( this )
argumentsList:被调用时的参数数组

  • 返回值:可以返回任何值
function sum(a, b) { return a + b }

let handler = { // 设置拦截对象
    apply(target, thisArg, argumentsList) { // 设置拦截对象的 apply 方法
        console.log(`Calculate sum: ${argumentsList}`); // Calculate sum: 1,2
        return target(argumentsList[0], argumentsList[1]) * 10;
    }
};
let proxyObj = new Proxy(sum, handler);

console.log(proxyObj(1, 2)); // 30
construct(target, argumentsList[, newTarget]) - 用于拦截 [new 操作] - 作用于构造函数
  • 为使 new 操作符在生成的 Proxy 对象上生效,目标函数必须具有 [[Construct]] 方法(即 new target() 必须是有效的)

接受 3 个参数:
target:目标函数,就是 Proxy() 的第 1 个参数
argumentsList:被调用时的参数数组
newTarget:最初被调用的构造函数

  • 返回值:一个实例对象
function People(name) { this.name = name };

let handler = { // 设置拦截对象
    construct(target, args) { // 设置拦截对象的 construct 方法
        console.log('拦截了 new 操作'); // 拦截了 new 操作
        return new target(...args);
    }
};
let ProxyPeople = new Proxy(People, handler);

let p = new ProxyPeople('superman');
console.log(p); // People { name: 'superman' }

可撤销代理对象

  • 通过 new Proxy(target, handle) 创建的代理对象不可撤销
  • 创建可撤销的代理对象的方式:Proxy.revocable(target, handler);
  • 接收 2 个参数:
    target:目标对象
    handler:拦截对象
  • 返回值:{"proxy": proxy, "revoke": revoke}
    proxy - 表示新创建的代理对象,和通过 new Proxy(target, handler) 创建的代理对象一样,不过它可以被撤销掉
    revoke - 撤销方法,直接调用即可撤销掉和它一起生成的那个代理对象
let obj = { name: 'superman' };

let handler = {};
let { proxy, revoke } = Proxy.revocable(obj, handler);

console.log(proxy.name); // superman
revoke(); // 撤销代理
console.log(proxy.name); // 报错

Reflect 反射

  • Reflect 是一个内置的对象,提供拦截 JS 操作的方法
Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值
let obj = {}

let res1 = Reflect.defineProperty(obj, "name", {
    get() {
        console.log("getter1"); // getter1
        return "superman";
    }
})
console.log("res1", res1); // res1 true —— 执行成功

let res2 = Reflect.defineProperty(obj, "name", {
    get() {
        console.log("getter2");
        return "superwoman";
    }
})
console.log("res2", res2); // res2 false —— 执行失败

console.log("obj.name", obj.name); // obj.name superman

上例的 Reflect.defineProperty() 如果改为 Object.defineProperty(),第 2 次调用将会直接抛出错误,代码不会继续往下执行
执行 Reflect.defineProperty() ,会返回一个布尔值:true-执行成功、false-执行失败
使用 Reflect.defineProperty() 则可通过判断其返回值是否为 ture,得知操作是否执行成功,进而确定下一步操作

反射 Object

  1. Reflect.get(target, property[, receiver]) - [获取] 属性
    手动 return 返回值:属性的值
  2. Reflect.set(target, property, value[, receiver]) - [添加]、[修改] 属性
    手动 return 返回值:true-操作成功、false-操作失败
  3. Reflect.deleteProperty(target, property) - [删除] 属性
    手动 return 返回值:true-操作成功、false-操作失败
new Proxy(data, {
    // 拦截 [获取] 属性
    get(target, prop) {
        return Reflect.get(target, prop)
    },
    // 拦截 [添加]、[修改] 属性
    set(target, prop, value) {
        Reflect.set(target, prop, value)
    },
    // 拦截 [删除] 属性
    deleteProperty(target, prop) {
        return Reflect.deleteProperty(target, prop)
    }
})

反射 Function

  1. Reflect.apply(target, thisArg, argumentsList)- 相当于 target(...argumentsList)
    可以返回任意值
  2. Reflect.construct(target, argumentsList[, newTarget]) - 相当于 new target(...argumentsList)
    返回一个实例对象
console.log(Math.ceil(4.4)); // 向上取整 5

// 通过 [反射] 调用 Math.ceil;  没有 this 指向,传入 null;  参数数组
let num = Reflect.apply(Math.ceil, null, [5.1]);
console.log(num); // 6

其实,只是调用函数的不同的方式而已:

function show(...args) {
    console.log(this);
    console.log(args);
}
// 正常调用 
show(1, 2, 3, 4); // this 是 window,args 是 [1,2,3,4]
// call 调用函数
show.call('aaa', 1, 2, 3, 4); // this 是 aaa,args 是 [1,2,3,4]
// apply 调用函数
show.apply('aaa', [1, 2, 3, 4]); // this 是 aaa,args 是 [1,2,3,4]
// reflect.apply 调用函数
Reflect.apply(show, 'aaa', [1, 2, 3, 4]); // this 是 aaa,args 是 [1,2,3,4]
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值