一、Proxy简介
1.概述
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
二、Proxy用法
1.基本用法
一个 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
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
// target 可以为空对象
let targetEpt = {
}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt)
// {name: "Tom"}
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {
}
let proxyEmpty = new Proxy(targetEmpty,{
})
proxyEmpty.name = "Tom"
targetEmpty) // {name: "Tom"}
2.Proxy实例方法
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]
//如果访问目标对象不存在的属性,会抛出一个错误
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
});
proxy.name // "张三"
proxy.age // 抛出一个错误
//get方法可以继承
let proto = new Proxy({
}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo // "GET foo"
- set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值
//限制age属性,该属性应该是一个不大于 200 的整数
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({
}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
//属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get和set方法,就可以做到防止这些内部属性被外部读写。
const handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${
action} private "${
key}" property`);
}
}
const target = {
};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
- has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值
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
// true
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)
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
// 1
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)
三、Reflect简介
1.概述
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API
2.Reflect对象的设计目的
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
- 修改某些Object方法的返回结果,让其变得更合理。
- 让Object操作都变成函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
四、Reflect用法
1.静态方法
Reflect对象一共有 13 个静态方法。
- Reflect.get(target, name, receiver):查找并返回target对象的name属性。
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError
- Reflect.set(target, name, value, receiver):设置target对象的name属性等于value。返回值为 boolean ,true 表示修改成功,false 表示失败。
let exam = {
name: "Tom",
age: 24,
set info(value){
return this.age = value;
}
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
// value 为空时会将 name 属性清除
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
let receiver1 = {
name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1
- Reflect.has(target, name):用于查找 name 属性在 obj 对象中是否存在。
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name'); // true
- Reflect.defineProperty(target, name, desc):用于为目标对象定义属性。如果 target 不是对象,会抛出错误。
const student = {
};
Reflect.defineProperty(student, "name", {
value: "Mike"}); // true
student.name; // "Mike"
- Reflect.deleteProperty(target, name):用于删除 obj 对象的 property 属性,返回值为 boolean
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24}
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true
- Reflect.getPrototypeOf(obj):用于读取 obj 的 proto 属性。
class Exam{
}
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype // true
//扩展: _proto_与prototype
对象的_proto_属性,和创建这个对象的构造函数的prototype是一个东西。
function Fn(){
}
var o=new Fn();
console.log(o._proto_===Fn.prototype);//true
- Reflect.setPrototypeOf(obj, newProto):用于设置目标对象的 prototype。
let obj ={
}
Reflect.setPrototypeOf(obj, Array.prototype); // true
- Reflect.apply(target, thisArg, args):等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
- Reflect.construct(target, args):等同于 new target(…args)
function exam(name){
this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
- Reflect.ownKeys(target):用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
- Reflect.isExtensible(target):用于判断 target 对象是否可扩展。返回值为 boolean 。
let exam = {
}
Reflect.isExtensible(exam) // true
- Reflect.preventExtensions(target):用于让 target 对象变为不可扩展。
let exam = {
}
Reflect.preventExtensions(exam) // true
- Reflect.getOwnPropertyDescriptor(target, name):用于得到 target 对象的 propertyKey 属性的描述对象。
var exam = {
}
Reflect.defineProperty(exam, 'name', {
value: true,
enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}
五、Proxy&Reflect组合使用
1.基础样例
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"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"
2.实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
//数据对象person是观察目标,函数print是观察者。
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${
person.name}, ${
person.age}`)
}
observe(print);
person.name = '李四';
// 自动输出:李四, 20
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {
set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
https://es6.ruanyifeng.com/#docs/proxy
https://www.runoob.com/w3cnote/es6-reflect-proxy.html