ES6 Reflect 与 Proxy

200 阅读6分钟

概述

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

基本用法

Proxy

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get    打印getting name
proxy.age = 25 // 实际执行 handler.set    打印setting age
补充:target和handler都可以为空对象

实例方法

get(target, propKey, receiver)

用于 target 对象上 propKey 的读取操作。

let exam ={
    name: "Tom",
    age: 24
}
let proxy = new Proxy(exam, {
  get(target, propKey, receiver) {
    console.log('Getting ' + propKey);
    return target[propKey];
  }
})
proxy.name // Getting name
set(target, propKey, value, receiver)

用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。

let validator = {
    set: function(obj, prop, value) {
        console.log(`setting ${prop} =  ${value}`);
        obj[prop] = value;
    }
};
let proxy= new Proxy({}, validator)
proxy.age = 100; // setting age =  100

apply(target, ctx, args)

用于拦截函数的调用、call 和 apply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组

function sub(a, b){
    return a - b;
}
let handler = {
    apply: function(target, ctx, args){
        console.log('handle apply');
        return Reflect.apply(...arguments);
    }
}
let proxy = new Proxy(sub, handler)
proxy(2, 1) // handle apply
has(target, propKey)

用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。

let  handler = {
    has: function(target, propKey){
        console.log("handle has");
        return propKey in target;
    }
}
let exam = {name: "Tom"}
let proxy = new Proxy(exam, handler)
'name' in proxy // handle has
注意:此方法不拦截 for ... in 循环。
construct(target, args)

用于拦截 new 命令。返回值必须为对象。

let handler = {
    construct: function (target, args, newTarget) {
        console.log('handle construct')
        return Reflect.construct(target, args, newTarget)  
    }
}
class Exam { 
    constructor (name) {  
        this.name = name 
    }
}
let ExamProxy = new Proxy(Exam, handler)
let proxyObj = new ExamProxy('Tom') // handle construct
deleteProperty(target, propKey)

用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。

defineProperty(target, propKey, propDesc)

用于拦截 Object.defineProperty若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。

let handler = {
    defineProperty: function(target, propKey, propDesc){
        console.log("handle defineProperty");
        return true;
    }
}
let target = {}
let proxy = new Proxy(target, handler)
proxy.name = "Tom" // handle defineProperty

// defineProperty 返回值为false,添加属性操作无效
let handler1 = {
    defineProperty: function(target, propKey, propDesc){
        console.log("handle defineProperty");
        return false;
    }
}
let target1 = {}
let proxy1 = new Proxy(target1, handler1)
proxy1.name = "Jerry"
console.log(target1); // {}

prototype属性

getPrototypeOf(target)

主要用于拦截获取对象原型的操作。包括以下操作:

- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
let exam = {}
let proxy = new Proxy({},{
    getPrototypeOf: function(target){
        console.log(`handler getPrototypeOf`);
        return exam;
    }
})
Object.getPrototypeOf(proxy) // handler getPrototypeOf
isExtensible(target)

用于拦截 Object.isExtensible 操作。判断你对象是否可拓展
该方法只能返回布尔值,否则返回值会被自动转为布尔值。

let proxy = new Proxy({},{
    isExtensible:function(target){
        return true;
    }
});
console.log(Object.isExtensible(proxy)); // true
ownKeys(target)

用于拦截对象自身属性的读取操作。主要包括以下操作:

- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for...in
let proxy = new Proxy( {
  name: "Tom",
  age: 24
}, {
    ownKeys(target) {
        return ['name'];
    }
});
console.log(Object.keys(proxy)); // ['name']
preventExtensions(target)   把对象变成不可拓展的对象

拦截 Object.preventExtensions 操作。

该方法必须返回一个布尔值,否则会自动转为布尔值。

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    // 返回前先调用 Object.preventExtensions
    Object.preventExtensions(target);
    return true;
  }
});
Object.preventExtensions(proxy)
proxy.name = 'tom'
console.log(proxy); // Proxy {}
Proxy.revocable()

用于返回一个可取消的 Proxy 实例。

let {proxy, revoke} = Proxy.revocable({}, {});
proxy.name = "Tom";
revoke();
proxy.name // TypeError: Cannot perform 'get' on a proxy that has been revoked

Reflect

ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。

Reflect 对象对某些方法的返回结果进行了修改,使其更合理。

Reflect 对象使用函数的方式实现了 Object 的命令式操作。
静态方法

Reflect.get(target, name, receiver)

查找并返回 target 对象的 name 属性。

let exam = {
    name: "Tom",
    age: 24,
    get info(){
        return this.name + this.age;
    }
}
console.log(Reflect.get(exam, 'name')); // "Tom"
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
    name: "Jerry",
    age: 20
}
console.log(Reflect.get(exam, 'info', receiver));; // Jerry20
Reflect.set(target, name, value, receiver)

给对象设置属性;返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。

let exam = {
    name: "Tom",
    age: 24,
}
Reflect.set(exam, 'age', 25); // true
console.log(exam); // {name: 'Tom', age: 25}
Reflect.has(obj, name)

是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

let exam = {
    name: "Tom",
    age: 24
}
console.log(Reflect.has(exam, 'name'));; // true
console.log(Reflect.has(exam, 'job'));; // false
Reflect.deleteProperty(obj, property)

是 delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

let exam = {
    name: "Tom",
    age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
console.log(exam); // {age: 24}
Reflect.construct(obj, args)

等同于 new target(...args)。

function exam(name){
    this.name = name;
}
let newObj = Reflect.construct(exam, ['Tom']); 
console.log(newObj);// exam {name: "Tom"}
Reflect.getPrototypeOf(obj)

用于读取 obj 的 proto 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。

class Exam{}
let obj = new Exam()
console.log(Reflect.getPrototypeOf(obj) === Exam.prototype); // true
Reflect.setPrototypeOf(obj, newProto)

用于设置目标对象的 prototype。

let obj ={}
Reflect.setPrototypeOf(obj, Array.prototype);
Reflect.apply(func, thisArg, args)

等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。

console.log(Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1])); // 5
// 相当于
console.log(Math.max.apply(null, [1, 3, 5, 3, 1])); // 5
Reflect.defineProperty(target, propertyKey, attributes)

用于为目标对象定义属性。如果 target 不是对象,会抛出错误。

const student = {};
Reflect.defineProperty(student, "name", {value: "Mike"}); // true
console.log(student.name);; // "Mike"
Reflect.getOwnPropertyDescriptor(target, propertyKey)

用于得到 target 对象的 propertyKey 属性的描述对象。

var exam = {}
Reflect.defineProperty(exam, 'name', {
  value: true,
  enumerable: false,
})
console.log(Reflect.getOwnPropertyDescriptor(exam, 'name')); // {value: true, writable: false, enumerable: false, configurable: false}
Reflect.isExtensible(target)

用于判断 target 对象是否可扩展

let exam = {}
Reflect.isExtensible(exam) // true
Reflect.preventExtensions(target)

用于让 target 对象变为不可扩展

let exam = {}
Reflect.preventExtensions(exam) // true
Reflect.ownKeys(target)

用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。

var exam = {
  name: 1,
  [Symbol.for('age')]: 4
}
console.log(Reflect.ownKeys(exam)); // ["name", Symbol(age)]

组合使用

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

let exam = {
    name: "Tom",
    age: 24
}
let handler = {
    get: function(target, key){
        console.log("getting "+key);
        return Reflect.get(target,key);
    },
    set: function(target, key, value){
        console.log("setting "+key+" to "+value)
        Reflect.set(target, key, value);
    }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry" // setting name to Jerry
proxy.name // getting name