Reflect 是ES6中新增的特性。它是一個普通對象,下面有13個靜態方法(enumerate
在最終的發佈版中被移除),能夠再全局下訪問。它不能當作函數調用,也不能夠用new
操做符生成一個實例。javascript
首先來講,Reflect的提出是爲了整合以前JS中存在的一些不太合理的地方。vue
1)更加有用的返回值java
Object.getOwnPropertyNames(Reflect) // ["defineProperty", "deleteProperty", "apply", "construct", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"]
功能跟Function.prototype.apply
相似。git
var a = [1,2,3]; Math.max.apply(null, a);// 3 Reflect.apply(Math.max, null, a); // 3
Reflect.preventExtensions用於讓一個對象變爲不可擴展。它返回一個布爾值,表示是否操做成功。
Reflect.isExtensible表示當前對象是否可擴展, 返回一個布爾值。es6
var o = {}; Reflect.isExtensible(o);// true Object.isExtensible(o);// true Object.freeze(o); Reflect.isExtensible(o);// false Object.isExtensible(o);// false // 這兩方法的區別 Object.isExtensible(1) // false Reflect.isExtensible(1) // 報錯 var newO = {}; Reflect.isExtensible(newO);// true Reflect.preventExtensions(newO); // true 返回true,表示該操做成功 Reflect.isExtensible(newO);// false
設置和獲取對象屬性, 這兩個方法還容許接受一個reciever,用於重定義setter和getter方法的上下文。github
var o = {}; Reflect.set(o, 'key', 'value'); console.log(o); // {key: "value"} Reflect.get(o, 'key'); // "value" Reflect.get(o, 'nokey'); // undefined
下面演示一下receiver的使用方法面試
var o = { name: 'O' } Object.defineProperty(o, 'sayHi', { get(){ return 'hi, I am ' + this.name } }) o.sayHi; // "hi, I am O" var receiver = { name: 'receiver' } // 下面是關鍵, 看好咯~ Reflect.get(o, 'sayHi', receiver); // "hi, I am receiver" // 下面試驗了下Proxy的用法,能夠忽略 var p = new Proxy(o, { get(o, k, p){ return o[k] ? Reflect.get(o, k, receiver) : undefined; }, set(o, k, v, p){ v += +new Date(); Reflect.set(o, k, v, p) } }); p.sayHi; // "hi, I am receiver" p.t = 'time:'; p.t; // "time:1530865528713"
Reflect.ownKeys方法用於返回對象的全部屬性數組。
這個數組的排序是根據: 先顯示數字, 數字根據大小排序,而後是 字符串根據插入的順序排序, 最後是symbol類型的key也根據插入插入順序排序。
出現這種排序是由於,你給一個對象屬性賦值時候, 對象的key的排序規則就是先數字, 在字符串, 最後是symbol類型的數據。數組
Reflect.ownKeys(JSON); // ["parse", "stringify", Symbol(Symbol.toStringTag)] Object.getOwnPropertyNames(JSON); // ["parse", "stringify"] Object.getOwnPropertySymbols(JSON); // [Symbol(Symbol.toStringTag)]
用於判斷對象是否具備某個屬性安全
Reflect.has(JSON, 'parse'); // true Reflect.has(JSON, 'nokeyxx'); // false Reflect.has(1, 'parse');// Uncaught TypeError: Reflect.has called on non-object
功能相似於new
操做符app
var a = new Array(3); console.log(a); // [empty × 3] var b = Reflect.construct(Array, [3]) console.log(b); // [empty × 3]
Reflect.defineProperty 對應於Object.DefineProperty;
Reflect.deleteProperfy 對應於 delete
語句;
var o = {}; Object.defineProperty(o, 'sayHi', { configurable: true, get(k){ return this[k] ? "Hi, I am " + this[k] : 'I have no name'; } }); o.sayHi;// "I have no name" Reflect.defineProperty(o, 'sayHello', { configurable: true, get(k){ return this[k] ? "Hi, I am " + this[k] : 'I have no name'; } }) o.sayHello; // "I have no name" // 演示屬性刪除方法 delete o.sayHi; // true Reflect.deleteProperty(o, 'sayHello'); // true
這兩個方法用於設置和訪問對象的原型__proto__
function A(){}; A.prototype.name= "A"; Reflect.getPrototypeOf(A) // ƒ () { [native code] } var a = new A(); Reflect.getPrototypeOf(a) // {name: "A", constructor: ƒ} a.name; // "A" var c = {}; Reflect.setPrototypeOf(c, A.prototype); c.name; // "A" A.prototype.newName = "C"; c.newName; // "C"
基本等同於Object.getOwnPropertyDescriptor,用於獲得指定屬性的描述對象。
Reflect.getOwnPropertyDescriptor(JSON, 'parse'); // {value: ƒ, writable: true, enumerable: false, configurable: true} var c = {}; Reflect.defineProperty(c, 'name', { configurable: true, enumerable: true, value: 'CCC', writable: false }); Reflect.getOwnPropertyDescriptor(c, 'name'); // {value: "CCC", writable: false, enumerable: true, configurable: true} c.name; // "CCC" c.name = 111; c.name; // "CCC", 由於writable=== false
Proxy 對象用於定義JS對象的基本操做行爲(如屬性查找,賦值,枚舉,函數調用等)。
下面看一個修改對象get行爲的demo:
var obj = { n : 1}; var p = new Proxy(obj, { get(t, k){ if( Reflect.has(t, k) ){ return t[k] + 100; } else { throw Error('no the property') } } }); p.n; // 101 p.p; // Uncaught Error: no the property
關於Proxy具體能夠從新定義哪些基本操做:
defineProperty
deleteProperty
apply
construct
get
getOwnPropertyDescriptor
getPrototypeOf
has
isExtensible
ownKeys
preventExtensions
set
setPrototypeOf
這個列表跟 Reflect 具備的靜態方法是一致的。更多信息能夠參考Proxy能夠處理的方法列表 的描述
不說上面這些值基本操做,咱們列舉下一下Proxy
的一些使用場景。
若是訪問一個對象中尚未初始化的值,Proxy能夠經過代理get方法,返回一個默認值。
function setDefault(defaults) { const handler = { get(t, k) { return Reflect.get(t, k) || defaults[k]; } } return new Proxy({},handler); } const d = setDefault({ name: 'name' }); const log = console.log; log(d.name); // "name" d.name = 'new name'; log(d.name); // "new name" log(d);
Proxy能夠代理對象的基礎操做,咱們經過代理 get 方法,控制外部對對象內部屬性的方法。這樣即可以達到隱藏某些特性(私有屬性)的目的。
const log = console.log; function hideProp(obj,filter) { const handler = { get(t, k) { if(!filter(k)){ let v = Reflect.get(t, k); if(typeof v === 'function'){ v = v.bind(t); } return v; } } } return new Proxy(obj,handler); } function filter(key){ return key[0] === '_'; } const o = { _p : 'no access', p: 'access', f: function(){ log(this._p); } }; const p = hideProp(o, filter); log(p.p); // access log(p._p); // undefined log(p.f()); // no access
不少JavaScript代碼使用字符串、普通或凍結的對象做爲枚舉。這些解決方案有其類型的安全問題,並且一般容易出錯。
Proxy能夠提供一個可行的替代方案。咱們採用一個簡單的鍵值對象,並經過保護它免受(甚至無心的)修改而使其更加健壯。甚至比Object.freeze更加完善。(Object.freeze不容許修改,但不會引起錯誤,而是靜默報錯)
const log = console.log; function enumF(obj) { const handler = { get(t, k) { if(Reflect.has(t, k)){ let v = Reflect.get(t, k); if(typeof v === 'function'){ v = v.bind(t); } return v; } }, set(t){ throw new TypeError('Enum is read only'); } } return new Proxy(obj,handler); } const o = { p: 'access', f: function(){ log(this._p); } }; const p = enumF(o); log(p.p); // access p.p = 'modified'; // TypeError // 對比Object.freeze const o1 = { p: '1' } Object.freeze(o1); o1.p = 'modified'; log(o1.p);
由於Proxy能夠截獲對象的大部分基礎操做,所以咱們其實是能夠跟蹤的對 對象的訪問和修改。經過記錄這些訪問和修改信息,能記錄下對這個對象的全部操做記錄。
const log = console.log; function trackChange(obj, onchange) { const handler = { deleteProperty(t, k){ const oldVal = t[k]; Reflect.deleteProperty(t, k); onchange(t, k , undefined, oldVal) }, set(t,k, v){ const oldVal = t[k]; Reflect.set(t, k , v); onchange(t, k, v, oldVal); } } return new Proxy(obj,handler); } const o = { p: 'access', f: function(){ log(this._p); } }; const p = trackChange(o, (t,k,newV, oldV)=>{ log(`the property: ${k} change from old value [${oldV}] to new value [${newV}]`) }); p.p = 'modified'; delete p.f;
來看看經過Proxy如何實現 單例模式。
const log = console.log; function singleInstance(obj){ let instance, handler = { construct:function(t){ if(!instance){ instance = Reflect.construct(...arguments); } return instance; } } return new Proxy(obj, handler); } function A(){ this.v = 1; } const p = singleInstance(A); const p1 = new p(); const p2 = new p(); log(p1.v); log(p2.v); p1.v = 'new value'; log(p1.v); log(p2.v);
總結一下,上面的幾個例子中,咱們基本上代理的都是對象的get
,set
,deleteProperty
方法。這些都是對象的常見操做。用好了Proxy能夠解決不少問題,例如vue.js數據雙向綁定的特性實際上也可使用Proxy實現。
說了這麼多,可是並不極力推薦使用Proxy,並非出於兼容性問題。主要是性能方面,能夠確定的是,使用了Proxy確定不如原有對象訪問速度更快。Proxy至關於在對象前添加了一道牆,任何操做都要先通過Proxy處理。這確定會增長成本。寫這篇文章的主要目的是 但願可以瞭解更多的東西,爲本身解決問題增長另一種方案。
【參考資料】