proxy
如果我们有一个需求,想要监听在一个对象上,对于其属性的各种操作,也就是在对对象属性进行操作的时候,自动执行一系列的预设操作
例如: 当我们修改对象的属性的时候,自动去更新界面上所有依赖(使用)该属性的dom,这种行为被称之为响应式
为了完成这个需求,我们第一想到的是Object.defineProperty方法
const user = {
name: 'Klaus',
age: 23
}
// Object.keys获取的是可迭代对象,所以可以直接使用forEach或for-of进行迭代
Object.keys(user).forEach(key => {
let value = user[key]
Object.defineProperty(user, key, {
set(newV) {
console.log(`属性:${key}设置了,值为 ${value}`)
// 这里本质上就是一个闭包
value = newV
},
get () {
console.log(`获取属性${key}`)
return value
}
})
})
// 这里设置和获取值,实际上是去获取和设置
// forEach构成的闭包中的那个value属性值
user.name = 'Alex'
console.log(user.name)
user.age = 18
console.log(user.age)
但是这样存在一个弊端,Object.defineProperty设计的初衷是为了添加属性描述符的,并不是用来监听一个完整的对象
所以如果我们想监听更加丰富的操作,比如新增属性、删除属性,in操作符,那么 Object.defineProperty是无能为力的
为此,在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理对象
也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象)
之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作
使用方式:
-
首先,我们需要new Proxy对象,并且传入需要侦听的对象以及一个处理对象,可以称之为handler
const p = new Proxy(target, handler) -
其次,我们之后的操作都是直接对Proxy的操作,而不是原有的对象,因为我们需要在handler里面进行侦听
- 我们所有的操作 都通过代理对象进行操作
- 代理对象监听我们对对象的操作,进行二次处理和加工后,再作用于原对象
const user = {
name: 'Klaus',
age: 23
}
// 第一个参数为 原始对象 也就是我们需要代理的那个对象
// 第二个参数为 侦听器对象
// 如果对应的操作 没有设置监听函数 那么就会使用默认行为 进行处理
// 如果设置了对应的操作,那么就会覆盖默认行为,直接执行对应的侦听器函数(捕获器函数)
// 即自己设置的捕获器函数是对原本行为的重写
const userProxy = new Proxy(user, {})
console.log(userProxy.name) // => Klaus
userProxy.name = 'Alex'
console.log(userProxy.name) // => Alex
const user = {
name: 'Klaus',
age: 23
}
const userProxy = new Proxy(user, {
// set方法有四个参数
// 参数1 - 代理的原始对象
// 参数2 - 属性名
// 参数3 - 属性值
// 参数4 - 代理对象本身 receiver === userProxy -> true
set(target, key, value, receiver) {
console.log(`属性${key}正在执行赋值操作`)
// 操作源对象的时候需要使用target
// 因为很有可能user和userProxy并不在同一个文件中
target[key] = value
// 返回布尔值表示新值是否设置成功
return true
},
// get方法有三个参数
// 参数1 - 代理的原始对象
// 参数2 - 属性名
// 参数3 - 代理对象本身
get(target, key, receiver) {
console.log(`属性${key}正在被读取`)
return target[key]
}
})
// 使用代理对象后,所有操作应该都通过代理对象进行操作,而不应该操作原始对象
console.log(userProxy.name) // => Klaus
userProxy.name = 'Alex'
console.log(userProxy.name) // => Alex
同时,proxy本身就是用来进行对象代理的,所以proxy相比defineProperty而言,可以监听的行为更多更全

const user = {
name: 'Klaus',
age: 23
}
const proxy = new Proxy(user, {
has(target, key) {
console.log(`${key} 正在执行in判断`)
return key in target
},
deleteProperty(target, key) {
console.log(`${key} 正在执行delete操作`)
return delete target[key]
}
})
console.log('name' in proxy)
delete proxy.age
function Foo(num1, num2) {
console.log(this, num1, num2)
}
const proxy = new Proxy(Foo, {
construct(target, args) {
console.log(`正在新建${target.name}的实例对象`)
return new target(...args)
},
apply(target, thisArg, args) {
console.log(`${target.name}正在执行apply方法`)
return target.apply(thisArg, args)
}
})
proxy.apply({ name: 'Klaus' }, [23, 32])
const foo = new proxy(10, 20)
reflect
Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射
也就意味着Reflect是一个和Math类似的对象,不需要通过new关键字直接可以使用
Reflect 是 JavaScript 中的一个内置对象,它提供了一组静态方法,用于操作对象自身
它与全局对象 Object 有着相似的功能,比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf()
但是它更加严格、更具语义性
Reflect出现的原因是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面
而随着时间的推移,在Object上定义的那些操作对象本身的方法,也就是那些类方法越来越多,就导致Object对象变得越来越庞大
另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪
所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上
Reflect 反映了对象自身的一些操作和修改,包括但不限于设置和读取对象属性、调用对象方法、创建对象实例等
Reflect 提供了一组 API,可以帮助我们更灵活地控制和管理对象的行为,而不必依赖于固定的语法
简而言之,Reflect是指对象上的一些操作,修改,调用对象自身的方法

