Proxy
为了解决上述问题实现监听,es6引入了Proxy。Proxy是一个类,通过其实例化可以帮助我们创建一个代理对象。
1,Proxy监听对象
Proxy的基本使用
如果我们希望监听一个对象的相关操作,我们可以先创建一个代理对象(通过new Proxy来创建obj的代理对象objProxy)
实例化objProxy也就是new Proxy的过程需要接收两个参数,第一个参数为需要创建代理的目标对象(必填);第二个参数为捕获器(非必填项,可传入{})
const obj = {
name: "why",
age: 18
}
//对obj对象进行监听
//1 首先通过Proxy方法创建一个proxy对象
//2 new Proxy并传入参数,第一个参数为想监听的obj对象,第二个参数为捕获器(可以为空值),为空值就是不对任何捕获器进行重写
const objProxy = new Proxy(obj, {
})
此时:
1,代理objProxy对象,可以直接访问原对象obj身上的所有属性(如使用objProxy.name可以获取到’why’)
2,对objProxy上的属性进行修改操作后,原对象obj上的属性值也会随之被修改(如修改objProxy.name=‘aaa’,obj.name也会变成’aaa’)
(注:这些都是在没有传入任何捕获器的情况下,即捕获器传入的空对象{},没有捕获器对代理对象的修改就直接对原对象产生了影响)
捕获器
完成了初始化对象的代理后,别忘了我们一开始的目的是监听对象中对数据的操作/访问,想要完成这个目标就需要利用捕获器,对Proxy中的捕获器进行重写。
捕获器的使用
我们自己定义的捕获器方法会覆盖掉系统内部本身默认的捕获器,当我们满足某些条件的时候就会自动触发对应的捕获器(如进行获取值操作的时候触发get捕获器)
这里先以get捕获器为例,当访问objProxy.name的时候会自动触发get捕获器。对传入的get捕获器进行方法的重写以满足我们的需要。get捕获器自动接收多个参数,分别是target(目标对象),key(目标对象的属性),receiver(代理对象)
与之类似的修改值时会触发set捕获器,参数与get类似,多了个newValue(传入的新值)
其他捕获器
Proxy共13个捕获器,除去上述的get/set捕获器外,还有设置/获取对象原型的捕获器,阻止对象添加新属性的捕获器,查询对象中是否存在某个属性的捕获器等等
使用频率最高的是has捕获器(判断某个属性是否in对象),get捕获器,set捕获器,deleteProperty捕获器(判断是否执行了delete操作符的捕获器)
js中函数的本质也是一个对象,所以new Proxy的目标对象也可以为函数,也存在针对函数的捕获器。apply捕获器(监听函数是否被调用),construct捕获器(监听是否执行new操作)
Reflect
Reflect也是ES6新增的一个api,Reflect本身是一个对象,常常和Proxy一起使用,是用来替代Object的
Reflect的用处
Reflect本身就是一个对象,所以用的时候可以直接用,调用这个对象上的方法即可,(不用像他的好兄弟proxy一样new出来用)
提供了很多操作JS对象的方法,类似Object中操作对象的方法
Reflect提供了很多操作JS对象的方法,有点像Object中操作对象的方法,很多方法本质上和Object身上的方法都是一样的(当前某些方法会同时存在于Reflect和Object上),未来的新方法只会部署在Reflect上
之所以多出来一个Reflect和Object做一样的事情是因为,早期ECMA标准没有考虑到对对象本身的操作如何设计更加规范,所以将很多方法的api都直接放到了Object上面。但是Object作为一个构造函数,这些操作实际上放在Object身上并不合适,所以ES6中多了Reflect,并将方法都放到了Reflect上
Reflect的常见方法
Reflect和Proxy配合使用
首先明确,我们创建代理对象的目的就是不在原对象上进行直接的修改,所以我们获取/修改都是在代理对象上修改的。但是在上文中的Proxy使用中,对Proxy的操作同时对原对象进行了修改——对objProxy的值进行修改,obj也受到了影响,事与愿违。
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
return target[key]
},
set: function(target, key, newValue, receiver) {
}
})
objProxy.name = "kobe"
console.log(objProxy.name)//kobe
console.log(obj.name)//kobe
为了解决这一问题就需要用到Reflect,不再直接对原对象进行操作,而是通过Reflect创建原对象的映射,避免对原对象直接访问和操作。
Reflect上有一些方法与Proxy的捕获器一一对应,如Relect.get对应Proxy上的get捕获器,将上面代码调整为
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
console.log("get---------")
return Reflect.get(target, key)
//通过Reflect的映射避免了对原对象的直接访问/操作
//对get捕获器进行重写,返回结果不再是原对象,而是通过Reflect上的get方法,Reflect上的方法和Proxy捕获器方法一一对应
//这里Reflect.get也接收三个参数,和get捕获器接收的参数一致,target原对象,key原对象的属性,receiver
},
set: function(target, key, newValue, receiver) {
console.log("set---------")
target[key] = newValue
Reflect.set(target, key,newValue)
//操作和get捕获器类似
const result = Reflect.set(target, key, newValue)
if (result) {
//设置成功执行XXX
} else {
//设置失败执行XXX
}
//通过Reflect.set设置值会返回一个布尔值表示这次修改值的操作是否成功(如object.freeze的话操作就会失败返回false),通过监听设置的结果我们可以执行一些对应的操作
}
})
objProxy.name = "kobe"
console.log(objProxy.name)
receiver参数
只有Proxy对象中get和set捕获器中的receiver参数。简而言之receiver参数指代的是代理对象(也就是上文中的objProxy), objProxy创建的时候会把本身作为receiver参数传入get和set捕获器,虽然和objProxy本质上是一个东西,但是规定不能直接用外面的objProxy,而要用receiver的形式
receiver可以改变原obj对象中getter/setter的this指向
当使用receiver改变了obj中getert/setter方法中的this指向,将其从指向obj改为指向objProxy中,这样才能让objProxy中get/set捕获器里面编写的拦截方法变得有意义了
//这里的get和set是js的obj中的关键字,通过关键字可以创建getter和setter函数。get函数没有参数,set函数会将等号右边的值作为参数,setter和getter连用可以创建一个伪属性,不能在具有真实值的属性上同时拥有一个setter函数
//
const obj = {
_name: "why",
get name() {
return this._name
},
set name(newValue) {
this._name = newValue
}
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
// receiver是创建出来的代理对象
console.log("get方法被访问--------", key, receiver)
console.log(receiver === objProxy)
return Reflect.get(target, key, receiver)
},
set: function(target, key, newValue, receiver) {
console.log("set方法被访问--------", key)
Reflect.set(target, key, newValue, receiver)
}
})
// console.log(objProxy.name)
//reflect的get和set方法同样可以传入receiver作为第三个参数,当receiver作为reflect的get方法的参数时,会改变原obj对象上的this指向,将其从指向自身(obj)改为指向代理对象(这里就是objProxy)
//使用console.log(objProxy.name)通过代理对象访问name属性的时候就会再次触发get方法,也就是这里会访问两次objProxy的get方法,一次是name被访问的时候触发的,
//另一次是_name被访问时触发的,获取objProxy.name时候触发get方法,这个是肯定的,还有另一次我理解是objProxy.name是通过obj对象获取的,也就是obj.name,但是receiver改变了obj中的this指向,指向了objProxy,所以又触发了一次objProxy身上的get
objProxy.name = "kobe"
Reflect中construct的作用
改A构建出来的东西的原型为B