ES6 Proxy 对象与 Reflect 对象

最后更新:
阅读次数:

Reflect 对象

Reflect 是一个 JavaScript 的内建对象,它为 JavaScript 的劫持操作提供了一些静态方法,这些方法的名字与 Proxy 对象定义的代理方法的名称一一对应。所以 Reflect 对象常与 Proxy 对象一起使用。

  • Reflect 不是一个函数对象,因此它是不可构造的
Object.prototype.toString.call(Reflect); // [object Object]
Object.prototype.toString.call(Math); // [object Math]

13 种静态方法

下面定义的静态方法与 JavaScript 一些很基础的操作具有相同的效果

  • Reflect.get(target, property[, receiver]): 相当于通过索引或点操作符从对象中获取某个属性的值
let obj = { name: "percy", age: 22 };

console.log(obj.name); // percy
console.log(obj["name"]); // percy
console.log(Reflect.get(obj, "name")); // percy
  • Reflect.set(target, property, value[, receiver]): 相当于在一个对象上设置一个属性
let obj = {};

obj.name = "percy";
Reflect.set(obj, "age", 33);

console.log(obj); // {name: "percy", age: 33}
  • Reflect.has(target, property): 该方法用于检查一个对象是否拥有某个属性,相当于 in 操作符
let obj = { name: "percy" };

console.log("name" in obj); // true
console.log(Reflect.has(obj, "name")); // true
console.log(Reflect.has(obj, "age")); // false
  • Reflect.deleteProperty(target, property): 用于删除属性,相当于 delete 操作符
let obj = {
name: "percy",
age: 23
};

let result = Reflect.deleteProperty(obj, "name");

console.log(result); // true
console.log(obj); // {age: 23}
  • Reflect.defineProperty(target, property, attributes): 基本等同于 Object.defineProperty() 方法,唯一不同是该方法的返回值是 Boolean 值
let obj = {};

let result = Reflect.defineProperty(obj, "name", {
value: "percy"
});

console.log(result); // true
console.log(obj); // {name: "percy"}
  • Reflect.getOwnPropertyDescriptor(target, property):Object.getOwnPropertyDescriptor() 方法相似,返回给定的属性的属性描述符
let arr = [1, 2, 3];
let result = Reflect.getOwnPropertyDescriptor(arr, "length");

console.log(result);
// {value: 3, writable: true, enumerable: false, configurable: false}
  • Reflect.getPrototypeOf(target):Object.getPrototypeOf() 方法,都是返回指定对象的原型对象
let Person = function() {};
let obj = new Person();

console.log(Reflect.getPrototypeOf(obj) === Person.prototype); // true
  • Reflect.setPrototypeOf(target, prototype): 同 Object.setPrototypeOf() 方法,都是将指定对象的原型对象 (即,内部的[[Prototype]] 属性)设置为另一个对象或为 null
let arr = [];

console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

Reflect.setPrototypeOf(arr, null);

console.log(Object.getPrototypeOf(arr) === Array.prototype); // false

arr.push(1); // Uncaught TypeError: arr.push is not a function
  • Reflect.isExtensible(target): 同 Object.isExtensible() 方法相似,都可以判断一个对象是否可扩展 (即是否能够添加新的属性)
    • 区别:如果该方法的第一个参数不是一个对象(原始值),那么将造成一个 TypeError 异常。对于 Object.isExtensible(),非对象的第一个参数会被强制转换为一个对象
let obj1 = {};
let obj2 = {};

console.log(Reflect.isExtensible(obj1)); // true
console.log(Reflect.isExtensible(obj2)); // true

Object.seal(obj1);
Object.freeze(obj2);

console.log(Reflect.isExtensible(obj1)); // false
console.log(Reflect.isExtensible(obj2)); // false
  • Reflect.preventExtensions(target): 同 Object.preventExtensions() 相似,都是让一个对象变的不可扩展,也就是永远不能再添加新的属性
    • 区别:如果该方法的第一个参数不是一个对象(原始值),那么将造成一个 TypeError 异常。 对于 Object.preventExtensions() 方法, 非对象的第一个参数将被强制转换为对象
let obj = {};

console.log(Object.isExtensible(obj)); // true

Reflect.preventExtensions(obj);

console.log(Object.isExtensible(obj)); // false
  • Reflect.ownKeys(target): 返回一个由目标对象自身的属性键组成的数组
    • 它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
let obj = {
name: "percy",
age: "23"
};

console.log(Reflect.ownKeys(obj)); // ["name", "age"]

obj[Symbol("phone")] = 17790901122;

console.log(Reflect.ownKeys(obj)); // ["name", "age", Symbol(phone)]
  • Reflect.apply(target, thisArg, argumentsList): 该方法通过指定的参数列表发起对目标(target)函数的调用,与函数的 apply() 方法类似
