Proxy
对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
语法:
var proxy = new Proxy(target, handler);
Proxy
对象的所有用法,都是上面这种形式,不同的只是 handler
参数的写法。
其中:
target
:表示要代理的对象handler
:也是一个对象,用来定义要代理的对象的行为var obj = {}
// 代理 obj 对象中属性的 get 和 set 行为
var proxyObj = new Proxy(obj, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
// 赋值语句触发 setter,代理 obj 对象的 set 行为,控制台打印 setting count!
proxyObj.count = 1
// 控制台打印:getting count! setting count!
++proxyObj.count
console.log(obj) // { count: 2 }
// 没有经过 proxyObj 的代理,控制台直接打印:2 ===> counthahahah
console.log(obj.count, "===> counthahahah")
obj.count = 0
console.log(proxyObj) // { count: 0 }
console.log(proxyObj.count) // getting count!
Proxy 实例也可以作为其他对象的原型对象。
var proxy = new Proxy({}, {
get: function(target, propKey, receiver) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
上面代码中,proxy
对象是 obj
对象的原型,obj 对象本身并没有 time 属性,所以根据原型链,会在 proxy 对象上读取该属性,导致被拦截。
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
上面代码中,一旦 proxy
代理 target.m
,后者内部的 this
就是指向 proxy
,而不是 target
。
这时只要改变 this
指向就可以解决这个问题
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {
get(target, propName) {
if(propName === 'm') {
// 绑定 this 指针
return target.m.bind(target)
}
return Reflect.get(target, propName)
}
};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // false
阮一峰 ES6 教程 ---- bookstack.cn/read/es6-3rd/spilt.2.docs-proxy.md
get(target, propKey, receiver)
:拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
set(target, propKey, value, receiver)
:拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
has(target, propKey)
:拦截propKey in proxy的操作,返回一个布尔值。
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(…)。
construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
Reflect
对象与 Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect
对象的设计目的有这样几个:
Object
对象的一些明显属于语言内部的方法(比如 Object.defineProperty
),放到 Reflect
对象上。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)
让它们变成了函数行为。Reflect
对象的方法与 Proxy
对象的方法一一对应,只要是 Proxy
对象的方法,就能在 Reflect
对象上找到对应的方法。这就让 Proxy
对象可以方便地调用对应的 Reflect
方法,完成默认行为,作为修改行为的基础。Proxy(target, {
set: function(target, name, value, receiver) {
// eg:Reflect.set 方法设置 target 对象的 name 属性,值为 value。
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});
上面代码中,Proxy
方法拦截 target
对象的属性赋值行为。它采用 Reflect.set
方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
下面是另一个例子。
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});
上面代码中,每一个 Proxy
对象的拦截操作(get、delete、has
),内部都调用对应的 Reflect
方法,保证原生行为能够正常执行。
观察者模式 ---- https://blog.csdn.net/weixin_43842373/article/details/121975015
class Subject {
observers = new Set() // 存放观察者列表
constructor(obj) {
this.obj = new Proxy(obj, { // 可观察对象
set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver)
// 多播,执行观察者函数
this.observers.forEach(observer => {
observer(this.obj)
});
return result
}
})
}
/**
* 观察者观察目标对象,传入一个观察者函数
* @param {Function} fn 观察者函数
*/
observe(fn) {
this.observers.add(fn)
}
}
const subject = new Subject({
name: 'test',
age: 18,
sex: '男'
})
// 观察
subject.observe((people) => {
console.log('你好,对象改变了的话告诉我')
})
subject.observe((people) => {
console.log('哈哈哈哈哈', people)
})
subject.obj.name = '星星'