1. 监听对象的操作
1. 通过defineProperty给对象创建属性描述符
我们知道在面向对象中对象属性描述符中有`set`与`get`可以对对象中的属性进行取值与赋值的监听。
const obj = {
name: "why",
age: 18
}
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get: function() {
console.log(`监听到obj对象的${key}属性被访问了`)
return value
},
set: function(newValue) {
console.log(`监听到obj对象的${key}属性被设置值`)
value = newValue
}
})
})
obj.name = "kobe"
obj.age = 30
console.log(obj.name)
console.log(obj.age)
虽然现在这种方式可以做到监听属性,因为对象属性描述符设计的初衷是用来定义属性的,我们强行将它变成了数据属性描述符。但是,我们想监听更加丰富的操作,比如新增、删除属性,我们这种方式就无能为力了。
2.Proxy
在ES6中新增了一个Proxy类,又称代理对象。如果我们希望监听一个对象的相关操作,都可以通过代理对象来完成。代理对象可以监听我们想要对原对象进行的一些操作(13种)
Proxy的使用:
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {})
objProxy.name = "kobe"
objProxy.age = 30
console.log(obj.name)
console.log(obj.age)
上面new一个Proxy对象时我们不对相关属性进行见监听的话,我们去更改代理对象时也会更改原对象的值。
设置捕获器的放式其实跟第一种方式的写法一样,在创建时对代理对象的捕获方法进行重写:
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
// 获取值时的捕获器
get: function(target, key) {
console.log(`监听到对象的${key}属性被访问了`, target)
return target[key]
},
// 设置值时的捕获器
set: function(target, key, newValue) {
console.log(`监听到对象的${key}属性被设置值`, target)
target[key] = newValue
}
})
console.log(objProxy.name)
console.log(objProxy.age)
objProxy.name = "kobe"
objProxy.age = 30
console.log(obj.name)
console.log(obj.age)
在get捕获器中的两个参数target与key分别是被代理的对象与当前获取属性的key值。
而set捕获器中的前两个参数与get一直,newValue显而易见就是我们要去把对象属性的原值修改成的新值。新增属性时也会被set进行捕获。
其实他们还有最后一个参数receiver这里先不展开。
Proxy中的其他捕获器
has: 监听in操作符的捕获器
const objProxy = new Proxy(obj, {
// 监听in的捕获器
has: function(target, key) {
console.log(`监听到对象的${key}属性in操作`, target)
return key in target
}
})
console.log("name" in objProxy)
deleteProperty:监听delete的捕获器
const objProxy = new Proxy(obj, {
// 监听delete的捕获器
deleteProperty: function(target, key) {
console.log(`监听到对象的${key}属性in操作`, target)
delete target[key]
}
})
delete objProxy.name
Proxy的所有的捕获器
最后的两个捕获器apply与construct比较特殊,他们是用来监听函数对象的捕获器。
function foo() {
}
const fooProxy = new Proxy(foo, {
apply: function(target, thisArg, argArray) {
console.log("对foo函数进行了apply调用")
return target.apply(thisArg, argArray)
},
construct: function(target, argArray, newTarget) {
console.log("对foo函数进行了new调用")
return new target(...argArray)
}
})
fooProxy.apply({}, ["abc", "cba"])
new fooProxy("abc", "cba")
apply是对函数进行apply调用时进行捕获,其参数除了第一个是目标对象,其他的参数跟调用apply传的参数一样。
construct是对函数进行new操作时进行捕获,它的第一个参数与第三个参数其实都是目标对象,argArray是new调用时传递的参数,它是个数组。
3. Reflect的作用
Reflect又称反射,是ES6新增的一个API,它是一个对象。它提供了很多操作对象的方法,有点类似于Object中操作对象的方法。那就有个疑问了,既然我们已经有Object了,为什么还要加一个Reflect呢。
这是因为在Js早期设计时,没有考虑到要对对象进行操作,而后面不知道要将这些操作的api放在哪里,于是一口气将这些api全部方法了Object上面。而Object作为一个构造函数,包含这些api显然不合适,而且还有类似in、delete这些操作,就让js看起来变的很奇怪。于是在ES6中,将这些操作就都放到了Reflect中。
Reflect和Proxy一起使用
我们在使用Proxy进行捕获操作时:
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
get: function(target, key) {
return target[key]
},
set: function(target, key, newValue) {
target[key] = newValue
}
})
objProxy.name = "kobe"
console.log(obj.name)
我们在set与get中都是使用target[key]的方式进行操作,其实这种操作还是对原对象进行的操作,这与我们使用Porxy对对象进行代理操作的方式背道而驰了。
于是我们可以使用Reflect来进行操作
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
console.log("get---------")
return Reflect.get(target, key)
},
set: function(target, key, newValue, receiver) {
console.log("set---------")
Reflect.set(target, key, newValue)
})
objProxy.name = "kobe"
console.log(objProxy.name)
这样的写法还有一个好处,当我们进行Reflect.set操作时,它会返回一个Boole类型的值来判断是否设置成功
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
set: function(target, key, newValue, receiver) {
console.log("set---------")
target[key] = newValue
const result = Reflect.set(target, key, newValue)
if (result) {
} else {
}
}
})
objProxy.name = "kobe"
console.log(objProxy.name)
4. Receiver参数的作用
现在我们有这样一串代码:
const obj = {
_name: "why",
get name() {
return this._name
},
set name(newValue) {
this._name = newValue
}
}
const objProxy = new Proxy(obj, {
get: function(target, key) {
console.log("get方法被访问--------", key)
return Reflect.get(target, key)
},
set: function(target, key, newValue) {
Reflect.set(target, key, newValue)
}
})
console.log(objProxy.name)
我们来分析一下它的运行流程,我们通过代理对象objProxy.name去问obj对象的_name时,它会先去调用代理对象的get方法,通过代理方法的Reflect.get去访问到obj对象中name的get方法,而其中的this指向的是obj对象。于是this._name就等于直接是对原对象obj._name进行访问,那么在obj中的get方法会绕过代理对象的拦截,直接访问原对象的内容。我们想要对原对象的所有访问都受到拦截就是失去了意义。
所以这里只会拦截到 name的访问,而我们其实想要对
_name的访问也进行拦截。
所以我们需要去想办法让obj对象中get方法的this指向我们的代理对象。这个时候我们就需要用到Receiver参数了。
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)
objProxy.name = "kobe"
在代理对象中的get、set方法中传入的receiver会改变原对象中get、set方法中的
this变成receiver,而这里的receiver在get、set方法传入时又指向了我们的代理对象,于是就可以将this变成我们的代理对象。
这里我们就能对_name的访问进行拦截了。
5. Reflect中construct的作用
现在我们有两个构造函数:
function Student(name, age) {
this.name = name
this.age = age
}
function Teacher() {
}
const stu = new Student("why", 18)
console.log(stu)
console.log(stu.__proto__ === Student.prototype)
如果我们想要使用Student中的构造函数但是创建出来的类型是Teacher要怎么做呢?
这里就要使用到Reflect中construct了。
function Student(name, age) {
this.name = name
this.age = age
}
function Teacher() {
}
// 执行Student函数中的内容, 但是创建出来对象是Teacher对象
const teacher = Reflect.construct(Student, ["why", 18], Teacher)
console.log(teacher)
console.log(teacher.__proto__ === Teacher.prototype)