Reflect
對象與Proxy
對象一樣,也是 ES6 為了操作對象
而提供的新 API。 Reflect 對象的設計目的有這樣幾個。
(1) 將 Object 對象的一些明顯屬于語言內部的方法(比如 Object.defineProperty ),放到 Reflect 對象上。現(xiàn)階段,某些方法同時在 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
}
(3) 讓 Object 操作都變成函數(shù)行為。某些 Object 操作是命令式,比如 name in obj 和 delete obj[name] ,而 Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name) 讓它們變成了函數(shù)行為。
// 老寫法
'assign' in Object // true
// 新寫法
Reflect.has(Object, 'assign') // true
(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;
}
});
上面代碼中, 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);
}
});
上面代碼中,每一個 Proxy 對象的攔截操作( get 、 delete 、 has ),內部都調用對應的 Reflect 方法,保證原生行為能夠正常執(zhí)行。添加的工作,就是將每一個操作輸出一行日志。
有了 Reflect 對象以后,很多操作會更易讀。
// 老寫法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新寫法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
Reflect
對象一共有13
個靜態(tài)方法。
上面這些方法的作用,大部分與 Object 對象的同名方法的作用都是相同的,而且它與 Proxy 對象的方法是一一對應的。下面是對它們的解釋。
Reflect.get
方法查找并返回 target 對象的 name 屬性,如果沒有該屬性,則返回 undefined 。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'bar') // 2
Reflect.get(myObject, 'baz') // 3
如果 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
如果第一個參數(shù)不是對象, Reflect.get 方法會報錯。
Reflect.get(1, 'foo') // 報錯
Reflect.get(false, 'foo') // 報錯
Reflect.set
方法設置target
對象的 name 屬性等于 value 。
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3
如果 name 屬性設置了賦值函數(shù),則賦值函數(shù)的 this 綁定 receiver 。
var myObject = {
foo: 4,
set bar(value) {
return this.foo = value;
},
};
var myReceiverObject = {
foo: 0,
};
Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1
注意,如果 Proxy 對象和 Reflect 對象聯(lián)合使用,前者攔截賦值操作,后者完成賦值的默認行為,而且傳入了 receiver ,那么 Reflect.set 會觸發(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
上面代碼中, Proxy.set 攔截里面使用了 Reflect.set ,而且傳入了 receiver ,導致觸發(fā) Proxy.defineProperty 攔截。這是因為 Proxy.set 的 receiver 參數(shù)總是指向當前的 Proxy 實例(即上例的 obj ),而 Reflect.set 一旦傳入 receiver ,就會將屬性賦值到 receiver 上面(即 obj ),導致觸發(fā) defineProperty 攔截。如果 Reflect.set 沒有傳入 receiver ,那么就不會觸發(fā) 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
如果第一個參數(shù)不是對象, Reflect.set 會報錯。
Reflect.set(1, 'foo', {}) // 報錯
Reflect.set(false, 'foo', {}) // 報錯
Reflect.has
方法對應name in obj
里面的in
運算符。
var myObject = {
foo: 1,
};
// 舊寫法
'foo' in myObject // true
// 新寫法
Reflect.has(myObject, 'foo') // true
如果 Reflect.has() 方法的第一個參數(shù)不是對象,會報錯。
Reflect.deleteProperty
方法等同于delete obj[name]
,用于刪除對象的屬性。
const myObj = { foo: 'bar' };
// 舊寫法
delete myObj.foo;
// 新寫法
Reflect.deleteProperty(myObj, 'foo');
該方法返回一個布爾值。如果刪除成功,或者被刪除的屬性不存在,返回 true ;刪除失敗,被刪除的屬性依然存在,返回 false 。
如果 Reflect.deleteProperty() 方法的第一個參數(shù)不是對象,會報錯。
Reflect.construct
方法等同于 new target(...args)
,這提供了一種不使用 new ,來調用構造函數(shù)的方法。
function Greeting(name) {
this.name = name;
}
// new 的寫法
const instance = new Greeting('張三');
// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);
如果 Reflect.construct() 方法的第一個參數(shù)不是函數(shù),會報錯。
Reflect.getPrototypeOf
方法用于讀取對象的__proto__
屬性,對應 Object.getPrototypeOf(obj) 。
const myObj = new FancyThing();
// 舊寫法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新寫法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
Reflect.getPrototypeOf 和 Object.getPrototypeOf 的一個區(qū)別是,如果參數(shù)不是對象, Object.getPrototypeOf 會將這個參數(shù)轉為對象,然后再運行,而 Reflect.getPrototypeOf 會報錯。
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 報錯
Reflect.setPrototypeOf
方法用于設置目標對象
的原型
(prototype),對應 Object.setPrototypeOf(obj, newProto) 方法。它返回一個布爾值,表示是否設置成功。
const myObj = {};
// 舊寫法
Object.setPrototypeOf(myObj, Array.prototype);
// 新寫法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0
如果無法設置目標對象的原型(比如,目標對象禁止擴展), Reflect.setPrototypeOf 方法返回 false 。
Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false
如果第一個參數(shù)不是對象, Object.setPrototypeOf 會返回第一個參數(shù)本身,而 Reflect.setPrototypeOf 會報錯。
Object.setPrototypeOf(1, {})
// 1
Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object
如果第一個參數(shù)是 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
Reflect.apply
方法等同于 Function.prototype.apply.call(func, thisArg, args)
,用于綁定 this 對象后執(zhí)行給定函數(shù)。
一般來說,如果要綁定一個函數(shù)的 this 對象,可以這樣寫 fn.apply(obj, args) ,但是如果函數(shù)定義了自己的 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, []);
Reflect.defineProperty
方法基本等同于Object.defineProperty
,用來為對象定義屬性。未來,后者會被逐漸廢除,請從現(xiàn)在開始就使用 Reflect.defineProperty 代替它。
function MyDate() {
/*…*/
}
// 舊寫法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// 新寫法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
如果 Reflect.defineProperty 的第一個參數(shù)不是對象,就會拋出錯誤,比如 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"
上面代碼中, Proxy.defineProperty 對屬性賦值設置了攔截,然后使用 Reflect.defineProperty 完成了賦值。
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');
Reflect.getOwnPropertyDescriptor 和 Object.getOwnPropertyDescriptor 的一個區(qū)別是,如果第一個參數(shù)不是對象, Object.getOwnPropertyDescriptor(1, 'foo') 不報錯,返回 undefined ,而 Reflect.getOwnPropertyDescriptor(1, 'foo') 會拋出錯誤,表示參數(shù)非法。
Reflect.isExtensible
方法對應Object.isExtensible
,返回一個布爾值
,表示當前對象是否可擴展
。
const myObject = {};
// 舊寫法
Object.isExtensible(myObject) // true
// 新寫法
Reflect.isExtensible(myObject) // true
如果參數(shù)不是對象, Object.isExtensible 會返回 false ,因為非對象本來就是不可擴展的,而 Reflect.isExtensible 會報錯。
Object.isExtensible(1) // false
Reflect.isExtensible(1) // 報錯
Reflect.preventExtensions
對應Object.preventExtensions
方法,用于讓一個對象變?yōu)?code>不可擴展。它返回一個布爾值,表示是否操作成功。
var myObject = {};
// 舊寫法
Object.preventExtensions(myObject) // Object {}
// 新寫法
Reflect.preventExtensions(myObject) // true
如果參數(shù)不是對象, Object.preventExtensions 在 ES5 環(huán)境報錯,在 ES6 環(huán)境返回傳入的參數(shù),而 Reflect.preventExtensions 會報錯。
// ES5 環(huán)境
Object.preventExtensions(1) // 報錯
// ES6 環(huán)境
Object.preventExtensions(1) // 1
// 新寫法
Reflect.preventExtensions(1) // 報錯
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)]
如果 Reflect.ownKeys() 方法的第一個參數(shù)不是對象,會報錯。
觀察者模式
(Observer mode)指的是函數(shù)自動觀察數(shù)據(jù)對象
,一旦對象有變化,函數(shù)就會自動執(zhí)行。
const person = observable({
name: '張三',
age: 20
});
function print() {
console.log( ${person.name}, ${person.age} )
}
observe(print);
person.name = '李四';
// 輸出
// 李四, 20
上面代碼中,數(shù)據(jù)對象 person 是觀察目標,函數(shù) print 是觀察者。一旦數(shù)據(jù)對象發(fā)生變化, print 就會自動執(zhí)行。
下面,使用 Proxy 寫一個觀察者模式的最簡單實現(xiàn),即實現(xiàn) observable 和 observe 這兩個函數(shù)。思路是 observable 函數(shù)返回一個原始對象的 Proxy 代理,攔截賦值操作,觸發(fā)充當觀察者的各個函數(shù)。
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;
}
上面代碼中,先定義了一個Set 集合
,所有觀察者函數(shù)都放進這個集合。然后, observable 函數(shù)返回原始對象的代理,攔截賦值操作。攔截函數(shù) set 之中,會自動執(zhí)行所有觀察者。
更多建議: