一、Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
1.1 Object.defineProperty
- Object.defineProperty 通过存储属性描述符来对对象属性的操作进行监听
-
Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的,是定义普通的属性,但是后面我们强行将它变成了数据属性描述符
-
如果监听更加丰富的操作,比如新增属性、删除属性,Object.defineProperty是无能为力的
const obj = { name: "lilei", age: 18, height: 1.78 } // 需求: 监听对象属性的所有操作 // 监听所有的属性: 遍历所有的属性, 对每一个属性使用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 = "hanmeimei" console.log(obj.age) obj.age = 17 console.log(obj.age) // 新增和删除监听不到 obj.adress = "北京市" delete obj.height -
1.2 Proxy基本使用
- proxy监听对象
- 监听一个对象的相关操作,先创建一个代理对象(Proxy对象),之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听对原对象进行哪些操作
-
new Proxy(target, handler)
const p = new Proxy(target, handler)
-
想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)
-
set函数有四个参数:
- target:目标对象(侦听的对象)
- property:将被设置的属性key
- value:新属性值
- receiver:调用的代理对象
-
get函数有三个参数:
- target:目标对象(侦听的对象)
- property:被获取的属性key
- receiver:调用的代理对象
const obj = { name: "lilei", age: 18, height: 1.78 } // 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 = "hanmeimei" console.log(objProxy.name) objProxy.name = "jim" objProxy.address = "北京市" console.log(objProxy.address) -
1.3 Proxy捕获器
-
handler.has():
in操作符的捕捉器 -
handler.get():属性读取操作的捕捉器
-
handler.set():属性设置操作的捕捉器
-
handler.deleteProperty():delete 操作符的捕捉器
-
handler.getPrototypeOf():Object.getPrototypeOf 方法的捕捉器
-
handler.setPrototypeOf():Object.setPrototypeOf 方法的捕捉器
-
handler.isExtensible():Object.isExtensible 方法的捕捉器(判断是否可以新增属性)
-
handler.preventExtensions():Object.preventExtensions 方法的捕捉器(阻止对象的扩展)
-
handler.getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 方法的捕捉器
-
handler.defineProperty():Object.defineProperty 方法的捕捉器
-
handler.ownKeys():Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
const obj = { name: "lilei", age: 18, height: 1.78 } // 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)
1.4 apply/construct(应用于函数对象)
- handler.apply():函数调用操作的捕捉器。
- handler.construct():new 操作符的捕捉器
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, otherArgs) { console.log("监听执行了new操作") console.log(target, otherArgs) return new target(...otherArgs) } }) // fooProxy.apply("abc", [111, 222]) new fooProxy("aaa", "bbb")
二、 Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与Proxy的方法相同。
Reflect不是一个函数对象,因此它是不可构造的。
2.1 Reflect 和 Object
- Reflect 提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法
- Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf()
- Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()
- 为什么还需要有Reflect对象
-
因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面,但是Object作为一个构造函数,这些操作实际上放到它身上并不合适,另外还包含一些类似于
in、delete操作符,让JS看起来是会有一些奇怪的; -
所以在ES6中新增了Reflect,这些操作都集中到了Reflect对象上,另外在使用Proxy时,可以做到不操作原对象。
-
- Object和Reflect对象之间的API关系,可以参考MDN文档
2.2 Object方法存在问题
- Object.defineProperty() 在设置对象的属性为不可配置时,仍设置对象的某个属性,并不知道是否定义成功
- Object.defineProperty() 在设置对象的属性为不可配置时,删除对象的某个属性,在严格模式下会报错
"use strict" const obj = { name: "kobe", age: 24 } Object.defineProperty(obj, "name", { configurable: false }) delete obj.name // Uncaught TypeError: Cannot delete property 'name' of #<Object> if (obj.name) { console.log("name没有删除成功") } else { console.log("name删除成功") }
2.3 Reflect -> 返回布尔值
-
Reflect.defineProperty() 在设置对象的属性时会返回一个布尔值
"use strict" const obj = { name: "kobe", age: 24 } Object.defineProperty(obj, "name", { configurable: false }) if (Reflect.deleteProperty(obj, "name")) { console.log("name删除成功") } else { console.log("name没有删除成功") } -
常见方法:与 Proxy 对应
-
Reflect.has(target, propertyKey):判断一个对象是否存在某个属性,和
in运算符 的功能完全相同 -
Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于 target[name]
-
Reflect.set(target, propertyKey, value[, receiver]):将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true
-
Reflect.deleteProperty(target, propertyKey):作为函数的delete操作符,相当于执行 delete target[name]
-
2.4 Reflect和Proxy一起使用
- 代理对象的目的:
- 不直接操作原对象
- 有返回值
- 改变访问器中的this指向
const obj = { name: "kobe", age: 24 } const objProxy = new Proxy(obj, { set: function(target, key, newValue) { // 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) { } }) // 操作代理对象 objProxy.name = "james" console.log(obj) - Proxy中的receiver
- 如果源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this
const obj = { _name: "kobe", 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 = "james" const objProxy = new Proxy(obj, { set: function(target, key, newValue, receiver) { // 1.好处一: 代理对象的目的: 不再直接操作原对象 // 2.好处二: Reflect.set方法有返回Boolean值, 可以判断本次操作是否成功 /* 3.好处三: > receiver就是外层Proxy对象 > Reflect.set/get最后一个参数, 可以决定对象访问器setter/getter的this指向 */ console.log("proxy中设置方法被调用") 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 = "james" console.log(objProxy.name)
2.4 Reflect的construct用法
- 有点像
new操作符构造函数 , 相当于运行new target(...args)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 = Reflect.construct(Person, ["kobe", 24], Student) const stu = new Student("kobe", 24) console.log(stu) // Student {name: 'kobe', age: 24} console.log(stu.__proto__ === Student.prototype) // true