Reflect 语法及使用
Reflect
是 ES6 中用来操作对象的 API;
在 ES6 之前我们只能使用Object.method
来访问原生对象的内部方法,ES6 语法将大部分内部方法加到了Reflect
上,所以我们现在可以使用Reflect.defineProperty
这样的形式来调用内部方法;
这个方法还修改了一些内部方法的返回值,使用Object.defineProperty
来定义一个无法配置的属性时,会抛出错误,而调用Reflect.defineProperty
会返回false
;
ES5 使用in | delete
保留字来判断或者删除属性,这样的写法很 JS 但很不 OO,过于命令式,在 ES6 后可以使用Reflect.has | Reflect.deleteProperty
来实现,这很coooooooool
;
Reflect
对象的方法可以在Proxy
对象的方法中使用,Proxy
对象总共有 13 种代理方法,在Reflect
中都有对应的方法;我们在代理方法中可以直接调用Reflect.method
来调用默认的原生对象方法,完成默认的对象操作,再添加自己的操作;
当
Proxy
和Reflect
对象一起使用时,为了避免重复递归触发当前代理方法,调用Reflect
方法不会再触发当前的方法;比如在handler
里设置了set
代理,那在set
代理中再调用Reflect.set
方法不会再次触发代理的set
方法;
由于Reflect
和Proxy
对象的方法都一样的,参数也差不多,这里就不介绍太多,罗列一下方法名和参数即可;
1
2
3
4
5
6
7
8
9
10
11
12
13
Reflect.get(target, name, receiver);
Reflect.set(target, name, value, receiver);
Reflect.has(target, name);
Reflect.apply(target, thisArg, args);
Reflect.ownKeys(target);
Reflect.construct(target, args);
Reflect.isExtensible(target);
Reflect.defineProperty(target, name, desc);
Reflect.deleteProperty(target, name);
Reflect.getPrototypeOf(target);
Reflect.setPrototypeOf(target, prototype);
Reflect.preventExtensions(target);
Reflect.getOwnPropertyDescriptor(target, name);
参数中
target
必须是一个对象,否则会直接报错;receiver
表示,当读取的属性部署了get | set
函数,函数的this
绑定到receiver
;
实例
Reflect.get(target, name, receiver)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
}
};
var myReceiverObject = {
foo: 4,
bar: 4
};
Reflect.get(myObject, 'baz', myReceiverObject); // 8
let p = new Proxy(
{},
{
get(t, k, r) {
console.log(t, k, r);
return Reflect.get(t, k, r) + 1;
}
}
);
从上述代码可以看出,在get
函数中,this
被改写成myReceiverObject
了,这就是第三个参数的作用了;
Reflect.set(target, name, value, receiver)
和get
方法类似,多传入了指定的属性值;当和Proxy
对象一起使用时,在Proxy
方法内部使用了set
方法并传入了第四个参数receiver
,会额外触发Proxy.defineProperty
方法,看一个例子;
1
2
3
4
5
6
7
8
9
10
11
12
13
let o = {};
let p = new Proxy(o, {
set(t, k, v, r) {
console.log('set');
return Reflect.set(t, k, v, r);
},
defineProperty(t, k, p) {
console.log('define');
}
});
p.a = 10;
// set
// define
这是因为传入了receiver
参数改变了set
函数内部this
的指向,上述代码指向了Proxy
实例p
,所以其实是在给实例p
做定义属性操作,此时就会触发defineProperty
的代理;如果不传入第四个参数,这时就是直接对第一个参数target
指向的o
对象操作了,就不会触发代理了;
Reflect.has(obj, name)
该方法对应 ES5 的in
运算符,在Proxy
那篇也提到过,in
运算符不区分是否是在原型上的属性,这个方法也一样,我们可以用这个方法来判断属性是否存在对象上(不论是在对象本身还是在对象的原型上);
1
2
3
4
5
6
7
8
9
10
11
let o = {
a: 1
};
let p = Object.create(o);
p.b = 2;
// ES5
'a' in p;
'b' in p;
// ES6
Reflect.has(p, 'a');
Reflect.has(p, 'b');
Reflect.deleteProperty(obj, name)
用于删除对象的方法;返回布尔值,删除成功或要删除的属性不存在,都返回true
;删除失败,属性依然存在返回false
;
1
2
3
4
5
let o = { a: 1 };
// ES5
delete o.a;
// ES6
Reflect.deleteProperty(o, 'a');
Reflect.construct(target, args)
等同于new Class()
,是创建构造函数的方法;
1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}
// ES5
let p = new Person('steven', 24);
// ES6
let q = Reflect.construct(Person, ['stevent', 24]);
Reflect.getPrototypeOf(obj)
用于读取对象的__proto__
属性,对应Object.getPrototypeof
方法;毕竟浏览器环境才有可能配置对象的原型属性;另外用Object.getPrototypeof
方法会将参数转化成对象,但是Reflect.getPrototypeOf
不会强制转换,会先进行类型检测,非对象类型直接抛出异常。
Reflect.setPrototypeOf(obj, newProto)
用于设置原型,返回布尔值,表示是否设置成功;如果无法设置目标对象的原型,就会返回false
;
Reflect.apply(func, thisArg, args)
用于绑定this
对象后执行第一个给定的参数;我们平常使用apply | call
方法来绑定函数的this
,现在我们可以使用这个方法来替代了;一个比较直观的例子:
1
2
3
4
5
6
7
8
let a = [1, 2, 3];
// ES5
let min = Math.min.apply(Math, a);
let max = Math.max.apply(Math, a);
let type = Object.prototype.toString.call(a);
// ES6
let min1 = Reflect.apply(Math.min, null, a);
let type1 = Reflect.apply(Object.prototype.toString, a, []);
那按照 ES6 的写法,我们可以封装一个APPLY
函数,用于改变函数内部this
值并执行;
1
2
3
function APPLY(method, _this, ...args) {
return Reflect.apply(method, _this, args);
}
Reflect.defineProperty(target, propertyKey, attributes)
用来显式定义对象属性,等同于Object.defineProperty
;
与Proxy.defineProperty
方法共用,例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
let p = new Proxy(
{},
{
defineProperty(t, k, d) {
console.log(t, k, d);
return Reflect.defineProperty(t, k, d);
}
}
);
p.a = 10;
// {} "a" {value: 10, writable: true, enumerable: true, configurable: true}
p.a;
// 10
Reflect.getOwnPropertyDescriptor(target, propertyKey)
用于获取对象属性的描述对象;几乎和Object.getOwnPropertyDescriptor
功能一致,除了Reflect
会先检测第一个参数的类型外;
Reflect.isExtensible(target)
用于判断对象是否可扩展;返回布尔值;和Object.isExtensible
功能一致;
Reflect.preventExtensions(target)
用于让一个对象变为不可扩展的对象;返回布尔值,表示是否操作成功;与Object.preventExtensions
功能一致;
Reflect.ownKeys(target)
用于返回对象自身所有属性,包括属性名是Symbol
类型的,不包括原型上的属性;基本等同于Object.getOwnPropertyNames + Object.getOwnPropertySymbols
的并集;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)]
使用 Proxy 实现观察者模式
在defineProperty 使用中,写了一个使用Object.definePropery
方法实现观察者模式 demo 的代码片段;这一小节我们会使用Proxy
来实现观察者模式;
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 发布者的构造函数
* @author: youzi
*/
class Publisher {
/**
* 发布者的构造函数
* @param {object} data 需要绑定发布和订阅的对象
* @author: youzi
*/
constructor(data) {
this.data = data;
this.watcherList = {};
// this.pushWatcherList();
this.watchData();
}
/**
* 添加观察者实例至观察者列表中
* @param {Watcher} watcher 观察者实例
* @author: youzi
*/
pushWatcherList(watcher) {
let key = watcher.key;
// let fun = watcher.method;
if (!this.watcherList[key]) {
this.watcherList[key] = [];
}
this.watcherList[key].push(watcher);
}
/**
* 创建Proxy实例,目标对象是data
* @return {Proxy} Proxy实例
* @author: youzi
*/
watchData() {
const that = this;
const handler = {
set(t, k, v, r) {
// 默认的set行为
const result = Reflect.set(t, k, v, r);
// 触发观察者列表中的方法
that.watcherList[k].map(watcher => {
watcher.method(k, v);
});
return result;
}
};
this.$data = new Proxy(this.data, handler);
}
/**
* 代替原生的set方法,规定只能使用这种方法来set对象的某个值
* @param {string} key 对象的key
* @param {any} val 对象的value
* @author: youzi
*/
setData(key, val) {
this.$data[key] = val;
}
}
/**
* 订阅者的构造函数
* @author: youzi
*/
class Observer {
/**
* 订阅者的构造函数
* @param {string} key 订阅对象的key
* @param {function} method 发布者发布消息后,订阅者的响应函数
* @param {Publisher} publisher 绑定的发布者实例
* @author: youzi
*/
constructor(key, method, publisher) {
this.key = key;
this.method = method;
this.publisher = publisher;
publisher.pushWatcherList(this);
}
}
const data = {
a: 1,
b: 2,
c: {
d: 3
}
};
let p = new Publisher(data);
const fun = (k, v) => {
console.log(k, v);
};
let w1 = new Observer('a', fun, p);
let w11 = new Observer('a', fun, p);
let w2 = new Observer('d', fun, p);
p.setData('a', 10);
p.setData('d', 12);
总的来说,观察者模式如果使用Proxy
来实现,我们需要返回的是Proxy
实例,并且只有对实例操作才会使得set
代理函数生效;上面的代码中,我们需要显示调用对象赋值方法setData
才会触发代理,直接对data
进行操作是无效的;
总结
Reflect
对象作用就是用来代替Object
的某些方法,所以都在ES5中有源函数可以查到的;另外在Proxy
代理函数中用到的很多,因为这俩对象的方法都是一一对应的,参数也一样,我们一般不会去直接替代对象的原生方法,而是选择在原生方法的基础上添加自己写的方法,所以这时使用Reflect
就很方便了。