let sumFunc = function(...args) {
return args.reduce((a, b) => a + b);
};

sumFunc.apply(null, [1, 2, 3]); // 6
Reflect.apply(sumFunc, null, [1, 2, 3, 44]); // 50
  • Reflect.construct(target, argumentsList[, newTarget]): 相当于 new target(...args)
let date = Reflect.construct(Date, [2018, 03, 01]);

console.log(date instanceof Date); // true
console.log(date.toString()); // Sun Apr 01 2018 00:00:00 GMT+0800 (CST)

Proxy 对象

Proxy,中文是代理的意思,它为目标对象提供了一种拦截机制,即在目标对象之前架设一层 拦截,外界对该对象的访问,都必须先通过这层拦截。

拦截的本质是:Proxy 重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

  • 使用 Proxy 构造函数来实现对目标对象的代理
    • new Proxy(target, handler): 一个代理对象实例
      • target: 目标对象,可以是任意类型的对象,比如数组,函数,甚至可以是代理对象
      • handler: 处理器对象,包含了一组代理方法,分别控制所生成代理对象的各种行为
Object.prototype.toString.call(Proxy); // [object Function]
Object.prototype.toString.call(new Proxy({}, {})); // [object Object]

深坑: 通过 new Proxy() 生成的代理对象才是 handler 对象里定义的行为作用的目标,而不是原对象???对不对

13 种代理方法

在代理中,除了专门为某些操作定义的陷阱外,其余操作均使用默认特性。

在下面所有的代理方法中,只有 apply 和 construct 的代理目标是一个函数

  • get: 用于拦截读取对象属性值的操作
let targetObj = {
name: "percy",
age: 21
};

let handler = {
get(target, property, receiver) {
// target 目标对象
// property 被获取的属性名
// receiver 一个 Proxy 对象或者继承 Proxy 的对象
// return 任意值

if (property === "name") {
return "percy666666";
} else {
return Reflect.get(target, property, receiver);
}
}
};

let p = new Proxy(targetObj, handler);

console.log(p.name); // percy666666
console.log(p.age); // 21
console.log(p); // Proxy {name: "percy", age: 21}
console.log(targetObj); // {name: "percy", age: 21}
  • set: 用于拦截写入属性的操作
let targetObj = {};

let handler = {
set(target, property, value, receiver) {
// target 目标对象
// property 被设置的属性名
// value 被设置的新值
// receiver 最初被调用的对象。通常是 Proxy 本身

if (property === "name") {
value = "Custom Name";
}

return Reflect.set(target, property, value, receiver);
}
};

let p = new Proxy(targetObj, handler);

p.name = "percy";
p.age = 23;

console.log(p); // Proxy {name: "Custom Name", age: 23}
console.log(targetObj); // { name: 'Custom Name', age: 23 }
  • has: 用于拦截 in 操作符
let targetObj = {
name: "percy",
age: 23
};

let handler = {
has(target, property) {
// target 目标对象
// property 需要检查是否存在的属性
// return 一个布尔值

if (property === "name") {
return false;
} else {
return Reflect.get(target, property);
}
}
};

let p = new Proxy(targetObj, handler);

console.log("name" in p); // false
console.log("age" in p); // true
  • deleteProperty: 用于拦截 delete 操作符
let targetObj = {
name: "percy",
age: 22
};

let handler = {
deleteProperty: function(target, property) {
// target: 目标对象
// property: 即将被删除的属性名

// 必须 return 一个布尔值来暗示属性是否删除成功
if (property === "name") {
return false;
} else {
return Reflect.deleteProperty(target, property);
}
}
};

let p = new Proxy(targetObj, handler);

delete p.name;
delete p.age;

console.log(p); // Proxy {name: "percy"}
console.log(targetObj); // {name: "percy"}
  • defineProperty: 拦截对象的 Object.defineProperty() 操作
let targetObj = {
name: "percy",
age: 23
};

let handler = {
defineProperty(target, property, descriptor) {
// target 目标对象
// property 属性名
// descriptor 待定义或修改的属性描述符的对象
// return 一个布尔值

descriptor = Object.assign({ value: "percy66666" }, descriptor);

return Reflect.defineProperty(target, property, descriptor);
}
};

let p = new Proxy(targetObj, handler);

console.log(Object.getOwnPropertyDescriptor(p, "name"));
// {value: "percy", writable: true, enumerable: true, configurable: true}

Object.defineProperty(p, "name", { writable: false });

console.log(Object.getOwnPropertyDescriptor(p, "name"));
// {value: "percy66666", writable: false, enumerable: true, configurable: true}
  • getOwnPropertyDescriptor: 拦截 Object.getOwnPropertyDescriptor() 操作
