1. 程式人生 > 實用技巧 >ECMAScript 6-Proxy

ECMAScript 6-Proxy

Proxy

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

生成例項

//第一個引數target表示所要攔截的目標物件,第二個引數handler是一個配置物件,用來定製攔截行為
var proxy = new Proxy(target, handler);
var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});
//解析:第一個引數是空物件,表示攔截的目標物件是空物件;第二個引數物件裡有個get方法,該函式將攔截對應的操作,這裡用來攔截對目標物件屬性的訪問請求
//於是每次訪問屬性都會返回35
proxy.time // 35
proxy.name // 35
proxy.title // 35

Proxy要點

  • 只有針對proxy物件進行操作,攔截器才起作用

  • 如果handler沒有設定任何攔截,那就等同於直接通向原物件

    var target = {};	//宣告一個需要被攔截的目標物件
    var handler = {};	//宣告一個攔截行為,這裡為空
    var proxy = new Proxy(target, handler);		//例項化一個攔截
    proxy.a = 'b';	//由於proxy沒有設定攔截,那麼這裡的proxy就相當於target
    target.a // "b"		
    
  • 同一個攔截器函式,可以設定攔截多個操作

Proxy技巧

  • 將 Proxy 物件,設定到object.proxy

    屬性

    var object = { proxy: new Proxy(target, handler) };
    
  • 將 Proxy 物件作為其他物件的原型物件

    var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
    
    let obj = Object.create(proxy);		//將Proxy物件作為obj的原型物件
    obj.time // 35
    

Proxy 支援的攔截操作

1. get(target, propKey, receiver)
攔截物件屬性的讀取,比如`proxy.foo`和`proxy['foo']`,`receiver`是個物件,可選
2. set(target, propKey, value, receiver)
攔截物件屬性的設定,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一個布林值
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),返回一個數組。該方法返回目標物件所有自身的屬性的屬性名,而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(...)。三個引數分別是目標物件、目標物件的上下文物件(this)和目標物件的引數陣列
13. construct(target, args)
攔截 Proxy 例項作為建構函式呼叫的操作,比如new proxy(...args)

Proxy 例項的方法

  • get

    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 // 丟擲一個錯誤
    

    get方法可以繼承

    let proto = new Proxy({}, {
      get(target, propertyKey, receiver) {
        console.log('GET '+propertyKey);
        return target[propertyKey];
      }
    });
    
    let obj = Object.create(proto);
    obj.xxx // "GET xxx"
    
  • set()

    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 // 報錯
    

    結合getset方法,就可以做到防止這些內部屬性被外部讀寫。比如設定的屬性名如果是下劃線開頭,表示這些屬性不應該被外部使用

    var handler = {
      get (target, key) {
        invariant(key, 'get');	//給該驗證方法傳屬性
        return target[key];
      },
      set (target, key, value) {
        invariant(key, 'set');	//給該驗證方法傳屬性
        target[key] = value;
        return true;
      }
    };
    function invariant (key, action) {
      if (key[0] === '_') {		//判斷傳過來的屬性第一個字元是否為_,有則丟擲錯誤
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
      }
    }
    var target = {};
    var proxy = new Proxy(target, handler);
    proxy._prop
    // Error: Invalid attempt to get private "_prop" property
    proxy._prop = 'c'
    // Error: Invalid attempt to set private "_prop" property
    
  • apply()

    var target = function () { return 'I am the target'; };
    var handler = {
      apply: function () {
        return 'I am the proxy';
      }
    };
    var p = new Proxy(target, handler);
    
    p()
    // "I am the proxy"