本文深入解析JavaScript ES6引入的Proxy和Reflect机制。通过对比Object.defineProperty的局限,阐述Proxy如何创建代理对象以监听属性读写、新增删除等操作。详细列举13种Proxy捕获器,并解释Reflect作为统一API接口的必要性及其与Object方法的对应关系,帮助开发者掌握现代JS对象操作的最佳实践。
一、监听对象的操作
首先探讨一个常见需求:假设存在一个对象,我们需要监控其属性被读取或赋值的整个过程。
- 基于此前掌握的知识,我们能否实现这一目标?
- 答案是肯定的。我们可以利用之前介绍过的存储属性描述符来实现对对象操作的监听。
上述代码示例展示了如何利用存储属性描述符对属性操作进行监听。
const obj = {
name: "why",
age: 18,
height: 1.88
}
// 需求: 监听对象属性的所有操作
// 监听属性的操作
// 1.针对一个属性
// let _name = obj.name
// Object.defineProperty(obj, "name", {
// set: function(newValue) {
// console.log("监听: 给name设置了新的值:", newValue)
// _name = newValue
// },
// get: function() {
// console.log("监听: 获取name的值")
// return _name
// }
// })
// 2.监听所有的属性: 遍历所有的属性, 对每一个属性使用defineProperty
const keys = Object.keys(obj)
for (const key of keys) {
let value = obj[key]
Object.defineProperty(obj, key, {
set: function(newValue) {
console.log(`监听: 给${key}设置了新的值:`, newValue)
value = newValue
},
get: function() {
console.log(`监听: 获取${key}的值`)
return value
}
})
}
// console.log(obj.name)
// obj.name = "kobe"
console.log(obj.age)
obj.age = 17
console.log(obj.age)然而,这种方法存在哪些局限性?
- 首先,Object.defineProperty的设计初衷,并非用于监听截止一个对象中所有的属性。
- 在定义某些属性时,我们的初衷通常是创建普通属性,但此处却强制将其转换为数据属性描述符。
- 此外,若需监听更复杂的操作,如新增或删除属性,Object.defineProperty则显得力不从心。
因此,必须明确,存储数据描述符的设计初衷并非用于监听整个对象的所有操作。
二、Proxy类基本使用
ES6引入了Proxy类,从名称即可看出,其旨在协助创建代理对象:
- 换言之,若希望
监听一个对象的相关操作,我们可以先构建一个代理对象(即Proxy对象); - 随后,针对原对象的所有操作均
通过代理对象来完成,通过代理对象即可监听我们对原对象执行的具体操作;
我们可以使用Proxy重新实现上述案例:
- 首先,我们需要new Proxy对象,并且传入
需要侦听的对象以及一个处理对象,可以称之为handler;- const p = new Proxy(target, handler)
- 此外,后续操作均直接作用于Proxy对象而非原对象,因为我们需要在handler中进行侦听;
const obj = {
name: "why",
age: 18,
height: 1.88
}
// 1.创建一个Proxy对象
const objProxy = new Proxy(obj, {
set: function(target, key, newValue) {
console.log(`监听: 监听${key}的设置值: `, newValue)
target[key] = newValue
},
get: function(target, key) {
console.log(`监听: 监听${key}的获取`)
return target[key]
}
})
// 2.对obj的所有操作, 应该去操作objProxy
// console.log(objProxy.name)
// objProxy.name = "kobe"
// console.log(objProxy.name)
// objProxy.name = "james"
objProxy.address = "广州市"
console.log(objProxy.address)三、Proxy常见捕获器
1.Proxy的set和get捕获器
若需侦听具体操作,可在handler中添加相应的捕捉器(Trap):
set和get分别对应函数类型;
- set函数有四个参数:
- target:目标对象(被侦听的源对象);
- property:将被设置的属性键名(key);
- value:新设置的属性值;
- receiver:调用代理对象的实例;
- get函数有三个参数:
- target:目标对象(被侦听的源对象);
- property:被获取的属性键名(key);
- receiver:调用代理对象的实例;
2.Proxy其它捕获器
这13个捕获器各自承担什么功能?
handler.getPrototypeOf()
- 对应Object.getPrototypeOf方法的捕捉器。
handler.setPrototypeOf()
- 对应Object.setPrototypeOf方法的捕捉器。
handler.isExtensible()
- 对应Object.isExtensible方法的捕捉器(用于判断对象是否可扩展,即能否新增属性)。
handler.preventExtensions()
- 对应Object.preventExtensions方法的捕捉器。
handler.getOwnPropertyDescriptor()
- 对应Object.getOwnPropertyDescriptor方法的捕捉器。
handler.defineProperty()
- 对应Object.defineProperty方法的捕捉器。 Proxy所有捕获器
handler.ownKeys()
- 对应Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕捉器。
handler.has()
- 对应in操作符的捕捉器。
handler.get()
- 对应属性读取操作的捕捉器。
handler.set()
- 对应属性设置操作的捕捉器。
handler.deleteProperty()
- 对应delete操作符的捕捉器。
handler.apply()
- 对应函数调用操作的捕捉器。
- handler.construct()
- 对应new操作符的捕捉器。
const obj = {
name: "why",
age: 18,
height: 1.88
}
// 1.创建一个Proxy对象
const objProxy = new Proxy(obj, {
set: function(target, key, newValue) {
console.log(`监听: 监听${key}的设置值: `, newValue)
target[key] = newValue
},
get: function(target, key) {
console.log(`监听: 监听${key}的获取`)
return target[key]
},
deleteProperty: function(target, key) {
console.log(`监听: 监听删除${key}属性`)
delete obj.name
},
has: function(target, key) {
console.log(`监听: 监听in判断 ${key}属性`)
return key in target
}
})
delete objProxy.name
console.log("age" in objProxy)3.Proxy的construct和apply
此外,我们还会见到construct和apply捕获器,它们专门用于处理函数对象:
function foo(num1, num2) {
console.log(this, num1, num2)
}
const fooProxy = new Proxy(foo, {
apply: function(target, thisArg, otherArgs) {
console.log("监听执行了apply操作")
target.apply(thisArg, otherArgs)
},
construct: function(target, otherArray) {
console.log("监听执行了new操作")
console.log(target, otherArray)
return new target(...otherArray)
}
})
// fooProxy.apply("abc", [111, 222])
new fooProxy("aaa", "bbb")四、Reflect介绍和作用
Reflect同样是ES6新增的API,它是一个一个对象,字面含义为反射。
那么Reflect的具体用途是什么?
- 它主要提供了大量操作JavaScript对象的方法,功能类似于Object中操作对象的方法;
- 例如Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf();
- 又如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty();
既然已有Object能执行这些操作,为何还需要什么还需要有Reflect这样的新增对象?
- 原因在于早期ECMA规范未充分考虑如何规范设计对
对象本身的操作,故将这些API置于Object上; - 但随着发展,
Object作为一个构造函数,这些操作实际上置于Object上并不合适; - 此外,还包含一些
类似于 in、delete操作符,导致JavaScript语言表现略显怪异; - 因此在ES6中
新增了Reflect,使这些操作统一集中到Reflect对象上; - 另外,在使用Proxy时,可实现
不操作原对象;
关于Object和Reflect对象间的API关系,可参考MDN文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods
Object和Reflect之间的区别:Reflect具有返回值
"use strict"
const obj = {
name: "why",
age: 18
}
Object.defineProperty(obj, "name", {
configurable: false
})
// Reflect.defineProperty()
// 1.用以前的方式进行操作
// delete obj.name
// if (obj.name) {
// console.log("name没有删除成功")
// } else {
// console.log("name删除成功")
// }
// 2.Reflect
if (Reflect.deleteProperty(obj, "name")) {
console.log("name删除成功")
} else {
console.log("name没有删除成功")
}五、Reflect的基本使用
Reflect包含哪些常见方法?它与Proxy一一对应,共13个:
Reflect.getPrototypeOf(target)
- 类似于Object.getPrototypeOf()。
Reflect.setPrototypeOf(target, prototype)
- 设置对象原型的函数。若更新成功,返回Boolean类型的true。
Reflect.isExtensible(target)
- 类似于Object.isExtensible()
Reflect.preventExtensions(target)
- 类似于Object.preventExtensions()。返回一个Boolean值。
Reflect.getOwnPropertyDescriptor(target, propertyKey)
- 类似于Object.getOwnPropertyDescriptor()。若对象中存在该属性,则返回对应的属性描述符,否则返回undefined。
Reflect.defineProperty(target, propertyKey, attributes)
- 与Object.defineProperty()类似。若设置成功则返回true Reflect的常见方法
Reflect.ownKeys(target)
- 返回包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(),但不受enumerable影响)。
Reflect.has(target, propertyKey)
- 判断对象是否存在某属性,功能与in运算符完全相同。
Reflect.get(target, propertyKey[, receiver])
- 获取对象某属性的值,类似于target[name]。
Reflect.set(target, propertyKey, value[, receiver])
- 将值分配给属性的函数。若更新成功,返回true。
Reflect.deleteProperty(target, propertyKey)
- 作为函数的delete操作符,相当于执行delete target[name]。
Reflect.apply(target, thisArgument, argumentsList)
- 对函数进行调用操作,同时可传入数组作为调用参数。功能类似于Function.prototype.apply()。
Reflect.construct(target, argumentsList[, newTarget])
- 对构造函数执行new操作,相当于执行new target(…args)。
我们可以将之前Proxy案例中对原对象的操作,全部修改为使用Reflect来执行:
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
set: function(target, key, newValue, receiver) {
// target[key] = newValue
// 1.好处一: 代理对象的目的: 不再直接操作原对象
// 2.好处二: Reflect.set方法有返回Boolean值, 可以判断本次操作是否成功
const isSuccess = Reflect.set(target, key, newValue)
if (!isSuccess) {
throw new Error(`set ${key} failure`)
}
},
get: function(target, key, receiver) {
}
})
// 操作代理对象
objProxy.name = "kobe"
console.log(obj)1.Reflect的receiver
在使用getter和setter时会发现存在receiver参数,其作用为何?
若源对象(obj)包含setter和getter访问器属性,可通过receiver来改变内部的this;
const obj = {
_name: "why",
set name(newValue) {
console.log("this:", this) // 默认是obj
this._name = newValue
},
get name() {
return this._name
}
}
// obj.name = "aaaa"
// console.log(obj.name)
// obj.name = "kobe"
const objProxy = new Proxy(obj, {
set: function(target, key, newValue, receiver) {
// target[key] = newValue
// 1.好处一: 代理对象的目的: 不再直接操作原对象
// 2.好处二: Reflect.set方法有返回Boolean值, 可以判断本次操作是否成功
/*
3.好处三:
> receiver就是外层Proxy对象
> Reflect.set/get最后一个参数, 可以决定对象访问器setter/getter的this指向
*/
console.log("proxy中设置方法被调用" + key)
const isSuccess = Reflect.set(target, key, newValue, receiver)
if (!isSuccess) {
throw new Error(`set ${key} failure`)
}
},
get: function(target, key, receiver) {
console.log("proxy中获取方法被调用")
return Reflect.get(target, key, receiver)
}
})
// 操作代理对象
objProxy.name = "kobe"
console.log(objProxy.name)2.Reflect的construct
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age) {
// Person.call(this, name, age)
const _this = Reflect.construct(Person, [name, age], Student)
return _this
}
// const stu = new Student("why", 18)
const stu = new Student("why", 18)
console.log(stu)
console.log(stu.__proto__ === Student.prototype)至此,关于JavaScript中Proxy与Reflect的详细解析文章介绍完毕,更多相关JavaScript Proxy-Reflect内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!