Proxy
Es6为了捕获对对象的一些操作而提供的Api, proxy比defineProperties更加强大,不仅可以监听对象的读写操作还能监听 删除 , new关键字 , in关键字 等。所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。详细见MDN
get
get(target, property, receiver)
当获取被劫持的对象中的属性时就会触发该函数,该函数使用后 必须得有一个返回值 ,这个返回值将会作为你访问属性时获取到的值
参数
target—— 是目标对象,该对象作为第一个参数传递给new Proxyproperty—— 目标属性名receiver—— 如果目标属性是一个 getter 访问器属性,则receiver就是本次读取属性所在的this对象。通常,这就是proxy对象本身(或者,如果我们从代理继承,则是从该代理继承的对象)。
let numbers = [0, 1, 2];
// 代理应该在所有地方都完全替代了目标对象。目标对象被代理后,任何人都不应该再引用目标对象。否则很容易搞砸。
numbers = new Proxy(numbers, {
get(target, property, receiver) {
if (property in target) {
return target[property];
} else {
return 0; // 默认值
}
}
});
alert(numbers[1]); // 1
alert(numbers[123]); // 0 (没有这样的元素)
set
set(target, property, value, receiver)
当设置被劫持的对象中的属性时就会触发该函数,该函数使用后默认返回false(返回false控制台会出现报错)设置成功必须返回true, 返回的布尔值只能控制是否抛出异常,是否更改取决于set函数中的逻辑
参数
target—— 是目标对象 该对象作为第一个参数传递给 new Proxyproperty—— 目标属性名称value—— 目标属性要设置的值receiver—— 与get钩子类似,仅与 setter 访问器相关。
let numbers = [];
numbers = new Proxy(numbers, { // (*)
set(target, prop, val) { // 拦截写入操作
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
numbers.push(1); // 添加成功
numbers.push(2); // 添加成功
alert("Length is: " + numbers.length); // 2
// 不能给代理对象没有的属性赋值
numbers.push("test"); // TypeError (proxy 的 `set` 操作返回 false)
注意:在通过set修改成功后必须返回
true如果没有返回则会触发typeError
ownKeys
ownKeys(target)
当使用 Object.keys , Object.values , for..in 和大多数遍历属性的方法都使用的是 ownKeys 钩子拦截。ownKeys只返回 enumerable:true 的非Symbol属性
参数
target—— 是目标对象 该对象作为第一个参数传递给 new Proxy
// 以下完成了一个在遍历对象的时候过滤掉私有属性
let user = {
name: "lucy",
age: 18,
_password: "***"
};
user = new Proxy(user, {
ownKeys(target) {
// 返回一个数组,遍历时的每一项就是该数组中的可枚举(enumerable)成员
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
// "ownKeys" 过滤掉 _password
for (let key in user) alert(key); // name,然后是 age
// 对这些方法同样有效:
alert(Object.keys(user)); // name,age
alert(Object.values(user)); // lucy,18
getOwnPropertyDescriptor
getOwnPropertyDescriptor(target, property)
循环遍历对象属性的时候触发,如: Object.values , for..in ,之前说过ownKeys会拦截大多数遍历对象属性的循环,而且ownKeys只能返回可枚举的非Symbol属性,怎么知道当前遍历的属性 是否是可枚举的属性 则通过 getOwnPropertyDescriptor 方法返回的配置信息得知
参数
target—— 是目标对象, 该对象作为第一个参数传递给 new Proxyproperty—— 目标属性名称
let user = {};
user = new Proxy(user, {
ownKeys(target) {
// 监听对象本没有任何属性,通过手动返回属性列表
// 但是这些属性都还是不可枚举的属性
return ['a', 'b', 'c'];
},
// 每有一个属性被遍历就调用改方法
getOwnPropertyDescriptor(target, property) {
return {
enumerable: true, //是否可枚举,是否可以遍历属性
configurable: true, //是否可配置,是否可以删除属性
writable: true //是否可写,是否可以设置修改属性
};
}
});
console.log(Object.keys(user)); // a, b, c
deleteProperty
deleteProperty(target, property)
删除(delete)劫持对象属性的时候触发 deleteProperty 方法. 该方法和 set 方法一样必须返回一个 布尔值 , 如果返回的布尔值是false则会抛出异常但具体是否删除取决于 deleteProperty 方法中的逻辑
参数
target—— 是目标对象, 该对象作为第一个参数传递给 new Proxyproperty—— 目标属性名称
// 该例子控制在劫持对象中不可以删除私有属性
const character = Symbol("character");
let person = {
name: "limao",
age: 18,
[character]: "暴躁",
_type: "人",
sayHello() {
console.log(`hello!我叫${this.name},今年${this.age}`);
}
}
person = new Proxy(person, {
deleteProperty(target, prop) {
if (typeof prop == "string" && prop.startsWith("_")) return false;
delete target[prop]
return false;
}
})
console.log(delete person._type); //抛出异常
console.log(delete person.age); //true
has
has(target, property)
当对劫持对象的属性使用 in 运算符(判断当前属性是否是目标对象的中的属性)的时候触发 has 方法
参数
target—— 是目标对象, 该对象作为第一个参数传递给 new Proxyproperty—— 目标属性名称
let range = {
start: 1,
end: 10
};
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end
}
});
alert(5 in range); // true
alert(50 in range); // false
apply
apply(target, thisArg, args)
apply 方法拦截函数的调用、以及call和apply操作, 不能作用于对象, 数组
参数
target是目标对象(函数)thisArg是 this 的值args是参数列表
// 使用apply拦截方法执行并做出处理
let double = {
apply(target, ctx, args) {
return target.apply(ctx, args) * 2
}
};
function sum(left, right) {
return left + right;
};
let proxy = new Proxy(sum, double);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
console.log(proxy.name); //sum
console.log(proxy.length); // 2
// 使用工具方法进行处理
function pack(f) {
return function() {
f.apply(null, arguments)
}
}
const packSum = pack(sum);
console.log(packSum(5, 6)); // 22
console.log(packSum.name); // ""
console.log(packSum.length); // 0
使用工具方法包装后的方法 packSum 无法获取到原函数的 name , length , proxy功能强大的多, 他将原函数所有的东西都转发到了目标对象
Reflect
Reflect是什么
Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能, 由于它类似于其他语言的反射,因此取名为Reflect
官方说Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy的捕捉器 方法相同。Reflect不是一个函数对象,因此它是不可构造的。详细见MDN
Reflect的设计目的
- 将操作ojbect对象的一些魔法(偏底层,内部的方法),以API的形式提供出来。
- 修改某些Object方法的返回结果,让其变得更合理。比如,
Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。 - 让
Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。javascript具备同时支持面向对象和函数式编程的能力
它里面到底提供了哪些API呢?
- Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value,等同于给对象的属性赋值
- Reflect.get(target, propertyKey): 读取对象target的属性propertyKey,等同于读取对象的属性值
- Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this和参数列表。等同于函数调用
- Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
- Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty,不同的是如果配置出现问题,返回false而不是报错
- Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
关于为什么要使用Reflect
让我们看一个示例。当我们使用proxy代理一个getter的时候,可以使用 target[propetry] 也可以使用 Reflect.get , 以下例子解释了为什么要使用 Reflect.get .
// 父级对象
let father = {
_name: "老李",
get name() {
return this._name
}
}
let fatherProxy = new Proxy(father, {
get(target, property, receiver) {
return target[property];
}
})
// 子级对象,隐式原型为father
let son = {
_name: "小李",
__proto__: fatherProxy
}
console.log(son.name); // 老李
访问子对象上 name 属性时,由于子对象上没有 name 属性就回去原型 father 上找
father 上有 name 属性访问触发 proxy 拦截方法 get , get 返回 target[property]
target 在这里是 proxy 监听的目标对象为 father , name 在 father 中为getter
这里触发getter由于是 father 调用所以getter函数中的 this 为 father
最后返回的值为 father 的 _name
但是我们实际这里获取的应该是子级的_name, 通过 son.name , name为getter,getter中的this指向应该是son
let father = {
_name: "老李",
get name() {
return this._name
}
}
let fatherProxy = new Proxy(father, {
get(target, property, receiver) {
return Reflect.get(target, property, receiver);
}
})
let son = {
_name: "小李",
__proto__: fatherProxy
}
console.log(son.name); // 小李
我们使用 Reflect.get 方法返回 name 在触发getter的时候会去使用 Reflect.get 的第三个参数 receiver , receiver 传递了正确的 this 给getter.
最后getter中使用的上下文是否就是 receiver ,通过下面的例子可以证明
let father = {
_name: "老李",
get name() {
return this._name
}
}
let mother = {
_name: "老谢",
get name() {
return this._name
}
}
let fatherProxy = new Proxy(father, {
get(target, property, receiver) {
return Reflect.get(target, property, mother);
}
})
let son = {
_name: "小李",
__proto__: fatherProxy
}
console.log(son.name); // 老谢