1. 程式人生 > >ES6 -- 最佳代理Proxy:例項方法

ES6 -- 最佳代理Proxy:例項方法

get方法

該方法用來代理屬性的讀取操作。當用戶希望訪問物件的某個屬性時,會直接觸發這個方法,而不是預設的讀取屬性方法。

栗子:

var car = {
  brank: "Benz"
};

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

本慄中,如果訪問的屬性存在,將會返回屬性值,但是如果屬性不存在,則會丟擲錯誤。在平常的情況下,如果訪問的屬性不存在,只是會返回undefined,不會報錯。

利用proxy,可以實現函式的鏈式呼叫:(程式碼來自阮一峰大神ES6部落格)

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

程式碼分析:如果想要實現函式的鏈式呼叫,就需要將可以呼叫某函式的物件作為函式的返回值。在上面這個栗子中,對oproxy的屬性(方法其實也可以看做屬性)的訪問總會被代理函式get攔截,並根據我們部署的程式碼,在函式的最後返回oproxy物件。這樣,對於這個物件的方法的訪問,就可以鏈式不斷的呼叫下去,因為每個方法呼叫後,都會返回原物件。

限制:如果物件的屬性被設定為configurable:false或者writable:false,那麼這時候,get屬性將不能被代理,通過Proxy訪問該屬性會報錯。

const cantChangeObj = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});

const handler = {
  get(target, propKey) {
    return 'geting'+propKey+'from'+target;
  }
};

const proxy = new Proxy(cantChangeObj, handler);

proxy.foo
// TypeError: Invariant check failed

set方法

set方法用來代理物件某個屬性的賦值操作。通過set,我們就可以獲得對屬性值的型別、大小等等的掌控能力。

let validate = {
  set: function(obj, prop, value) {
    if (prop === 'year') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The year must be an integer');
      }
      if (year < 2000) {
        throw new RangeError('The year seems invalid');
      }
    }

    // 對於year以外的屬性,可以直接儲存,或使用if增加其他判斷
    obj[prop] = value;
  }
};

let car = new Proxy({}, validate);

car.year = 2001;

car.year // 2001
car.year = 'Benz' // 報錯
car.year = 1999 // 報錯

上述程式碼對car物件的year屬性設定了範圍:首先必須是數字,其次,必需要在2000以後(太老舊的車不能通過驗證)。

應用1: 對於set方法我們還可以將資料與DOM元素繫結,每當元素值發生變化,DOM也就相應的發生變化。

應用2: 有些物件的內部屬性,是我們不希望他人有意或無意修改的,通常會用下劃線“_”開頭,set方法可以幫助我們切實的阻止他人對此類物件的訪問。

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

限制:同get一樣,如果目標物件自身的某個屬性,不可寫也不可配置,那麼set不得改變這個屬性的值,只能返回同樣的值,否則報錯。

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()
// "I am the proxy"

當p被作為函式呼叫的時候(p()),代理方法apply就會生效,返回的是apply函式的字串。

另一個稍微複雜的栗子:

var twice = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments) * 2;
  }
};
function sum (left, right) {
  return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30

總上面幾個程式碼我們可以總結出——apply代理的途徑包括:1、當直接執行proxy函式時,2、proxy呼叫call或者apply時。

has方法

has方法將作為方法HasProperty的代理,HasProperty的功能是判斷物件是否具有某個屬性。

應用1: 使用has方法,可以幫助我們隱藏某些屬性,不被in運算子發現。

var handler = {
  has (target, key) {
    if (key === 'privateProp') {
      return false;
    }
    return key in target;
  }
};
var target = { privateProp: 'myPassword', prop: 'foo' };
var proxy = new Proxy(target, handler);
'privateProp' in proxy // false

需要注意的是,has方法代理的方法是HasProperty(在沒有任何其他配置的前提下,一個物件自身的屬性和繼承的屬性都可以被此方法找到),而不是HasOwnProperty(此方法儘可以找到屬於物件自身的屬性)。還需要注意,has只對in運算子有效,而不是for…in…。

限制:如果原物件被設定為不可配置/禁止擴充套件,那麼,該操作就會報錯:

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false;
  }
});

'a' in p // TypeError is thrown