Reflect
概述
將Object對(duì)象的一些明顯屬于語(yǔ)言內(nèi)部的方法(比如Object.defineProperty)杈湾,放到Reflect對(duì)象上∽阜纾現(xiàn)階段喉祭,某些方法同時(shí)在Object和Reflect對(duì)象上部署觉渴,未來(lái)的新方法將只部署在Reflect對(duì)象上检痰。也就是說(shuō),從Reflect對(duì)象上可以拿到語(yǔ)言內(nèi)部的方法苇经。
修改某些Object方法的返回結(jié)果赘理,讓其變得更合理。比如扇单,Object.defineProperty(obj, name, desc)在無(wú)法定義屬性時(shí)商模,會(huì)拋出一個(gè)錯(cuò)誤,而Reflect.defineProperty(obj, name, desc)則會(huì)返回false蜘澜。
讓Object操作都變成函數(shù)行為施流。某些Object操作是命令式,比如name in obj和delete obj[name]鄙信,而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為瞪醋。
Reflect對(duì)象的方法與Proxy對(duì)象的方法一一對(duì)應(yīng),只要是Proxy對(duì)象的方法装诡,就能在Reflect對(duì)象上找到對(duì)應(yīng)的方法趟章。這就讓Proxy對(duì)象可以方便地調(diào)用對(duì)應(yīng)的Reflect方法,完成默認(rèn)行為慎王,作為修改行為的基礎(chǔ)蚓土。也就是說(shuō),不管Proxy怎么修改默認(rèn)行為赖淤,你總可以在Reflect上獲取默認(rèn)行為蜀漆。
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);
}
});
// 每一個(gè)Proxy對(duì)象的攔截操作(get、delete咱旱、has)确丢,內(nèi)部都調(diào)用對(duì)應(yīng)的Reflect方法
// 保證原生行為能夠正常執(zhí)行。添加的工作吐限,就是將每一個(gè)操作輸出一行日志鲜侥。
靜態(tài)方法
大部分與Object對(duì)象的同名方法的作用都是相同的,而且它與Proxy對(duì)象的方法是一一對(duì)應(yīng)的诸典。
Reflect.get(target, name, receiver)
Reflect.get方法查找并返回target對(duì)象的name屬性描函,如果沒(méi)有該屬性,則返回undefined狐粱。如果第一個(gè)參數(shù)不是對(duì)象舀寓,Reflect.get方法會(huì)報(bào)錯(cuò)。
如果name屬性部署了讀取函數(shù)(getter)肌蜻,則讀取函數(shù)的this綁定receiver互墓。
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
Reflect.get(myObject, 'baz') // 3
Reflect.set(target, name, value, receiver)
Reflect.set方法設(shè)置target對(duì)象的name屬性等于value。如果第一個(gè)參數(shù)不是對(duì)象蒋搜,Reflect.set會(huì)報(bào)錯(cuò)篡撵。
如果name屬性設(shè)置了賦值函數(shù)(setter)判莉,則賦值函數(shù)的this綁定receiver。
注意育谬,如果 Proxy對(duì)象和 Reflect對(duì)象聯(lián)合使用骂租,前者攔截賦值操作,后者完成賦值的默認(rèn)行為斑司,而且傳入了receiver渗饮,那么Reflect.set會(huì)觸發(fā)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
這是因?yàn)镻roxy.set的receiver參數(shù)總是指向當(dāng)前的 Proxy實(shí)例(即上例的obj)宿刮,而Reflect.set一旦傳入receiver互站,就會(huì)將屬性賦值到receiver上面(即obj),導(dǎo)致觸發(fā)defineProperty攔截僵缺。如果Reflect.set沒(méi)有傳入receiver胡桃,那么就不會(huì)觸發(fā)defineProperty攔截
Reflect.has(obj, name)
Reflect.has方法對(duì)應(yīng)name in obj里面的in運(yùn)算符。如果第一個(gè)參數(shù)不是對(duì)象磕潮,會(huì)報(bào)錯(cuò)
// 舊寫(xiě)法
'foo' in myObject // Boolean
// 新寫(xiě)法
Reflect.has(myObject, 'foo') // Boolean
Reflect.construct(target, args)
Reflect.construct方法等同于new target(...args)翠胰,這提供了一種不使用new,來(lái)調(diào)用構(gòu)造函數(shù)的方法自脯。如果Reflect.construct()方法的第一個(gè)參數(shù)不是函數(shù)之景,會(huì)報(bào)錯(cuò)。
function Greeting(name) {
this.name = name;
}
// new 的寫(xiě)法
const instance = new Greeting('張三');
// Reflect.construct 的寫(xiě)法
const instance = Reflect.construct(Greeting, ['張三']);
Reflect.getPrototypeOf(obj)
Reflect.getPrototypeOf方法用于讀取對(duì)象的proto屬性膏潮,對(duì)應(yīng)Object.getPrototypeOf(obj)锻狗。
Reflect.getPrototypeOf和Object.getPrototypeOf的一個(gè)區(qū)別是,如果參數(shù)不是對(duì)象焕参,Object.getPrototypeOf會(huì)將這個(gè)參數(shù)轉(zhuǎn)為對(duì)象轻纪,然后再運(yùn)行,而Reflect.getPrototypeOf會(huì)報(bào)錯(cuò)叠纷。
Reflect.setPrototypeOf(obj, newProto)
Reflect.setPrototypeOf方法用于設(shè)置目標(biāo)對(duì)象的原型(prototype)刻帚,對(duì)應(yīng)Object.setPrototypeOf(obj, newProto)方法。它返回一個(gè)布爾值涩嚣,表示是否設(shè)置成功崇众。
如果第一個(gè)參數(shù)是undefined或null,Object.setPrototypeOf和Reflect.setPrototypeOf都會(huì)報(bào)錯(cuò)缓艳。
如果第一個(gè)參數(shù)不是對(duì)象校摩,Object.setPrototypeOf會(huì)返回第一個(gè)參數(shù)本身,而Reflect.setPrototypeOf會(huì)報(bào)錯(cuò)阶淘。
如果無(wú)法設(shè)置目標(biāo)對(duì)象的原型(比如,目標(biāo)對(duì)象禁止擴(kuò)展)互妓,Reflect.setPrototypeOf方法返回false溪窒。
Reflect.apply(func, thisArg, args)
一般來(lái)說(shuō)坤塞,如果要綁定一個(gè)函數(shù)的this對(duì)象,可以這樣寫(xiě)fn.apply(obj, args)澈蚌,但是如果函數(shù)定義了自己的apply方法摹芙,就只能寫(xiě)成Function.prototype.apply.call(fn, obj, args)調(diào)用Function原型上定義的apply方法,采用Reflect對(duì)象可以簡(jiǎn)化這種操作宛瞄。
const ages = [11, 33, 12, 54, 18, 96];
// 舊寫(xiě)法
const youngest = Math.min.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新寫(xiě)法
const youngest = Reflect.apply(Math.min, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);
Reflect.defineProperty(target, propertyKey, attributes)
Reflect.defineProperty方法基本等同于Object.defineProperty浮禾,用來(lái)為對(duì)象定義屬性。未來(lái)份汗,后者會(huì)被逐漸廢除盈电,請(qǐng)從現(xiàn)在開(kāi)始就使用Reflect.defineProperty代替它。第一個(gè)參數(shù)不是對(duì)象杯活,就會(huì)拋出錯(cuò)誤匆帚。
Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定屬性的描述對(duì)象旁钧,將來(lái)會(huì)替代掉后者吸重。
如果第一個(gè)參數(shù)不是對(duì)象,Object.getOwnPropertyDescriptor(1, 'foo')不報(bào)錯(cuò)歪今,返回undefined嚎幸,而Reflect.getOwnPropertyDescriptor(1, 'foo')會(huì)拋出錯(cuò)誤,表示參數(shù)非法寄猩。
Reflect.isExtensible (target)
Reflect.isExtensible方法對(duì)應(yīng)Object.isExtensible鞭铆,返回一個(gè)布爾值,表示當(dāng)前對(duì)象是否可擴(kuò)展焦影。
如果參數(shù)不是對(duì)象车遂,Object.isExtensible會(huì)返回false,因?yàn)榉菍?duì)象本來(lái)就是不可擴(kuò)展的斯辰,而Reflect.isExtensible會(huì)報(bào)錯(cuò)舶担。
Reflect.preventExtensions(target)
Reflect.preventExtensions對(duì)應(yīng)Object.preventExtensions方法,用于讓一個(gè)對(duì)象變?yōu)椴豢蓴U(kuò)展彬呻。它返回一個(gè)布爾值衣陶,表示是否操作成功。
如果參數(shù)不是對(duì)象闸氮,Object.preventExtensions在 ES5 環(huán)境報(bào)錯(cuò)剪况,在 ES6 環(huán)境返回傳入的參數(shù),而Reflect.preventExtensions會(huì)報(bào)錯(cuò)蒲跨。
Reflect.ownKeys (target)
Reflect.ownKeys方法用于返回對(duì)象的所有屬性译断,基本等同于Object.getOwnPropertyNames與Object.getOwnPropertySymbols之和。
如果Reflect.ownKeys()方法的第一個(gè)參數(shù)不是對(duì)象或悲,會(huì)報(bào)錯(cuò)孙咪。
實(shí)例:使用 Proxy 實(shí)現(xiàn)觀察者模式
觀察者模式(Observer mode)指的是函數(shù)自動(dòng)觀察數(shù)據(jù)對(duì)象堪唐,一旦對(duì)象有變化,函數(shù)就會(huì)自動(dòng)執(zhí)行翎蹈。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {handler});
function handler(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
const person = observable({
name: '張三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 李四, 20