let targetObj = {
name: "percy",
age: 23
};

let handler = {
getOwnPropertyDescriptor(target, property) {
// target 目标对象
// property 属性名
// return 一个对象或 undefined

if (property === "name") {
return { value: "fuck", configurable: true };
} else {
return Reflect.getOwnPropertyDescriptor(target, property);
}
}
};

let p = new Proxy(targetObj, handler);

console.log(Object.getOwnPropertyDescriptor(p, "name"));
// {value: "fuck", writable: false, enumerable: false, configurable: true}

console.log(Object.getOwnPropertyDescriptor(p, "age"));
// {value: 23, writable: true, enumerable: true, configurable: true}
  • getPrototypeOf: 拦截对象的 Object.getPrototypeOf() 操作
let obj = {};
let proto = {};
let handler = {
getPrototypeOf(target) {
console.log(target === obj); // true
console.log(this === handler); // true

return proto;
}
};

let p = new Proxy(obj, handler);

console.log(Object.getPrototypeOf(p) === proto); // true
  • setPrototypeOf: 拦截对象的 Object.setPrototypeOf() 操作
let obj = {};
let proto = {};
let handler = {
setPrototypeOf(target, prototype) {
console.log(target === obj); // true
console.log(prototype === proto);
console.log(this === handler); // true

return {};
}
};

let p = new Proxy(obj, handler);

Object.setPrototypeOf(p, proto);

console.log(Object.getPrototypeOf(p) === proto); // false
  • isExtensible: 拦截对象的 Object.isExtensible() 操作
let obj = {};
let handler = {
isExtensible(target) {
console.log(target === obj); // true

return false;
}
};

let p = new Proxy(obj, handler);

Object.isExtensible(p);
// Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
  • preventExtensions: 拦截对象的 Object.preventExtensions() 操作
let obj = {};
let handler = {
preventExtensions(target) {
console.log(target === obj); // true

return false;
}
};

let p = new Proxy(obj, handler);

Object.preventExtensions(p);
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned falsish
  • ownKeys: 拦截对象自身属性的读取操作
let obj = {
name: "percy",
age: 23
};

let handler = {
ownKeys(target) {
return [];
}
};

let p = new Proxy(obj, handler);

console.log(Object.keys(obj)); // ["name", "age"]
console.log(Object.keys(p)); // []
  • apply: 用于拦截函数的调用
let sumFunc = function(...args) {
return args.reduce((a, b) => a + b);
};

let handler = {
apply(target, thisArg, argumentsList) {
// target 目标对象
// thisArg 函数被调用时的上下文对象
// argumentsList 函数被调用时的参数数组
// return 任意值

if (argumentsList.length < 3) {
return "参数至少为3个";
} else {
return Reflect.apply(target, thisArg, argumentsList);
}
}
};

let p = new Proxy(sumFunc, handler);

console.log(p(11, 22)); // 参数至少为3个
console.log(p(11, 22, 33)); // 66
console.log(p.apply(null, [11, 22])); // 参数至少为3个
console.log(p.apply(null, [11, 22, 33])); // 66
console.log(p.call(null, 11, 22)); // 参数至少为3个
console.log(p.call(null, 11, 22, 33)); // 66

console.log(sumFunc(11, 22)); // 33
console.log(sumFunc.apply(null, [11, 22])); // 33
  • construct: 用于拦截 new 操作符
let func = function() {};
let handler = {
construct: function(target, argumentsList, newTarget) {
// target 目标对象
// argumentsList 构造函数的参数列表
// newTarget ...

// 必须 return 一个对象,否则会报错
return {
name: "percy"
};
}
};

let p = new Proxy(func, handler);

let p1 = new p();

console.log(p1); // {name: "percy"}

可撤销代理

通常,在创建代理后,代理不能脱离其目标对象,即这些代理为不可撤销代理。但是如果我们出于某种目的需要撤销对目标对象的代理,这时我们就可以使用 Proxy.revocable() 方法来创建一个可撤销代理。

  • Proxy.revocable() 方法在被调用后会返回一个对象,这个对象有两个属性
    • proxy 可被撤销的代理对象
    • revoke 撤销代理要调用的函数
let obj = {
name: "percy",
age: 23
};

let handler = {
ownKeys(target) {
return [];
}
};

let revocableProxy = Proxy.revocable(obj, handler);

console.log(Object.keys(revocableProxy.proxy));
// []

// 撤销代理
revocableProxy.revoke();

console.log(Object.keys(revocableProxy.proxy));
// Uncaught TypeError: Cannot perform 'ownKeys' on a proxy that has been revoked

参考资料