目录
1.get(target, propKey, receiver)
2、set(target, propKey, value, receiver)
5、construct(target, args, newTarget)
6、deleteProperty(target, propKey)
7、defineProperty(target, propKey, descriptor)
8、getOwnPropertyDescriptor(target, propKey)
13、 setPrototypeOf(target, proto)
1、Reflect.get(target, name, receiver)
2、Reflect.set(target, name, value, receiver)
4、Reflect.deleteProperty(obj, name)
5、Reflect.construct(target, args)
6、 Reflect.getPrototypeOf(obj)
7、Reflect.setPrototypeOf(obj, newProto)
8、Reflect.apply(func, thisArg, args)
9、Reflect.defineProperty(target, propertyKey, attributes)
10、Reflect.getOwnPropertyDescriptor(target, propKey)
11、Reflect.isExtensible(target)
12、Reflect.preventExtensions(target)
Proxy
概述
Proxy是 ES6 为了操作对象引入的 API ,Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
proxy这个词的意思是"代理",在这里表示由它来"代理"一些操作,可以译为"代理器"。
基本用法
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
let target = { name: "Tom", age: 2 };
let handler = {
get: (target, key) => {
console.log("getting %s!", key);
return target[key];
},
set: (target, key, value) => {
console.log("setting %s 的值为 %s!", key, value);
target[key] = value;
},
};
let proxy = new Proxy(target, handler);
proxy.name; // getting name! 实际执行 handler.get 方法
proxy.name = "Jerry"; // setting name 的值为 Jerry! 实际执行 handler.set 方法
请看上面代码,target参数就是要拦截的目标对象,handler参数就是拦截对应的操作。
handler可以设置为空对象,相当于
没有设置任何拦截,等同于直接访问原对象。
let proxy = new Proxy({ name: "Tom", age: 2 }, {});
console.log(proxy.name); // Tom
Proxy 实例方法
1.get(target, propKey, receiver)
用于拦截目标的读取操作,接受三个参数,target表示目标对象、propKey表示属性名、receiver表示proxy实例本身。第三个参数非必须。
let proxy = new Proxy(
{ name: "Tom", age: 2 },
{
get: (target, propKey) => {
// 拦截操作
console.log("正在读取 %s 属性", propKey);
return target[propKey];
},
}
);
proxy.name; // 正在读取 name 属性
// get方法可继承
let obj = Object.create(proxy);
obj.name; // 正在读取 name 属性
2、set(target, propKey, value, receiver)
用于拦截 target 对象上的 propKey 的赋值操作。接受四个参数,target表示目标对象、propKey表示属性名、value表示属性值、receiver表示proxy实例本身。第四个参数非必须。
let handler = {
set: (target, propKey, value) => {
if (propKey === "age" && !Number.isInteger(value)) {
throw new TypeError("The age is not a integet");
}
if (propKey === "age" && value > 20) {
throw new RangeError("The age seems invalid");
}
target[propKey] = value;
return true;
},
};
let proxy = new Proxy({ name: "Tom", age: 2 }, handler);
proxy.age = 4;
console.log(proxy.age); // 4
proxy.age = 21; // RangeError: The age seems invalid
proxy.age = "4岁"; // TypeError: The age is not a integet
第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
贴一个第四个参数的例子。
let handler = {
set: (target, propKey, value, receiver) => {
target[propKey] = receiver;
return true;
},
};
let proxy = new Proxy({}, handler);
proxy.name = "Tom";
console.log(proxy.name === proxy); // true
如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
let target = {};
Object.defineProperty(target, "age", {
value: 4,
writable: false, // 属性是否可以被修改
});
let handler = {
set: (target, propKey, value, receiver) => {
target[propKey] = receiver;
},
};
let proxy = new Proxy(target, handler);
console.log(proxy.age); // 4
proxy.age = 5;
console.log(proxy.age); // 4
3、apply(target, ctx, args)
用于拦截函数的调用、call 和 apply 操作。接受三个参数,target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。
每当执行proxy
函数(直接调用或call
和apply
调用),就会被apply
方法拦截。
let target = (x, y) => x + y;
let handler = {
apply(target, ctx, args) {
return Reflect.apply(...arguments) * 2;
},
};
let proxy = new Proxy(target, handler);
console.log(proxy(1, 2)); // 6
console.log(proxy.call(null, 3, 4)); // 14
console.log(proxy.apply(null, [5, 6])); // 22
4、has(target, propKey)
用来拦截hasProperty方法,即判断对象是否具有某个属性时,会被这个方法方法拦截。典型的操作就是in运算符。接受两个属性,分别是目标对象和要查询的属性名。has方法不判断是自身属性还是继承属性。注意:此方法对 for...in 不生效。
let handler = {
has(target, propKey) {
if (propKey === "age" && target[propKey] < 0.3) {
console.log(`${target.name}是幼猫, 还不可以吃零食`);
return false;
}
console.log("handler has");
return propKey in target;
},
};
let proxy = new Proxy({ name: "Tom" }, handler);
console.log("name" in proxy); // handler has true
let Tom = { name: "Tom", age: 2 };
let yuanDan = { name: "yuanDanDan", age: 0.2 };
let cat1 = new Proxy(Tom, handler);
let cat2 = new Proxy(yuanDan, handler);
console.log("age" in cat1); // handler has true
console.log("age" in cat2); // yuanDanDan是幼猫, 还不可以吃零食 false
// for...in 不拦截
for (let k in cat2) {
console.log(cat2[k]); // yuanDanDan 0.2
}
5、construct(target, args, newTarget)
用于拦截 new 命令,接受三个参数,分别是目标对象、构造函数参数数组,new命令作用的构造函数。construct返回的必须是对象,否则会报错。因为construct拦截的是构造函数,他的目标对象必须是函数,否则也会报错。construct方法中this指向hanldler,不是实例对象。
let handler = {
construct(target, args, newTarget) {
console.log(this === handler);
return Reflect.construct(target, args, newTarget);
},
};
class Cat {
constructor(name) {
this.name = name;
}
}
let proxy = new Proxy(Cat, handler);
let cat = new proxy("Tom");
console.log(cat); // true Cat {name: 'Tom'}
console.log(cat.name); // Tom
6、deleteProperty(target, propKey)
用于拦截delete操作,如果返回false或者抛出错误,则propKey无法被delete命令删除。
let handler = {
deleteProperty(target, propKey) {
if (propKey[0] === "_") {
throw new Error(
`Invalid attempt to delete private "${propKey}" property`
);
}
delete target[propKey];
return true;
},
};
let proxy = new Proxy({ name: "Tom", _age: 2 }, handler);
delete proxy.name;
console.log(proxy.name); // undefined
delete proxy._age; // Error: Invalid attempt to delete private "_age" property
7、defineProperty(target, propKey, descriptor)
用于拦截Object.defineProperty方法的操作。
let handler = {
defineProperty(target, propKey, descriptor) {
return false;
},
};
let proxy = new Proxy({}, handler);
proxy.name = "Tom";
console.log(proxy.name); // undefined
8、getOwnPropertyDescriptor(target, propKey)
用于拦截Object.getOwnPropertyDescriptor方法,返回一个属性描述对象或者undefined。
let handler = {
getOwnPropertyDescriptor(target, key) {
let descriptor = Object.getOwnPropertyDescriptor(target, key);
console.log(descriptor);
return descriptor;
},
};
let proxy = new Proxy({ name: "Tom" }, handler);
Object.getOwnPropertyDescriptor(proxy, "name"); // {value: 'Tom', writable: true, enumerable: true, configurable: true}
9、getPrototypeOf(target)
主要用于拦截获取对象原型的操作。 具体以下几种:
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
let target = {}, handler = {
getPrototypeOf(target) {
return target;
}
}
let proxy = new Proxy(target, handler)
console.log(Object.getPrototypeOf(proxy) === target); // true
注意:getPrototypeOf 方法的返回值必须是对象或者 null,否则就会报错。如果目标对象不可扩展(non-extensible),getPrototypeOf 方法必须返回目标对象的原型对象。
10、 isExtensible(target)
用于拦截Object.isExtensible操作。
注意:它的返回值必须与目标对象的isExtensible属性保持一致,否则会抛出错误。
let proxy = new Proxy(
{},
{
isExtensible(target) {
console.log("handler isExtensible");
return true;
},
}
);
Object.isExtensible(proxy); // handler isExtensible
let proxy1 = new Proxy(
{},
{
isExtensible(target) {
return false;
},
}
);
Object.isExtensible(proxy1); //TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
11、 ownKeys(target)
用来拦截对象自身属性的读取操作。具体拦截以下几种:
Object.keys()
Object.getOwnPropertySymbols()
Object.getOwnPropertyNames()
for...in
// 拦截第一个字符为下划线的属性名
let target = { name: "Tom", _age: 2, _sex: "小公猫" };
let handler = {
ownKeys(target) {
return Reflect.ownKeys(target).filter((el) => el[0] !== "_");
},
};
let proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)); // ['name']
使用Object.keys()方法时,有三类属性回呗ownKeys()方法自动过滤,不会返回。
目标对象不存在的属性
- 属性名为Symbol值
不可枚举属性
let target = { name: "Tom", [Symbol.for("friend")]: "Jerry" };
let handler = {
ownKeys(target) {
return ["name", Symbol.for("friend"), "sex", "age"];
},
};
Object.defineProperty(target, "age", {
enumerable: false,
value: 2,
});
let proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)); // ["name"]
// 打印出来只有一个name属性,其他不存在的属性和Sybmol和不可枚举属性都被自动过滤掉
方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
let proxy = new Proxy(
{},
{
ownKeys(target) {
return [null];
},
}
);
Object.keys(proxy); // TypeError: null is not a valid property name
如果目标对象自身包含不可配置的属性,则该属性必须被ownKeys()方法返回,否则报错。
let target = { name: "Tom" };
Object.defineProperty(target, "sex", {
configurable: false,
value: "男",
});
let proxy = new Proxy(target, {
ownKeys(target) {
return ["name"];
},
});
Object.keys(proxy); // TypeError: 'ownKeys' on proxy: trap result did not include 'sex'
如果对象是不可扩展的 (not-extensible),此时ownKeys()方法返回的数组必须包含目标对象的所有属性,且不能包含其他多余的属性,否则就会报错。
let target = { name: "Tom", age: 2, sex: "男" };
let handler = {
ownKeys(target) {
return ["name", "age", "sex", "friend"];
},
};
Object.preventExtensions(target);
let proxy = new Proxy(target, handler);
// 包含多余属性 friend 所以报错
Object.keys(proxy); // TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible
12、preventExtensions(target)
用于拦截 Object.preventExtensions 操作。返回一个 Boolean 值,否则会自动转为 Boolean 值。
let proxy = new Proxy(
{},
{
preventExtensions(target) {
return true;
},
}
);
Object.preventExtensions(proxy); // TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
注意:只有目标对象不可扩展时,才可以返回 true,否则就会报错。所以,需要在 preventExtensions() 方法中把目标对象改成不可扩展才可以返回 true。
let proxy = new Proxy(
{},
{
preventExtensions(target) {
console.log("handler preventExtensions");
Reflect.preventExtensions(target);
return true;
},
}
);
console.log(Object.preventExtensions(proxy)); // handler preventExtensions Proxy {}
13、 setPrototypeOf(target, proto)
用于拦截 Object.setPrototypeOf() 方法。返回一个 Boolean 值,否则会自动转为 Boolean 值。如果目标对象不可扩展(not-extensible),setPrototypeOf() 方法不得改变目标对象的原型。
let proto = {};
let proxy = new Proxy(function () {}, {
setPrototypeOf(target, proto) {
// 只要修改target的原型对象,就抛出错误。
throw new Error("禁止更改原型 😎");
},
});
Object.setPrototypeOf(proxy, proto); // Error: 禁止更改原型 😎
Proxy.revocable()
Proxy.revocable()方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,可以取消Proxy
实例。
let target = { name: "Tom" },
handler = {};
let { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.name); // Tom
revoke();
console.log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked
Reflect
概述
Reflect对象与Proxy对象一样,也是ES6为了操作对象提供的API。
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect 对象对某些方法的返回结果进行了修改,使其更合理。
Reflect 对象使用函数的方式实现了 Object 的命令式操作。
静态方法
Reflect对象一共有13个静态方法。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
这些方法的作用,大部分与Object
对象的同名方法的作用都是相同的,而且它与Proxy
对象的方法是一一对应的。
1、Reflect.get(target, name, receiver)
Refect.get 方法查找并返回 target 对象的 name 属性,如果没有,则返回undefined。
当 name 属性部署了 getter 方法,则 getter 方法的 this 会绑定 receiver。
如果第一个参数不是对象,Refect.get 方法会报错。
let target = {
name: "Tom",
age: 2,
get info() {
return `name: ${this.name}, 年龄: ${this.age}`;
},
};
let receiver = {
name: "Jerry",
age: 2,
};
console.log(Reflect.get(target, "name")); // Tom
console.log(Reflect.get(target, "age")); // 2
console.log(Reflect.get(target, "info")); // name: Tom, 年龄: 2
console.log(Reflect.get(target, "info", receiver)); // name: Jerry, 猫龄: 2
console.log(Reflect.get(null, "name")); // TypeError: Reflect.get called on non-object
2、Reflect.set(target, name, value, receiver)
将 target 的 name 属性设置为 value。返回一个 Boolean 值,true 表示成功,false 表示失败。
当 name
属性设置了 setter 方法,则 setter 方法的 this 会
绑定 receiver。
value 为空时会将 name 属性清除。
如果第一个参数不是对象,Refect.set 方法会报错。
let target = {
name: "Jerry",
age: 2,
set info(value) {
return (this.age = value);
},
};
let receiver = {
name: "Tom",
age: 2,
};
Reflect.set(target, "age", 3);
console.log(target.age); // 3
console.log(receiver.age); // 2
Reflect.set(target, "age", 4, receiver);
console.log(target.age); // 3
console.log(receiver.age); // 4
Reflect.set(target, "age");
console.log(target.age); // undefined
Reflect.set(null, "age"); // TypeError: Reflect.set called on non-object
3、Reflect.has(obj, name)
是name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean 值。如果第一个参数不是对象,Reflect.has 方法会报错。
let target = {
name: "Jerry",
};
console.log("name" in target); // true
console.log(Reflect.has(target, "name")); // true
console.log("age" in target); // false
console.log(Reflect.has(target, "age")); // false
Reflect.has(null, "name"); // TypeError: Reflect.has called on non-object
4、Reflect.deleteProperty(obj, name)
是delete obj[name] 的函数化,用于删除对象属性,返回一个 Boolean 值。如果第一个参数不是对象,Reflect.deleteProperty 方法会报错。
let obj = { name: "Tom", age: 2 };
console.log(obj); // {name: 'Tom', age: 2}
Reflect.deleteProperty(obj, "name");
console.log(obj); // {age: 2}
Reflect.deleteProperty(null, "name"); // TypeError: Reflect.deleteProperty called on non-object
5、Reflect.construct(target, args)
Reflect.construct 方法 等于 new target(...args)。
function cat(name, age) {
this.name = name;
this.age = age;
}
console.log(new cat("Tom", 2)); // {name: 'Tom', age: 2}
console.log(Reflect.construct(cat, ["Tom", 2])); // {name: 'Tom', age: 2}
6、 Reflect.getPrototypeOf(obj)
用于读取 obj 的 _proto_ 属性,相当于 Object.getPrtotypeOf(obj)。Object.getPrptptypeOf 方法如果参数不是对象,会吧参数转成对象,然后读取。Reflect.getPrototypeOf 方法如果参数不是对象会报错。
class Cat {}
let obj = new Cat();
console.log(Reflect.getPrototypeOf(obj)); // {constructor: ƒ}
console.log(Cat.prototype); // {constructor: ƒ}
console.log(Reflect.getPrototypeOf(obj) === Cat.prototype); // true
Reflect.getPrototypeOf(1); // TypeError: Reflect.getPrototypeOf called on non-object
7、Reflect.setPrototypeOf(obj, newProto)
用于设置咪表对象的 prototype。相当于 Object.setProrotypeOf(obj, newProto)。返回一个 Boolean 值,表示是否设置成功。如果第一个参数不是对象,Object.setPrototypeOf 方法会返回第一个参数本身,Reflect.setPrototypeOf 方法会报错。如果参数是null,则都会报错。
let cat = {};
console.log(Object.setPrototypeOf(cat, Array.prototype)); // Array {}
console.log(Reflect.setPrototypeOf(cat, Array.prototype)); // true
console.log(Object.setPrototypeOf(1, Array.prototype)); // 1
console.log(Reflect.setPrototypeOf(1, Array.prototype)); // TypeError: Reflect.setPrototypeOf called on non-object
8、Reflect.apply(func, thisArg, args)
相当于Function.prototype.apply.call(func, thisArg, agts),用于绑定 this 对象后执行给定函数。
let arr = [9, 1, 2, 3, 4, 5, 6, 7, 8];
// 旧写法
let max = Math.max.apply(Math, arr);
console.log(max); // 9
// 新写法
let newmax = Reflect.apply(Math.max, Math, arr);
console.log(newmax); // 9
9、Reflect.defineProperty(target, propertyKey, attributes)
相当于 Object.deginfProperty(target, propertyKey, attr),用于为目标对象定义属性。如果第一个参数不是对象会报错。
let cat = {};
Object.defineProperty(cat, "age", { value: 2 });
console.log(cat); // {age: 2}
Reflect.defineProperty(cat, "name", { value: "Tom" });
console.log(cat); // {age: 2, name: 'Tom'}
Reflect.defineProperty(null, "name", { value: "Tom" }); // TypeError: Reflect.defineProperty called on non-object
这个方法可以与 Proxy.defineProperty 配合使用。Proxy.defineProperty 对属性赋值进行拦截,然后使用 Reflect.defineProperty 完成赋值。
let target = {},
handler = {
defineProperty(target, prop, desctiptor) {
Reflect.defineProperty(target, prop, desctiptor);
},
};
let proxy = new Proxy(target, handler);
proxy.name = "Tom";
console.log(proxy); // Proxy {name: 'Tom'}
10、Reflect.getOwnPropertyDescriptor(target, propKey)
相当于 Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象。如属性不存在,返回 undefined。如果第一个参数不是对象,则 Object.getOwnPropertyDescriptor 返回 undefined,Reflect.getOwnPropertyDescriptor会报错。
let cat = {};
Reflect.defineProperty(cat, "name", { value: "Tom" }); // {name: 'Tom'}
console.log(Object.getOwnPropertyDescriptor(cat, "name")); // {value: 'Tom', writable: false, enumerable: false, configurable: false}
console.log(Reflect.getOwnPropertyDescriptor(cat, "name")); // {value: 'Tom', writable: false, enumerable: false, configurable: false}
console.log(Reflect.getOwnPropertyDescriptor(cat, "age")); // undefined
console.log(Reflect.getOwnPropertyDescriptor(cat, "sex")); // undefined
console.log(Object.getOwnPropertyDescriptor(1, "name")); // undefined
console.log(Reflect.getOwnPropertyDescriptor(1, "name")) // TypeError: Reflect.getOwnPropertyDescriptor called on non-object
11、Reflect.isExtensible(target)
相当于 Object.isExtensible,返回一个 Boolean 值,表示目标对象是否可扩展。如果参数不是对象,Object.isExtensible 方法会返回 false,而 Reflect.isExtensible 会报错。
let cat = {};
console.log(Object.isExtensible(cat)); // true
console.log(Reflect.isExtensible(cat)); // true
console.log(Object.isExtensible(null)); // false
console.log(Reflect.isExtensible(null)); // TypeError: Reflect.isExtensible called on non-object
12、Reflect.preventExtensions(target)
用相当于 Object.preventExtensions,用于让对象变为不可扩展。如果 target 参数不是对象,会抛出错误。
let cat = {};
console.log(Object.preventExtensions(cat)); // {}
console.log(Reflect.preventExtensions(cat)); // true
console.log(Reflect.isExtensible(cat)); // false
console.log(Object.preventExtensions(null)); // null
console.log(Reflect.preventExtensions(null)); // TypeError: Reflect.preventExtensions called on non-object
13、Reflect.ownKeys(target)
相当于 Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols 之和。用于返回对象的所有属性。
let cat = {
name: "Tom",
[Symbol.for("age")]: 2,
};
console.log(Object.getOwnPropertyNames(cat)); // ['name']
console.log(Object.getOwnPropertySymbols(cat)); // [Symbol(age)]
console.log(Reflect.ownKeys(cat)); // ['name', Symbol(age)]
实例:使用Proxy实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
先定义一个Set集合,把所有观察者添加放入集合,然后使用observable函数创建Proxy代理,进行拦截操作。拦截函数中用Array.forEach方法执行所有观察者。
let target = { name: "Tom", age: 2 };
let handler = {
set(target, key, value, receiver) {
let result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach((observer) => observer());
return result;
},
};
// 定义Set集合
let queuedObservers = new Set();
// 观察者
let print1 = () => console.log(`观察者1:${cat.name}, ${cat.age}`);
let print2 = () => console.log(`观察者2:${cat.name}, ${cat.age}`);
// 添加观察者
let observe = (fn) => queuedObservers.add(fn);
observe(print1);
observe(print2);
// 创建Proxy代理
let observable = (target) => new Proxy(target, handler);
// 观察目标
let cat = observable(target);
cat.name = "Jerry"; // 观察者1:Jerry, 2 观察者2:Jerry, 2
cat.age = 3; // 观察者1:Jerry, 3 观察者2:Jerry, 3