ECMAScript6(12):Proxy 和 Reflect

Proxy 對象

Proxy 用來修改某些默認操做,等同於在語言層面作出修改。因此屬於一種元編程(meta programming), 即對編程語言進行編程。字面理解爲Proxy代理了某些默認的操做。
其使用格式以下:編程

var proxy = new Proxy(target, handler);

target是被代理的目標對象,handler也是個對象,用來定製攔截行爲,內部定義每一個被代理的行爲。
注意:數組

  • 若是但願這個代理有效,須要在 proxy 對象上調用屬性方法,而不是在 target 上調用
  • 若是指定 handler 爲空對象,那麼獲得對象和原對象同樣
  • 獲得的 proxy 是 target 的引用,若是沒有代理,在 proxy 上的修改和在 target 上的修改等同

看一個簡單的實例app

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
console.log(proxy.time);    //35
console.log(proxy.name);    //35
console.log(proxy.title);    //35
//被代理的對象不管輸入什麼屬性都返回35

實際上,proxy 對象也能夠被繼承:編程語言

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
var obj = Object.create(proxy);
obj.time = 20;
console.log(obj.time);    //20
console.log(obj.name);    //35

感覺一下它的威力:函數

var obj = new Proxy({}, {
  get: function(target, key, receiver){
    console.log(`getting ${key} ...`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver){
    console.log(`setting ${key} ...`);
    return Reflect.set(target, key, value, receiver);
  }
});

obj.count = 1;            //setting count ...
++obj.count;              //getting count ...
                          //setting count ...
console.log(obj.count);   //getting count ...
                          //2

能夠看出來,handler對象中 get 方法表示屬性的訪問請求,set 方法表示屬性的寫入請求。
固然不單單 get 和 set, 咱們能夠定義如下攔截函數:this

  • get(target, propKey, receiver = target)

攔截對象的讀取屬性。當 target 對象設置了 propKey 屬性的 get 函數時,receiver 綁定 get 函數的 this。返回值任意prototype

  • set(target, propKey, value, receiver = target)

攔截對象的寫入屬性。返回一個布爾值代理

  • has(target, propKey)

攔截 propKey in proxy 操做符,返回一個布爾值code

  • deleteProperty(target, propKey)

攔截 delete proxy[propKey] 操做符,返回一個布爾值orm

  • enumerate(target)

攔截 for(let i in proxy) 遍歷器,返回一個遍歷器

  • hasOwn(target, propKey)

攔截 proxy.hasOwnProperty('foo'),返回一個布爾值

  • ownKeys(target)

攔截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy),返回一個數組。該方法返回對象全部自身屬性,包括不可遍歷屬性,不包括 Symble屬性,可是Object.keys(proxy)不該該包括不可遍歷屬性

  • getOwnPropertyDescriptor(target, propKey)

攔截 Object.getOwnPropertyDescriptor(proxy, propKey),返回其屬性描述符

  • defineProperty(target, propKey, propDesc)

攔截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc),返回一個布爾值

  • preventExtensions(target)

攔截 Object.preventExtensions(proxy),返回一個布爾值

  • getPrototypeOf(target)

攔截 Object.getPrototypeOf(proxy),返回一個對象

  • isExtensible(target)

攔截 Object.isExtensible(proxy),返回一個布爾值

  • setPrototypeOf(target, proto)

攔截 Object.setPrototypeOf(proxy, proto),返回一個布爾值

  • apply(target, object, args)

攔截對 proxy 實例的函數操做,包括 proxy(...args),proxy.call(object, ...args),proxy.apply(object, args)

  • construct(target, args, proxy)

攔截用 new 調用 proxy 函數的操做,construct()返回的不是對象會報錯

如下列舉一些 Proxy 的實例

訪問對象不存在的屬性報錯

var obj = new Proxy({}, {
  get: function(target, key){
    if(key in target){
      return Reflect.get(target, key);
    } else {
      throw new ReferenceError(`"${key}" is not in object`);
    }
  }
});
obj.look = "picture";
console.log(obj.look);     //"picture"
console.log(obj.sleep);    //ReferenceError: "sleep" is not in object

數組索引爲負時返回倒數位置的值

var origin = [10,20];
var arr = new Proxy(origin, {
  get(target, key){
    let index = parseInt(key);
    if(index < 0){
      index = target.length + index;
      if(index < 0) return undefined;
    }
    return Reflect.get(target, index);
  }
});
console.log(arr[0]);     //10
console.log(arr[1]);     //20
console.log(arr[2]);     //undefined
console.log(arr[-1]);    //20
console.log(arr[-4]);    //undefined

保護對象內以 "_" 開頭的屬性爲私有屬性:

