# Reflect
- 概述
- 静态方法
- 实例:使用 Proxy 实现观察者模式
# 1. 概述
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。
# 设计目的
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。
(2) 修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
2
3
4
5
6
7
8
9
10
11
12
13
14
(3) 让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
2
3
4
5
(4) Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});
2
3
4
5
6
7
8
9
上面代码中,Proxy
方法拦截target
对象的属性赋值行为。它采用Reflect.set
方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
下面是另一个例子。
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
上面代码中,每一个Proxy
对象的拦截操作(get
、delete
、has
),内部都调用对应的Reflect
方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。
有了Reflect对象以后,很多操作会更易读。
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
2
3
4
5
# 2. 静态方法
Reflect对象一共有 13 个静态方法。
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.has(target, name)
Reflect.deleteProperty(target, name)
Reflect.construct(target, args)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
Reflect.apply(func, thisArg, args)
Reflect.defineProperty(target, name, desc)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.ownKeys(target)
上面这些方法的作用,大部分与Object
对象的同名方法的作用都是相同的,而且它与Proxy
对象的方法是一一对应的。
# (1) Reflect.get(targetObj, PropKey, receiver)
查找并返回targetObj
对象的PropKey
属性,如果没有该属性,则返回undefined
。
第一个参数必须是对象,如果不是对象,会报错。
let myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
let myReceiverObject = {
foo: 4,
bar: 4,
};
console.log(Reflect.get(myObject, 'foo')) // 1
console.log(Reflect.get(myObject, 'bar')) // 2
console.log(Reflect.get(myObject, 'baz')) // 3
// Reflect.get()传入第三个参数,代表目标对象的 getter 函数中的 this
console.log(Reflect.get(myObject, 'baz', myReceiverObject)) // 8
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# (2) Reflect.set(target, name, value, receiver)
将target
对象的name
属性赋值为value
如果传入第四个参数
receiver
,则整个set
操作都是针对receiver
对象。
let obj1 = {
foo: 1,
set bar(value) {
return this.foo = value;
}
}
let obj2 = {
foo: 9
}
console.log(obj1.foo) // 1
Reflect.set(obj1, 'foo', 2);
console.log(obj1.foo) // 2
Reflect.set(obj1, 'bar', 3) // 触发 obj1 中的 setter, setter 中 this 指向 obj1
console.log(obj1.foo) // 3
Reflect.set(obj1, 'foo', 4, obj2) // 传入 obj2 后,修改的就是 obj2 了
console.log(obj1.foo) // 3 // obj1 中的 foo 未被修改
console.log(obj2.foo) // 4 obj2 中的 foo 被修改
Reflect.set(obj1, 'bar', 4, obj2) // 传入 obj2 后,setter 中 this 指向 obj2
console.log(obj1.foo) // 3 obj1 中的 foo 未被修改
console.log(obj2.foo) // 4 obj2 中的 foo 被修改
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
注意,如果 Proxy
对象和 Reflect
对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为。如果Reflect.set()
传入了第四个参数receiver
,那么Reflect.set
会触发Proxy.defineProperty
拦截。
let p = {
a: 'a'
};
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value, receiver)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
// defineProperty
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上面代码中,Proxy.set
拦截里面使用了Reflect.set
,而且传入了receiver
,导致触发Proxy.defineProperty
拦截。这是因为Proxy.set
的receiver
参数总是指向当前的 Proxy
实例(即上例的obj
),而Reflect.set
一旦传入receiver
,就会将属性赋值到receiver
上面(即obj
),导致触发defineProperty
拦截。如果Reflect.set
没有传入receiver
,那么就不会触发defineProperty
拦截。
let p = {
a: 'a'
};
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# (3) Reflect.has(target, name)
Reflect.has
方法对应name in obj
里面的in
运算符。
var myObject = {
foo: 1,
};
// 旧写法
'foo' in myObject // true
// 新写法
Reflect.has(myObject, 'foo') // true
2
3
4
5
6
7
8
9
# (4) Reflect.deleteProperty(target, name)
Reflect.deleteProperty
方法等同于delete obj[name]
,用于删除对象的属性。
const myObj = { foo: 'bar' };
// 旧写法
delete myObj.foo;
// 新写法
Reflect.deleteProperty(myObj, 'foo');
2
3
4
5
6
7
该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。
# (5) Reflect.construct(targetFunc, args)
targetFunc 必须是函数
Reflect.construct
方法等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
function Greeting(name) {
this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);
2
3
4
5
6
7
8
9
如果Reflect.construct()
方法的第一个参数不是函数,会报错。
# (6) Reflect.getPrototypeOf(target)
Reflect.getPrototypeOf
方法用于读取对象的__proto__
属性,对应Object.getPrototypeOf(obj)
。
const myObj = new FancyThing();
// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
2
3
4
5
6
7
Reflect.getPrototypeOf
和Object.getPrototypeOf
的一个区别是,如果参数不是对象,Object.getPrototypeOf
会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf
会报错。
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 报错
2
# (7) Reflect.setPrototypeOf(target, prototype)
Reflect.setPrototypeOf
方法用于设置目标对象的原型(prototype
),对应Object.setPrototypeOf(obj, newProto)
方法。它返回一个布尔值,表示是否设置成功。
const myObj = {};
// 旧写法
Object.setPrototypeOf(myObj, Array.prototype);
// 新写法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0
2
3
4
5
6
7
8
9
如果无法设置目标对象的原型(比如,目标对象禁止扩展),Reflect.setPrototypeOf
方法返回false
。
Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false
2
3
4
如果第一个参数不是对象,Object.setPrototypeOf
会返回第一个参数本身,而Reflect.setPrototypeOf
会报错。
Object.setPrototypeOf(1, {})
// 1
Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object
2
3
4
5
如果第一个参数是undefined
或null
,Object.setPrototypeOf
和Reflect.setPrototypeOf
都会报错。
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(null, {})
// TypeError: Reflect.setPrototypeOf called on non-object
2
3
4
5
# (8) Reflect.apply(target, thisArg, args)
Reflect.apply
方法等同于Function.prototype.apply.call(func, thisArg, args)
,用于绑定this
对象后执行给定函数。
一般来说,如果要绑定一个函数的this
对象,可以这样写fn.apply(obj, args)
,但是如果函数定义了自己的apply
方法,就只能写成Function.prototype.apply.call(fn, obj, args)
,采用Reflect
对象可以简化这种操作。
const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);
2
3
4
5
6
7
8
9
10
11
# (9) Reflect.defineProperty(target, name, desc)
Reflect.defineProperty
方法基本等同于Object.defineProperty
,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty
代替它。
function MyDate() {
/*…*/
}
// 旧写法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// 新写法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
2
3
4
5
6
7
8
9
10
11
12
13
如果Reflect.defineProperty
的第一个参数不是对象,就会抛出错误,比如Reflect.defineProperty(1, 'foo')
会报错。
这个方法可以与Proxy.defineProperty
配合使用。
const p = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log(descriptor);
return Reflect.defineProperty(target, prop, descriptor);
}
});
p.foo = 'bar';
// {value: "bar", writable: true, enumerable: true, configurable: true}
p.foo // "bar"
2
3
4
5
6
7
8
9
10
11
上面代码中,Proxy.defineProperty
对属性赋值设置了拦截,然后使用Reflect.defineProperty
完成了赋值。
# (10) Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getOwnPropertyDescriptor
基本等同于Object.getOwnPropertyDescriptor
,用于得到指定属性的描述对象,将来会替代掉后者。
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
// 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新写法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
2
3
4
5
6
7
8
9
10
11
Reflect.getOwnPropertyDescriptor
和Object.getOwnPropertyDescriptor
的一个区别是,如果第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')
不报错,返回undefined
,而Reflect.getOwnPropertyDescriptor(1, 'foo')
会抛出错误,表示参数非法。
# (11) Reflect.isExtensible(target)
Reflect.isExtensible
方法对应Object.isExtensible
,返回一个布尔值,表示当前对象是否可扩展。
const myObject = {};
// 旧写法
Object.isExtensible(myObject) // true
// 新写法
Reflect.isExtensible(myObject) // true
2
3
4
5
6
7
如果参数不是对象,Object.isExtensible
会返回false
,因为非对象本来就是不可扩展的,而Reflect.isExtensible
会报错。
Object.isExtensible(1) // false
Reflect.isExtensible(1) // 报错
2
# (12) Reflect.preventExtensions(target)
Reflect.preventExtensions
对应Object.preventExtensions
方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
var myObject = {};
// 旧写法
Object.preventExtensions(myObject) // Object {}
// 新写法
Reflect.preventExtensions(myObject) // true
2
3
4
5
6
7
如果参数不是对象,Object.preventExtensions
在 ES5 环境报错,在 ES6 环境返回传入的参数,而Reflect.preventExtensions
会报错。
// ES5 环境
Object.preventExtensions(1) // 报错
// ES6 环境
Object.preventExtensions(1) // 1
// 新写法
Reflect.preventExtensions(1) // 报错
2
3
4
5
6
7
8
# (13) Reflect.ownKeys(target)
Reflect.ownKeys
方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果Reflect.ownKeys()方法的第一个参数不是对象,会报错。
# (3) 实例:使用 Proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
比如,以下代码是需求:
const person = observable({ // 定义可观察的目标
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print); // 定义观察者
person.name = '李四';
// print() 观察到变化,执行函数,输出 '李四, 20'
2
3
4
5
6
7
8
9
10
11
12
13
上面代码中,数据对象person
是观察目标,函数print
是观察者。一旦数据对象发生变化,print
就会自动执行。
下面,使用 Proxy
写一个观察者模式的最简单实现,即实现observable
和observe
这两个函数。思路是observable
函数返回一个原始对象的 Proxy
代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
2
3
4
5
6
7
8
9
10
11
上面代码中,先定义了一个Set
集合,所有观察者函数都放进这个集合。然后,observable
函数返回原始对象的代理,拦截赋值操作。拦截函数set
之中,会自动执行所有观察者。