const user = {
name: 'Klaus',
age: 23
}
Object.defineProperty(user, 'name', {
configurable: false
})
// 早期的做法
// 1. 单从这句delete语句中而言,我们不能知道name属性是否被正常删除
// 2. 对于对象的操作有些是方法,而有些是操作符,不够统一
// 3. 对于对象的操作, 非严格模式下 可能是静默错误,而在严格模式下,可能直接报错
// delete user.name
// 为此,ES6将这里对于对象自身进行操作的(主要是那些类方法),单独抽取到了Reflect对象上
// Reflect.deleteProperty就是用于替换delete操作符
// 1. 使属性删除操作变成方法,和其它方法(如defineProperty)一样 更为的统一规范
// 2. 该方法会返回boolean类型值,表示是否删除成功
// 我们可以直接进行判断,而不会出现静默错误,或单独只在严格模式下报错的情况
if (Reflect.deleteProperty(user, 'name')) {
console.log('删除成功')
} else {
console.log('删除失败')
}
基本使用
const user = {
name: 'Klaus',
age: 23
}
const proxy = new Proxy(user, {
set(target, key, value) {
console.log('setter')
// target === user -> true
// 所以使用下述方法 本质依旧是直接在操作源对象
// target[key] = value
// 但是使用Proxy的目的就是为了不直接操作源对象
// 所以为了使原始对象和代理对象进行分离
// 同时 为了 更好的符合规范, 可以使用Reflect来替换上述方法的操作
// 使用Reflect来在语言层面去操作源对象,而不是直接使用JS API在语言层面去操作原对象(优点一)
// 同时Reflect上的API相比较原本的那些操作对象的API而言,更为的规范和安全(优点二)
// 所以Reflect 经常 和 Proxy 一起结合使用
Reflect.set(target, key, value)
},
get(target, key) {
console.log('getter')
// return target[key]
return Reflect.get(target, key)
}
})
proxy.name = 'Alex'
console.log(proxy.name)
receiver
如果我们的源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this
const user = {
_name: 'Klaus',
get name() {
// 默认情况下,代理操作原始对象的时候
// 访问器中的get和set方法中的this是原始对象
// 此时对于_name的设置 无法被监听到
console.log(this === user) // => true
return this._name
},
set name(v) {
console.log(this === user) // => true
this._name = v
}
}
const proxy = new Proxy(user, {
get(target, key) {
console.log('getter')
// 此时操作target对象的this就是源对象,并不是代理对象
return Reflect.get(target, key)
},
set(target, key, value) {
console.log('setters')
Reflect.set(target, key, value)
}
})
proxy.name = 'Alex'
console.log(proxy.name)
const user = {
_name: 'Klaus',
get name() {
return this._name
},
set name(v) {
this._name = v
}
}
const proxy = new Proxy(user, {
// receiver参数的作用是用于修改访问器中的this
// 以便于对应的实际操作的私有属性(在本例中就是_name)的操作,也可以被监听到
get(target, key, rceiver) {
// receiver就是当前proxy对象本身
console.log(reveiver === proxy) // => true
console.log('getter')
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('setters')
Reflect.set(target, key, value, receiver)
}
})
proxy.name = 'Alex'
console.log(proxy.name)
Reflect.construct
# 下述代码在功能上等价于
# new target(...argumentsList)
Reflect.construct(target, argumentsList)
# 下述代码在功能上等价于
# new target.apply(newTarget, argumentsList)
Reflect.construct(target, argumentsList, newTarget)
function Person(name, age) {
this.name = name
this.age = age
}
// 借用构造函数继承属性
function Student(name, age) {
// 如果存在 Reflect.construct
if (Reflect && Reflect.construct) {
// Reflect.construct(需要调用构造函数, 参数列表构成的数组,实际需要的类型对应的构造函数)
return Reflect.construct(Person, [name, age], Student)
// 上述代码的含义是 调用Person的构造函数,传入name和age,但是创建出的实例类型是Student的实例
// 等价于 Person.apply(this, [name, age])
} else {
// Person.apply(Student, [name, age])
return Person.apply(this, [name, age])
}
}
const stu = new Student('Klaus', 23)
console.log(stu)