var o = {
  "_name": "Bob",
  "age": 13,
  "_fun": function(){
    console.log("_fun is called");
  }
};
var obj = new Proxy(o, {
  get(target, key){
    if(key.charAt(0) === '_'){
      return undefined;
    }
    return Reflect.get(target, key);
  },
  set(target, key, value){
    if(key.charAt(0) === '_'){
      throw new Error('Cannot define a property begin with "_"');
    }
    return  Reflect.set(target, key, value);
  },
  has(target,key){
    if(key.charAt(0) === '_'){
      return false;
    }
    return Reflect.has(target, key);
  },
  deleteProperty(target,key){
    if(key.charAt(0) === '_'){
      return false;
    } else {
      Reflect.deleteProperty(..arguments);
    }
  },
  apply(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect apply(...arguments);
    }
  },
  defineProperty(target,key,desc){
    if(key.charAt(0) === '_'){
      return new Error(`cannot define property begin with "_"`);
    } else {
      Reflect.defineProperty(..arguments);
    }
  },
  setPrototypeOf(target,proto){
    throw new TypeError(`Cannot change the proto of ${target}`);
  },
  construct(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect construct(...arguments);
    }
  }
});

console.log(obj.age);    //13
obj.age = 20;
console.log(obj.age);    //20
console.log(obj._name);  //undefined
obj._hobby = "Coding";   //Error: Cannot define a property begin with "_"
_name in key             //false
delete obj._name;
Object.defineProperty(obj,"_hobby",{
  value: "Coding"
});
Object.defineProperties(obj,{
  '_hobby': {
    value: "Coding"
  }
});
obj._fun();
var a = new obj._fun();
obj.__proto__ = {};     //Cannot define a property begin with "_"
Object.setPrototypeOf(obj,{})    //Cannot change the proto of obj

固然不是全部 proxy 代理都不可取消,下面方法設置的代理是能夠經過定義代理時返回的revoke函數取消:

var a = {
  name:"Bob"
};
var {proxy, revoke} = Proxy.revocable(a, {
  get(target,key){
    return undefined;
  }
});
proxy.name;   //undefined;
revoke();
proxy.name;   //TypeError: Cannot perform 'get' on a proxy that has been revoked

Reflect 對象

Reflect 對象有一下做用:

  1. 將 Object對象的一些明顯屬於語言層面的方法部署在 Reflect 上
  2. 修改某些 Object 對象的方法使其更合理。好比 Object.defineProperty 遇到沒法定義屬性時會拋出錯誤,而 Reflect.defineProperty 會返回 false
  3. 把因此 object 的操做都替換成函數行爲,好比用 Reflect.has(obj,name) 替換 name in obj
  4. 保證只要是 Proxy 有的方法就必定能夠在 Reflect 上找到相同的方法,這樣能夠在實現 proxy 時方便的完成默認行爲。換言之,不管 proxy 怎麼修改默認行爲,你總能夠在 Reflect 上找到真正默認的行爲

代理在添加額外的功能時,利用 Reflect 保證了原始功能的實現。舉個例子:

var loggedObj = new Proxy({}, {
  get(target,propKey){
    console.log(`getting ${target}.${propKey}`);  //固然你最好把操做記錄到一個 log 中
    return Reflect.get(target,propKey);
  }
});

Reflect有如下方法:

  • Reflect.getOwnPropertyDescriptor(target, propKey)

等同於 ObjectgetOwnPropertyDescriptor(target, propKey)

  • Reflect.defineProperty(target,propKey,desc)

等同於 Object.defineProperty(target,propKey,desc)

  • Reflect.getOwnPropertyNames(target)

等同於 Object.getOwnPropertyNames(target)

  • Reflect.getPrototypeOf(target)

等同於 Object.getPrototypeOf(target)

  • Reflect.setPrototypeOf(target, proto)

等同於 Object.setPrototypeOf(target, proto)

  • Reflect.deleteProperty(target, propKey)

等同於 delete target.propKey

  • Reflect.enumerate(target)

等同於 for ... in target

  • Reflect.freeze(target)

等同於 Object.freeze(target)

  • Reflect.seal(target)

等同於 Object.seal(target)

  • Reflect.preventExtensions(target)

等同於 Object.preventExtensions(target)

  • Reflect.isFrozen(target)

等同於 Object.isFrozen(target)

  • Reflect.isSealed(target)

等同於 Object.isSealed(target)

  • Reflect.isExtensible(target)

等同於 Object.isExtensible(target)

  • Reflect.has(target, propKey)

等同於 propkey in object

  • Reflect.hasOwn(target, propKey)

等同於 target.hasOwnProperty(propKey)

  • Reflect.ownKeys(target)

遍歷獲得target自身全部屬性,包括不可枚舉屬性,不包括 Symbol 屬性

  • Reflect.get(target,propKey, receiver = target)

若是 propKey 是個讀取器,則讀取器中的 this 綁定到 receiver

var per = {
  bar: function(){console.log("per-bar")}
}
var obj = {
  get foo(){ this.bar(); },
  bar: function (){console.log("obj-bar")}
};
Reflect.get(obj, "foo", per);    //"per-bar"
  • Reflect.set(target,propKey, value, receiver = target)

若是 propKey 是個讀取器,則讀取器中的 this 綁定到 receiver

  • Reflect.apply(target, thisArg, args)

等同於 Function.prototype.apply.call(target, thisArg, args)thisArg.target(args)

  • Reflect.construct(target,args)

等同於 new target(...args)

注意以上方法中,Reflect.set(), Reflect.defineProperty(), Reflect.freeze(), Reflect.seal(), Reflect.preventExtensions() 在成功時返回 true, 失敗時返回 false。對應的 Object 方法失敗時會拋出錯誤。

相關文章
相關標籤/搜索