监听对象的操作
我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程。我们可以通过之前的属性描述符中的存储属性描述符来做到。如果不知道对象属性描述符的可以看这篇文章:对象属性描述符。
使用Object.defineproperty的缺点:
- 首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的。我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强 行将它变成了数据属性描述符。
- 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty是无能为力的。
所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象
存取属性描述符的操作
监听一个属性,以obj中的name属性为例子
const obj = {
name:'harry',
age:21
}
Object.defineProperty(obj,"name",{
get(){
console.log('监听到Obj对象的name属性被访问了');
return undefined ; //这是默认的操作
},
set(){
console.log('监听到obj对象的name属性被设置了');
}
})
console.log(obj.name);
obj.name = 'zhangsan'
如果要监听多个属性
const obj = {
name:'harry',
age:21
}
Object.keys(obj).forEach(item=>{
let value = obj[item]
Object.defineProperty(obj,item,{
get(){
console.log(`监听到obj对象的${item}属性被访问了`);
return value
},
set(newValue){
console.log(`监听到obj对象的${item}属性被设置值`);
value = newValue
}
})
})
obj.name = 'kobe'
obj.age = 21
console.log(obj.name);
console.log(obj.age);
Proxy基本使用
在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的。也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。
采用Proxy的方式监听对象
注意,这里的set捕获器有四个参数:target:目标对象(侦听的对象),property:将被设置的属性key,value:新属性值,receiver:调用的代理对象。
get捕获器有三个参数:target:目标对象(侦听的对象)、property:被获取的属性key、receiver:调用的代理对象。
const obj = {
name : 'harry',
age:21
}
const objProxy = new Proxy(obj,{
//获取值时候的捕获器,会替换掉它默认的捕获器
get(target,key){ //target是当前的对象,key是属性
console.log(`监听对象的${key}属性被访问了`,target);
return target[key]
},
//设置值的捕获器
set(target,key,newValue){
target[key] = newValue
console.log(`监听对象的${key}属性被设置值了${newValue}`,target);
}
})
console.log(objProxy.name);
objProxy.age = 30
console.log(obj.age); //30
如果不写捕获器,默认也是进行如上的操作。
const obj = {
name : 'harry',
age:21
}
const objProxy = new Proxy(obj,{
})
console.log(objProxy.name);
objProxy.age = 30
console.log(obj.age); //30
Proxy的捕获器
Proxy一共有13种捕获器。
比较常用的捕获器
const obj = {
name:'harry', //数据属性描述符
age:18
}
//变成一个访问属性描述符
//这里有13个捕获器
const objProxy = new Proxy(obj,{
//获取值时候的捕获器,会替换掉它默认的捕获器
get:function(target,key){ //这是会自动进行回调的(target,key)会传两个属性
console.log(`监听对象的${key}属性被访问了`,target);
return target[key] //target是当前的对象
},
//设置值的捕获器
set:function(target,key,newValue){
target[key] = newValue
console.log(`监听对象的${key}属性被设置值了,值为${newValue}`,target);
},
//监听in的捕获器
has(target,key){
console.log(`监听对象的${key}属性的in操作`,target);
return key in target
},
//监听delete的捕获器
deleteProperty(target,key){
console.log(`监听对象的${key}属性的delete操作`,target);
delete target[key]
}
})
// in操作符
console.log("name" in objProxy); //true
//delete操作
delete objProxy.name
Proxy对函数对象的监听
function foo(){
}
const fooProxy = new Proxy(foo,{
//针对apply的捕获器
apply(target,thisArg,argArray){
console.log("对foo函数进行了apply调用");
return target.apply(thisArg,argArray)
},
//针对new的捕获器
construct(target,argArray,newTarget){
return new target(...argArray) //参数展开
}
})
// foo()
// foo.apply({},["abc","cba"])
fooProxy.apply({},["abc","cba"])
new fooProxy("abc","cba")
Reflet的使用
Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。那么这个Reflect有什么用呢?
- 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
- 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
- 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
- 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面
- 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适
- 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪
- 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上
Reflect常见的方法:
Reflect案例
const obj = {
name : 'harry',
age:18
}
const objProxy = new Proxy(obj,{
get(target,key,receiver){
console.log(`${target}对象的${key}属性被调用了`);
return Reflect.get(target,key) //替换return target[key]
},
set(target,key,newValue,receiver){
console.log('----------set-------------');
//使用Reflect的好处
const result = Reflect.set(target,key,newValue)
//会返回结果,判断设置成功和设置失败,这是它的优势
if(result){
}else{
}
}
})
objProxy.name = "kobe"
console.log(objProxy.name);
关于Reciever参数的作用
const obj = {
_name:'harry',
get name(){
return this._name //这个this指向哪?答案是Obj对象
//想办法让这个this指向Proxy对象而不是obj对象
//receiver就是做这个功能的
},
set name(newValue){
this._name = newValue
}
}
const objProxy = new Proxy(obj,{
get(target,key,receiver){
return Reflect.get(target,key,receiver) //这第三个参数可以改变obj对象中的this
},
set(target,key,newValue,receiver){
Reflect.set(target,key,newValue,receiver)
}
})
objProxy.name = '张三'
console.log(obj.name);
Reflect中construct的使用
这个在某些情况下会使用到,比如下面的代码,要求执行Student函数的内容,但是创建出来的是Teacher对象。
function Student(name,age){
this.name = name
this.age = age
}
function Teacher(){
}
const stu = new Student('harry','21')
console.log(stu); //Student类型
console.log(stu.__proto__ === Student.prototype);
//执行Student函数的内容,但是创建出来的对象是Teacher对象
//执行Student函数中的内容
const teacher = Reflect.construct(Student,["harry",18],Teacher) //第一个参数,执行的构造函数,第二个参数为属性,第三个为需要创建出来的对象
console.log(teacher);
console.log(teacher.__proto__ === Teacher.prototype);