ES6 Proxy

Proxy 用於修改某些操作的默認行爲,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。

Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來“代理”某些操作,可以譯爲“代理器”。

ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。

var proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

Proxy支持的攔截對象一共有13種

  • 1、get(target, propKey, receiver):攔截對象屬性的讀取
  • 2、set(target, propKey, value, receiver):攔截對象屬性的設置,返回一個布爾值
  • 3、has(target, propKey):攔截propKey in proxy的操作,返回一個布爾值
  • 4、deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值。
  • 5、ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性
  • 6、getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象
  • 7、defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值
  • 8、preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值
  • 9、getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象
  • 10、isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值
  • 11、setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標對象是函數,那麼還有兩種額外操作可以攔截
  • 12、apply(target, object, args):攔截 Proxy 實例作爲函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
  • 13、construct(target, args):攔截 Proxy 實例作爲構造函數調用的操作,比如new proxy(...args)

1、get()

get方法用於攔截某個屬性的讀取操作,可以接受三個參數,依次爲目標對象、屬性名和 proxy 實例本身(嚴格地說,是操作行爲所針對的對象),其中最後一個參數可選

var person = {
  name: "張三"
};

var proxy = new Proxy(person, {
  get: function(target, property) {
    if (property in target) {
      return target[property];
    } else {
      throw new ReferenceError("Property \"" + property + "\" does not exist.");
    }
  }
});

proxy.name // "張三"
proxy.age // 拋出一個錯誤

上面代碼表示,如果訪問目標對象不存在的屬性,會拋出一個錯誤。如果沒有這個攔截函數,訪問不存在的屬性,只會返回undefined

get方法可以繼承

let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET ' + propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.foo // "GET foo"

利用 Proxy,可以將讀取屬性的操作(get),轉變爲執行某個函數,從而實現屬性的鏈式操作。

var pipe = (function () {
  return function (value) {
    var funcStack = [];
    var oproxy = new Proxy({} , {
      get : function (pipeObject, fnName) {
        if (fnName === 'get') {
          return funcStack.reduce(function (val, fn) {
            return fn(val);
          },value);
        }
        funcStack.push(window[fnName]);
        return oproxy;
      }
    });

    return oproxy;
  }
}());

var double = n => n * 2;
var pow    = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; // 63

2、set()

set方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次爲目標對象、屬性名、屬性值和 Proxy 實例本身,其中最後一個參數可選

假定Person對象有一個age屬性,該屬性應該是一個不大於 200 的整數,那麼可以使用Proxy保證age的屬性值符合要求

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 對於滿足條件的 age 屬性以及其他屬性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 報錯
person.age = 300 // 報錯

3、apply()

apply方法攔截函數的調用、call和apply操作。

apply方法可以接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()

上面代碼中,變量p是 Proxy 的實例,當它作爲函數調用時(p()),就會被apply方法攔截,返回一個字符串。

4、has()

has方法用來攔截HasProperty操作,即判斷對象是否具有某個屬性時,這個方法會生效。典型的操作就是in運算符。

has方法可以接受兩個參數,分別是目標對象、需查詢的屬性名

var handler = {
  has (target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章