概览
Proxy
和 Reflect
是都是在ES6中引入的新对象。它们也是 vue3 实现响应式的基础。这篇文章会深入理解分析这两个重要的角色。
正文
Proxy
Proxy
对象用于创建一个对象的代理,从而实现对目标对象的访问拦截和修改(如属性查找、赋值、枚举和函数调用), 即修改这些操作的默认行为。
- 语法:
new Proxy(target, handler)
target
: 需要代理的目标对象(可以是任何类型,包括原生对象)
handler
: 一个对象,其属性是当执行一个操作时定义代理的行为的函数。
handler
实例方法
参照 MDN 文档,handler
可以如下 13 种方法
-
handler.apply(target,thisArg,args)
handler.apply
用于拦截函数的调用、call
和apply
操作,返回一个值。如果没有定义该方法,那么调用函数时会直接调用目标函数。 -
handler.construct(target,args)
用于拦截构造函数
new
操作符的调用 -
handler.defineProperty(target,key,descriptor)
handler.defineProperty
用于拦截对象Object.defineProperty()
操作,返回一个布尔值。如果返回false
或者报错,则定义属性操作无效。 -
handler.deleteProperty(target,key)
handler.deleteProperty
用于拦截对对象属性的delete
操作,返回一个布尔值。如果返回false
或者报错,则删除操作无效。 -
handler.get(target,key,receiver)
handler.get
用于拦截对象某个属性的读取操作,返回该属性的值。如果属性不存在,则返回undefined
。如果属性部署了getter
,那么getter
的this
是指向第三个参数receiver
,若参数receiver
省略,则this
默认指向target
。 -
handler.getOwnPropertyDescriptor(target,key)
handler.getOwnPropertyDescriptor
用于拦截Object.getOwnPropertyDescriptor()
操作,返回属性描述对象Descriptor
或者undefined
。 -
handler.getPrototypeOf(target)
handler.getPrototypeOf(target)
用于拦截获取对象原型,会拦截如下操作:Object.prototype.__prototype_
、Object.prototype.isPrototypeOf()
、Object.getPrototype()
、Reflect.getPrototypeOf()
和instanceof
等操作。
handler.getPrototypeOf
的返回值必须是对象或者null
,否则会报错。若target
是不可扩展的,则必须返回目标对象的原型对象。 -
handler.has()
拦截
in
操作符 -
handler.isExtensible(target)
handler.isExtensible(target)
用于拦截Object.isExtensible()
操作,返回一个布尔值,并且该布尔值与target
的isExtensible
属性值相同,否则会报错。 -
handler.ownKeys(target)
handler.ownKeys
方法用于拦截对对象自身属性的读取操作返回一个数组;如Object.getOwnPropertyNames()
和Object.getOwnPropertySymbols()
,Object.keys()
和for...in
等 -
handler.preventExtensions(target)
handler.preventExtensions
方法用于拦截Object.preventExtensions()
,该方法必须返回一个布尔值,否则会被自动转为布尔值。 -
handler.set(target,key,value,receiver)
handler.set
用于拦截对象某个属性的赋值操作,返回一个布尔值。如果赋值成功,则返回true
;否则,返回false
。如果属性部署了setter
,那么setter
的this
是指向第四个参数receiver
,若参数receiver
省略,则this
默认指向target
。 -
handler.setPrototypeOf(target,prototype)
handler.setPrototypeOf
方法主要用于拦截Object.setPrototypeOf()
方法,返回一个布尔值。如果设置成功,则返回true
;否则,返回false
。
Reflect
Reflect
对象是ES6 为了操作对象而提供的新 API,它提供了拦截 JavaScript 操作对象的方法。这些方法与Proxy
的handler
方法相对应。
Reflect
静态方法
Reflect
对象一共有 13 个静态方法,大部分与Object
对象的同名方法的作用相同,并且与Proxy
对象的方法相对应。
-
Reflect.apply(func,thisArg,args)
Reflect.apply
方法用于调用函数,返回函数执行结果。该方法与Function.prototype.apply()
方法功能相同,但是Reflect.apply
方法的参数是func
函数、this
对象和参数数组,返回值是函数执行结果。 -
Reflect.construct(target,args)
Reflect
是一个静态类,不能实例化,也不能被继承,因此无法使用new
操作符。但是Reflect.construct
方法可以用来调用构造函数。
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const p = Reflect.construct(Person, ["Bob", 18]);
console.log(p); // Person {name: '张三', age: 18}
//等价于
const p = new Person("Bob", 18);
Reflect.get(target,name,receiver)
Reflect.get
用于查找target
对象的name
属性,返回该属性的值。如果name
属性不存在,则返回undefined
。如果target
的name
属性部署了getter
,那么getter
的this
是指向第三个参数receiver
,若参数receiver
省略,则this
默认指向target
。
js
const studentA = {
name: "Bob",
age: 18,
get info() {
return `${this.name} is ${this.age}`;
},
};
const studentB = {
name: "Alice",
age: 19,
};
console.log(Reflect.get(studentA, "info")); // Bob is 18
console.log(Reflect.get(studentA, "info", studentB)); // Alice is 19
Reflect.set(target,name,value,receiver)
Reflect.set
用于设置target
对象的name
属性等于value
。如果name
属性设置了setter
,则setter
的this
绑定receiver
。
js
const studentA = {
name: "Bob",
set setSex(value) {
this.sex = value;
},
};
const studentB = {
name: "Alice",
};
Reflect.set(studentA, "setSex", "male");
console.log(studentA.sex); // male
Reflect.set(studentA, "setSex", "female", studentB);
console.log(studentA.sex, studentB.sex); // male,female
Reflect.defineProperty(target,name,desc)
Reflect.defineProperty
方法基本等同于Object.defineProperty
,返回一个布尔值。如果操作失败,会抛出一个错误。Object.defineProperty
会逐渐被废弃。
另外Reflect.defineProperty
的第一个参数必须是对象,否则会抛出错误。
js
Reflect.defineProperty({}, "name", {
value: "Bob", // 值
writable: true, // 是否可写
enumerable: true, // 是否可枚举
configurable: true, // 是否可配置
});
-
Reflect.deleteProperty(target,name)
Reflect.deleteProperty
方法用于删除对象的属性,返回一个布尔值。如果删除成功,返回true
;如果删除失败,返回false
。同理,若
Reflect.deleteProperty
的第一个参数不是对象,也会抛出错误。 -
Reflect.has(target,name)
Reflect.has
方法对应name in target
的操作。如果target
对象继承了name
属性,返回true
;否则返回false
。相比name in obj
写法,Reflect.has
是函数的形式,且心智负担很小。 -
Reflect.ownKeys(target)
Reflect.ownKeys
方法用于返回对象的所有属性,包括不可枚举属性。它的返回值是一个数组,数组的成员是字符串,每个字符串对应一个属性名。
Reflect.ownKeys
基本等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
,但它是一个静态方法,不需要实例化。同理,若
Reflect.ownKeys
的第一个参数不是对象,也会抛出错误。
js
Reflect.ownKeys({
name: "Bob",
age: 18,
[Symbol("symbol")]: "symbol",
});
// ['name', 'age', Symbol(symbol)]
Reflect.isExtensible(target)
Reflect.isExtensible
方法对应Object.isExtensible
方法,返回一个布尔值,表示当前对象是否可扩展。
同理,若Reflect.isExtensible
的第一个参数不是对象,也会抛出错误。
在某些情况下,Reflect.isExtensible(obj)
会返回false
,如对象obj
被Object.preventExtensions(obj)
、Object.seal(obj)
、Object.freeze(obj)
等处理后,会返回false
。另外,JS 某些内置对象也是不可扩展对象,如Math
、JSON
。
总结如下:
方法/状态 | isExtensible | 能否添加属性 | 能否删除属性 | 能否修改值 |
---|---|---|---|---|
默认对象 | ✅ true | ✅ 是 | ✅ 是 | ✅ 是 |
Object.preventExtensions() |
❌ false | ❌ 否 | ✅ 是 | ✅ 是 |
Object.seal() |
❌ false | ❌ 否 | ❌ 否 | ✅ 是 |
Object.freeze() |
❌ false | ❌ 否 | ❌ 否 | ❌ 否 |
-
Reflect.preventExtensions(target)
Reflect.preventExtensions(target)
对应Object.preventExtension
方法,用于让一个对象变为不可扩展,返回一个布尔值,表示是否操作成功。 -
Reflect.getOwnPropertyDescriptor(target,name)
Reflect.getOwnPropertyDescriptor(target,name)
对应Object.getOwnPropertyDescriptor
方法,用于获取对象的属性描述符,返回一个PropertyDescriptor
对象。
js
let p={}
Reflect.defineProperty(p,'name',{
value:"Jinus",
enumerable:false,
writable:true,
configurable:true,
})
console.log(Reflect.getOwnPropertyDescriptor(p,'name'))
// {value:"Jinus",enumerable:false,writable:true,configurable:true,}
console.log(Reflect.getOwnPropertyDescriptor(p,'age'))
// undefined
-
Reflect.getPrototypeOf(target)
Reflect.getPrototypeOf
方法对应Object.getPrototypeOf
,用于读取对象的__proto__
属性,返回对象的原型。若参数target
不是对象,Reflect.getPrototypeOf
会报错,而Object.getPrototypeOf
会将target
转为对象,再获取该对象的__prototype
。 -
Reflect.setPrototypeOf(target,prototype)
Reflect.setPrototypeOf
方法对应Object.setPrototypeOf
,用于设置对象的原型prototype
,返回一个布尔值,表示是否操作成功。若参数
target
不是对象,Reflect.setPrototypeOf
会报错,而Object.setPrototypeOf
会返回target
自身。特殊情况下,target
为null
或undefined
,Reflect.setPrototypeOf
会何Object.setPrototypeOf
都会报错。