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
的介入,操作原來要訪問的就是這個對象;
第二個參數是一個配置對象,對於每一個被代理的操作,需要提供一個對應的處理函數,該函數將攔截對應的操作。
一個技巧是將 Proxy 對象,設置到object.proxy
屬性,從而可以在object
對象上調用。
利用 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
var object = { proxy: new Proxy(target, handler) };
Proxy 實例也可以作為其他對象的原型對象。
var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); let obj = Object.create(proxy); obj.time // 35
對於可以設置、但沒有設置攔截的操作,則直接落在目標對象上,按照原先的方式產生結果。
(1)get(target, propKey, receiver)
攔截對象屬性的讀取,比如proxy.foo
和proxy[‘foo‘]
。
最後一個參數receiver
是一個對象,可選,參見下面Reflect.get
的部分。
(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(...)
。
(13)construct(target, args)
攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)
。
set()
set
方法用來攔截某個屬性的賦值操作。
假定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 // 報錯
上面代碼中,由於設置了存值函數set
,任何不符合要求的age
屬性賦值,都會拋出一個錯誤,這是數據驗證的一種實現方法。
利用set
方法,還可以數據綁定,即每當對象發生變化時,會自動更新 DOM。
有時,我們會在對象上面設置內部屬性,屬性名的第一個字符使用下劃線開頭,表示這些屬性不應該被外部使用。
結合get
和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
ES6之主要知識點(十)Proxy