基本概念
Reflect
是一个内置的对象,它提供拦截 JavaScript
操作的方法,这些方法与 处理器对象(Proxy)
的方法相同。Reflect
不是一个函数对象,不能通过 new
运算符对其进行调用,或者将 Reflect
对象作为一个函数来调用,Reflect
的所有属性和方法都是静态的
Reflect
对象可以拿到语言内部的方法- 在现阶段
Reflect
的方法基本和Object
行为一致,未来的新方法将只部署在Reflect
对象上 Reflect
方法针对Object
的某些方法进行了优化
let obj = {name:'obj'};
Object.freeze(obj); // 设置 obj 不可修改
Object.defineProperty(obj, 'info', {value:'info'});
// Uncaught TypeError: Cannot define property info, object is not extensible
/// Object.defineProperty 返回的信息是个 TypeError
...
Reflect.defineProperty(obj, 'info', {value: 'info'});
// false
/// Reflect.defineProperty 则会直接告知false,从直观感觉上,对象冻结,不可修改,直接告知失败就好,比返回个 TypeError 更好
Reflect
还把某些Object
的命令操作改成函数行为- 比如 in,delete,对应的 Reflect 方法为 Reflect.has 和 Reflect.deleteProperty
'name' in obj;
// true
Reflect.has(obj, 'name');
// true
/// 这种改变会使代码看起风格更为统一、规范,代码的可读性也提高了不少
Reflect静态方法
Reflect.apply(target, thisArgument, argumentsList)
通过指定的参数列表发起对目标(target)函数的调用,与Function.prototype.apply
方法类似- 参数
target
目标函数;thisArgument
target 函数调用时绑定的 this 对象;argumentsList
target 函数调用时传入的实参列表,该参数是一个类数组对象
- 异常: target对象不可调用,抛出 TypeError
- 参数
let info = function (info = '-', age = 23) {
console.log(this.name + ' ' + info + ' ' + age)
};
Function.prototype.apply.call(info, {name:'rede'}, ['info', 30]); // rede info 30
Reflect.apply(info, {name:'rede'}, ['info', 30]); // rede info 30
从上面可以看出 Reflect.apply
的写法更加简洁明了,第一个参数是需要调用的函数,第二个参数是准备调用函数的对象,第三个参数是被调用函数传入参数
/// 上面的简洁也主要是针对 Function.prototype.apply.call 而言,如果按下面的写法,这种写法更为简洁
info.apply({name:'rede'}); // rede - 23
info.apply({name:'rede'}, ['info', 29]); // rede info 29
/// Reflect.apply 的写法主要的好处,应该是代码风格的统一,而且易读性更高
Reflect.apply(info, {name:'rede'}, ['info', 30]);
Reflect.construct(target, argumentsList[, newTarget])
行为有点像new
操作符,相当于运行new target(...args)
- 参数
target
被运行的目标构造函数argumentsList
类数组,目标构造函数调用时的参数newTarget
作为新创建对象的原型对象的 constructor 属性
- 异常: 如果 target 或者 newTarget 不是构造函数,抛出 TypeError 异常
- 参数
/// 当作普通 new 操作符使用
function Greeting(name) {
this.name = name;
}
const ins = new Greeting('张三');
const ins2 = Reflect.construct(Greeting, ['李四']);
ins.name; // 张三
ins2.name; // 李四
...
/// argumentsList 支持可变的参数来调用构造函数,效果和使用 new 操作符搭配对象展开符调用一样
new Array(1,2,3,4,5); // [1,2,3,4,5]
new Array([1,2,3,4,5]); // [[1,2,3,4,5]]
new Array(...[1,2,3,4,5]); // [1,2,3,4,5]
Reflect.construct(Array, [1,2,3,4,5]); // [1,2,3,4,5]
...
/// 作为新创建对象的原型对象
function someInfo () {}
someInfo.prototype.info = 'info';
let result2 = Reflect.construct(Array, [], someInfo);
result2.info; // info
// 控制台查看
result2.__proto__;
...
info: "info"
constructor: ƒ someInfo()
Reflect.defineProperty(target, propertyKey, attributes)
基本等同于Object.defineProperty
方法,唯一不同是返回Boolean
值,可以精确添加或修改对象上的属性- 参数
target
目标对象propertyKey
要定义或修改的属性名称;attributes
要定义或修改属性的描述
- 返回值: 布尔值,表示是否被成功定义
- 异常: 如果目标不是Object,抛出TypeError
- 参数
let obj = {};
Object.defineProperty(obj, 'name', {
value:'name'
});
// {name: "name"}
Object.getOwnPropertyDescriptor(obj, 'name');
// {value: "name", writable: false, enumerable: false, configurable: false}
...
/// Reflect.defineProperty 使用方法和 Object.defineProperty 一致
Reflect.defineProperty(obj, 'info', {
value:'info'
})
// true
Object.getOwnPropertyDescriptor(obj, 'info');
// {value: "info", writable: false, enumerable: false, configurable: false}
Reflect.defineProperty
和 Object.defineProperty
使用方式一致,区别在于Object.defineProperty
返回一个对象,如果属性没有被成功定义,抛出一个 TypeError
,使用这个方法定义属性时,可以通过 try...catch
去捕获其中任何的错误
Reflect.defineProperty
方法,返回一个Boolean
说明该属性是否被成功定义,使用这个方法定义属性时,可以通过 if...else
处理相关逻辑
Reflect.deleteProperty(target, propertyKey)
删除属性,作用与 delete 操作符基本相同- 参数
target
目标对象propertyKey
属性名
- 返回值:一个 Boolean 类型的对象指示是否存在此属性
- 异常:如果目标对象并非 Object 类型,抛出 TypeError
- 参数
let obj = {name: 'name', info:'info'};
delete obj.name; // true
Reflect.deleteProperty(obj, 'info'); // true
Reflect.get(target, propertyKey[, receiver])
从一个对象中取值- 参数
target
取值的目标对象;propertyKey
需要获取值的键值;receiver
如果 propertyKey 部署了读取函数(getter)
,则读取函数中的 this 将临时指向receiver
的作用域
- 返回值: 对应属性值
- 参数
// target值必须是对象
Reflect.get(1, 'a'); // Uncaught TypeError: Reflect.get called on non-object
...
// Object
let obj = {x:1, y:2}
Reflect.get(obj, 'x'); // 1
...
// Array
let arr = [1, 2, 3];
Reflect.get(arr, 0); // 1 数组也是对象,此时的写法,相当于获取 arr[0]
...
// sum getter
let myObj = {
num1: 1,
num2: 2,
get sum () {
return this.num1 + this.num2
}
}
Reflect.get(myObj, 'sum'); // 3
Reflect.get(myObj, 'sum', {num1: 2, num2: 2}); // 4 此时 sum 中 this 指向的是传入对象
Reflect.set(target, propertyKey, value[, receiver])
以函数的形式,给对象上设置属性- 参数
target
目标对象propertyKey
设置的属性名称value
设置的属性值;receiver
如果 propertyKey 部署了赋值函数(setter)
,则赋值函数中的this
将临时指向receiver
的作用域
- 返回值: 返回一个 Boolean 值表明是否成功设置属性
- 参数
// Object
let obj = {};
Reflect.set(obj, 'info', 'name');
obj.info; // name
...
// Array
let arr = [];
Reflect.set(arr, 0, 'info');
arr[0]; // info
...
// setter 属性设置
let myObj = {
foo: 4,
bar: '',
set info (val) {
this.bar = val === this.foo;
}
}
myObj.bar; // ''
Reflect.set(myObj, 'info', 3); // 没有设置 receiver,对应的 this 还是指向 myObj,所以对应的 this.foo 为 4
myObj.bar; // false
Reflect.set(myObj, 'info', 3, {foo:3}); // 设置了 receiver,对应的 this 指向到这个对象中,所以对应的 this.foo 为 3
myObj.bar; // true
是否设置了 receiver 会影响 Proxy 的拦截行为
let myObj = {
foo: 4,
bar: '',
set info (val) {
this.bar = val === this.foo;
}
}
let bar = new Proxy(myObj, {
set(target, key, value, receiver) {
console.log('触发了set', target, receiver);
Reflect.set(target, key, value)
},
defineProperty(target, key, attribute) {
console.log('触发了defineProperty');
Reflect.defineProperty(target, key, attribute);
}
});
bar.info = 3;
// 触发了set {foo: 4, bar: ""} Proxy {foo: 4, bar: ""}
...
bar.foo = 3;
// 触发了set {foo: 4, bar: false} Proxy {foo: 4, bar: false}
...
bar = new Proxy(myObj, {
set(target, key, value, receiver) {
console.log('触发了set', target, receiver);
Reflect.set(target, key, value, receiver); // 设置了receiver
},
defineProperty(target, key, attribute) {
console.log('触发了defineProperty');
Reflect.defineProperty(target, key, attribute);
}
});
bar.info = 3;
// 触发了set {foo: 3, bar: false} Proxy {foo: 3, bar: false}
// 触发了set {foo: 3, bar: false} Proxy {foo: 3, bar: false}
// 触发了defineProperty
...
bar.foo = 5;
// 触发了set {foo: 3, bar: true} Proxy {foo: 3, bar: true}
// 触发了defineProperty
从上面对比我们能看出来如果 Reflect.set
没有设置 receiver
只会触发一次 Proxy
中的 set
拦截,而且不管对应属性是不是通过 setter 进行设置(这是 Proxy 的行为,都会被拦截)
但是如果设置了 receiver
,由 set 数设置的属性(set info
)则会触发两次 set
拦截,并触发 defineProperty
,普通属性则只会触发一次 set
拦截和一次defineProperty
这是因为普通属性赋值时被拦截器的 set
拦截,在 set拦截器
中因为 target
是目标对象(myObj)。如果 Reflect.set
不传入 receiver
就相当于直接修改目标对象的属性值,对目标对象的修改(myObj)自然不会影响到代理对象(bar)的拦截器行为
但当使用了 recevier
参数,由于 set拦截器
中的 recevier
默认就是代理对象(bar)。如果再有相关属性赋值的行为,就会触发 proxy 的相关拦截行为。
上例中 bar.foo = 5;
是针对非 set 属性赋值,流程上会先触发 拦截器的 set,而 set 又会触发 Reflect.set,进而触发拦截器的defineProperty,所以通过在最终执行时,只触发了拦截器上的defineProperty在此操作中我们是调用了Reflect.set方法触发了代理对象的属性修改行为,进而触发了defineProperty拦截器
对info进行操作时会有两次set输出,是因为我们在其方法内部调用了this.foo,相当于再次触发了一个新值变动,所以会有两次set和一次的defineProperty输出
let obj = {x:1};
let handler = {
set(target, key, value, receiver) {
console.log('触发了set');
Reflect.set(target, key, value, receiver);
},
defineProperty (target, propKey, value, receiver) {
console.log('defineProperty');
Reflect.defineProperty(target, propKey, value);
}
};
let proxy = new Proxy(obj, handler);
Reflect.set(obj, 'x', 3); // 第一次调用
// true
...
Reflect.set(obj, 'x', 3, proxy); // 第二次调用
// defineProperty
// false
...
Reflect.defineProperty(proxy, 'y', {value:2}); // 第三次调用
// defineProperty
// false
...
Reflect.defineProperty(obj, 'z', {value:2}); // 第四次调用
// true
- 第一次调用时没有传入receiver,所以相关的赋值并不会传到proxy上,进而也不会触发相关拦截
- 第二次调用时传入了receiver,相关赋值也会传到proxy上,自然触发了相关拦截器
- 第三个赋值和第四个赋值就是类似其执行逻辑(赋值到拦截器上自然就触发了相关拦截器)
let obj = {x:1, z:'', set y (val) {val === 3}}
// 下面set和defineProperty都传了recevier
let handler = {
set (target, propKey, value, recevier) {
console.log('set');
Reflect.set(target, propKey, value, recevier)
},
defineProperty (target, propKey, value, recevier) {
console.log('defineProperty');
Reflect.defineProperty(target, propKey, value, recevier);
}
};
let proxy = new Proxy(obj, handler);
proxy.y = 2;
// set
proxy.x = 2;
// set
// defineProperty
// 进行如下修改
obj = {x:1, z:'', set y (val) {val === 3; this.z = true}};
proxy = new Proxy(obj, handler);
proxy.y = 2;
// set
// set
// defineProperty
在这个例子中第一次进行proxy.y
的操作因为对应的存储函数并没有其他的操作行为,所以就算是传入了recevier
信息,因为没有对proxy
的后续操作行为(如果是操作x,或者z就会形成类似把相关属性赋值给proxy
的行为会继续触发defineProperty
的拦截),一样不会触发后续的拦截器,
如我们进行修改,因为后续有触发this.z
的赋值行为,就相当于是直接操作proxy.z
,set
和 defineProperty
拦截器都走了一遍
Reflect.has(target, propertyKey)
用于检查一个对象是否拥有某个属性, 相当于in 操作符- 参数:
target
目标对象propertyKey
属性名
- 返回值: 一个 Boolean 类型的对象指示是否存在此属性
- 异常:如果目标对象并非Object 类型,抛出TypeError
- 参数:
let obj = {name:'name'};
'name' in obj; // true
Reflect.has(obj, 'name'); // true
Reflect.isExtensible(target)
判断一个对象是否可扩展 (即是否能够添加新的属性)。它与Object.isExtensible()
方法一样- 参数:
target
目标对象
- 返回值:返回一个布尔值值表明该对象是否可扩展
- 异常:如果目标不是对象,则抛出一个TypeError
- 参数:
let obj = {};
Reflect.isExtensible(obj); // true
Object.preventExtensions(obj); // 阻止新属性添加到对象
Reflect.isExtensible(obj); // false
Reflect.isExtensible
和Object.isExtensible
的区别在于,Object.isExtensible
会对目标对象进行隐示转换
Object.isExtensible(1);
// false
...
Reflect.isExtensible(1);
// Uncaught TypeError: Reflect.isExtensible called on non-object
Reflect.ownKeys(target)
返回一个由目标对象自身的属性键组成的数组- 参数:
target
目标对象
- 返回值: 由目标对象的自身属性键组成的 Array
- 异常: 如果目标对象并非Object 类型,抛出TypeError
- 参数:
这个方法相当于是Object.getOwnPropertyNames
、Object.getOwnPropertySymbols
、Object.keys
三者的合集,结果和Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
一致
let a = {name:'name'};
let mySymbol = Symbol('a');
a[mySymbol] = 'Hello!';
Reflect.defineProperty(a, 'info', {value:'info'}); // info不可枚举
Object.keys(a); // ['name'] 可枚举的属性
Object.getOwnPropertyNames(a); // ["name", "info"] 可枚举与不可枚举的属性
Object.getOwnPropertySymbols(a); // [Symbol(a)] 由Symbol定义的属性
Reflect.ownKeys(a); // ["name", "info", Symbol(a)] 可枚举,不可枚举以及Symbol定义的属性
Reflect.preventExtensions(target)
阻止新属性添加到对象,该方法与Object.preventExtensions
相似- 参数:
target
目标对象
- 返回值: 返回一个布尔值值表明该对象是否成功被设置为不可扩展
- 异常: 如果目标不是对象,则抛出一个TypeError
- 参数:
let obj = {};
Reflect.preventExtensions(obj); // true
Reflect.preventExtensions
与Object.preventExtensions
区别在于,Object.preventExtensions
会对目标对象进行隐示转换
Object.preventExtensions(1);
// 1
...
Reflect.preventExtensions(1);
// Uncaught TypeError: Reflect.preventExtensions called on non-object
Reflect.getOwnPropertyDescriptor(target, propertyKey)
基本等同于Object.getOwnPropertyDescriptor
返回给定的属性的属性描述符,如果属性不存在则返回 undefined- 参数:
target
目标对象propertyKey
待查询的属性名
- 返回值: 如果存在则返回相关属性描述,如果不存在则返回undefined
- 异常: 如果目标不是对象,则抛出TypeError
- 参数:
let obj = {name: 'name'};
Reflect.getOwnPropertyDescriptor(obj, 'name');
// {value: "name", writable: true, enumerable: true, configurable: true}
...
Reflect.getOwnPropertyDescriptor(obj, 'info');
// undefined
Reflect.getOwnPropertyDescriptor
和Object.getOwnPropertyDescriptor
的区别是,如果第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')
不报错,返回undefined
,而Reflect.getOwnPropertyDescriptor(1, 'foo')
会抛出错误,表示参数非法
Reflect.getPrototypeOf(target)
返回指定对象的原型,与Object.getPrototypeof
类似- 参数:
target
目标对象
- 返回值: 给定对象的原型。如果没有继承的属性,则返回 null
- 异常: 如果目标不是 Object,抛出一个TypeError异常
- 参数:
let num = new Number();
num.__proto__; // 非标准方法
// Number...
Object.getPrototypeOf(num);
// Number...
Reflect.getPrototypeOf(num);
// Number...
Reflect.getPrototypeOf
和Object.getPrototypeOf
的一个区别是:如果参数不是对象,Object.getPrototypeOf
会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf
会报错
Object.getPrototypeOf(1);
// Number...
...
Reflect.getPrototypeOf(1);
// Uncaught TypeError: Reflect.getPrototypeOf called on non-object
Reflect.setPrototypeOf(target, prototype)
指定对象的原型(即内部的[[Prototype]]
属性)为另一个对象或为 null- 参数:
target
目标对象prototype
对象的新原型
- 返回值: 返回一个布尔值表示原型是否设置成功
- 异常: 如果目标不是对象,则抛出一个TypeError
- 参数:
let obj = {name:'name'};
let info = {};
Object.setPrototypeOf(info, obj);
info.name; // name
Reflect.setPrototypeOf(info, obj); // 功能一致
如果目标对象无法被设置,比如禁止扩展
Object.preventExtensions(info);
Object.setPrototypeOf(info, obj); // Uncaught TypeError: #<Object> is not extensible
Reflect.setPrototypeOf(info, obj); // false
Reflect.setPrototypeOf
的行为会比较符合代码的操作习惯,因为这时的预计行为也应该是设置失败,而不是直接返回个TypeError
如果第一个参数不是对象,Object.setPrototypeOf
会返回参数本身 和Reflect.setPrototypeOf
会报错
Object.setPrototypeOf(1, {});
// 1
...
Reflect.setPrototypeOf(1, {});
// Uncaught TypeError: Reflect.setPrototypeOf called on non-object
如果第一个参数是null或者undefined
Object.setPrototypeOf(null, {}); // Uncaught TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(null, {}); // Uncaught TypeError: Reflect.setPrototypeOf called on non-object
虽然两者均报错,但是错误信息是不同的,Reflect.setPrototypeOf
的行为更为统一