ES6 代理 Proxy 与 Reflect API

1,295 阅读3分钟

Proxy 与 Reflect

ES6 添加内置对象 Proxy 和 Reflect , 允许开发人员拦截并定义基本语言操作的自定义行为。

  • 代理 Proxy 可以拦截 JS 引擎内部目标的底层对象操作,然后触发特定操作的陷阱函数。
  • Reflect 对象方法的默认特性与相同底层操作一致。

Proxy 构造函数

    let proxy = new Proxy(target, handler) 

target 指代理的对象。handler 指处理程序(定义一个或多个陷阱的对象)。

Proxy 可用陷阱

代理陷阱 拦截行为 默认特性
has in 操作符 Reflect.has()
get 属性访问:proxy[foo]、proxy.bar
Reflect.get()
set 属性赋值:proxy[foo] = bar、proxy.foo = bar Reflect.set()
deleteProperty delete 操作符 Reflect.deleteProperty()
getPrototypeOf Object.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
Reflect.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf() Reflect.setPrototypeOf()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty Object.defineProperty() Reflect.defineProperty()
ownKeys Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Object.assign()
Reflect.ownKeys()
apply proxy(..args)
Function.prototype.apply()
Function.prototype.call()
Reflect.apply()
construct new proxy(...args) Reflect.construct()

简单使用

has 陷阱
let target = { 
    value1: "aaa",
    value2: "bbb"
};
let proxy = new Proxy(target, {
    has(target, key){
        if(key === "value2"){
            return false;
        }
        return Reflect.has(target, key);
    }
});
console.log("value1" in proxy);     //  true
console.log("value2" in proxy);     //  false

当使用 in 操作符都会调用 has 陷阱,并传入两个参数:target(代理目标),key(访问的属性键,字符串或 Symbol )。Reflect.has()也接受这些参数并返回 in 操作符默认响应。Reflect API接受的参数与相对应的陷阱参数相同。

get
let target = { value: "aaa" };
let proxy = new Proxy(target, {
    get(target, key, receiver){
        console.log(typeof key);
        if(key in receiver){    //  由于 in 操作符会触发代理的
            return Reflect.get(target, key, receiver);
        }
        throw new TypeError(key+"属性不存在!");
    }
});
console.log(proxy.value);   //  "aaa"
console.log(proxy.value1);  //  抛出错误
console.log(proxy[0]);
/*
    输出结果:"string", 抛出错误
    注意 get 陷阱 key 类型只能是字符串或 Symbol,所以对于数组的拦截操作记得转换 key 的类型
*/

get 陷阱拦截属性读取操作,传入3个参数:target(代理目标),key(读取的属性键,字符串或Symbol),receiver(操作的对象,一般指代理)。一般访问不存在的属性是 undefined,通过 get 陷阱抛出类型错误。

set
let target = { _value: "aaa" };
let proxy = new Proxy(target, {
    set(target, key, value, receiver){
        if(key[0]==="_"){
            return false;
        }
        return Reflect.set(target, key, value, receiver);
    }
});
proxy._value = "bbb";
console.log(proxy._value);   //  "aaa"

示例通过 set 陷阱使得不能对代理目标下划线开头的属性进行修改,通常下划线开头属性为私有属性。

deleteProperty
let proxy = new Proxy({ name: "target" }, {
    deleteProperty(target, key){
        if(key === "name"){
            return false;
        }
        return Reflect.deleteProperty(target, key);
    }
});
delete proxy.name; 
console.log(proxy.name);    //  "target"

拦截 delete 操作,传入参数:target(代理目标),key(删除的属性键,字符串或Symbol)。

原型代理陷阱

原型代理陷阱对返回值有限制:

  • getPrototypeOf 陷阱返回的必须是对象或 null,只要返回值必将导致运行错误。
  • setPrototypeOf 陷阱返回为 false,表示操作失败,Object.setPrototypeOf()会抛出错误,如果返回任何不是 false 的值,那么 Object.setPrototypeOf() 操作成功。
let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf (target) {
        return null;
    },
    setPrototypeOf (target, proto) {
        return false;
    }
});
console.log(Object.getPrototypeOf(proxy);   //  null
Object.setPrototypeOf(proxy, {});           //  抛出错误
construct
function fun () {};
let proxy = new Proxy(fun, {
    construct(target, argumentsList){
        argumentsList.forEach(value => {
            if(typeof value !== "number"){
                throw new TypeError("所有参数必须为数字!");
            }
        });
    }
});
let object = new proxy(1,2,"3");    //  抛出TypeError: 所有参数必须为数字!

通过 new 操作符调用类构造函数,就会触发 construct 陷阱,拦截操作并检查参数。

apply
function person (name) {
    this.name = name;
}

let proxy = new Proxy(person, {
    apply(target, thisArg, argumentsList){
        throw new TypeError("必须使用 new 来调用!");
    }
});

proxy("aaa");   //  抛出TypeError: 必须使用 new 来调用!

普通执行、Function.prototype.apply()、Function.prototype.call() 3个方式调用函数都会触发 apply 陷阱,并传入3个参数:target(代理目标),thisArg 函数被调用时内部this的值,argumentsList 执行函数的参数。

后续

简单认识下 Proxy、Reflect,在以后项目中使用到会第一时间记录下来。