元程式設計是指的是開發人員對 「語言本身進行程式設計」。一般是程式語言暴露了一些API,供開發人員操作語言本身的某些特性。
從ES6開始,新增了Proxy和Reflect特性,擴展了元程式設計(Meta Programming)能力,允許攔截並自訂基礎語言操作行為(例如,屬性查找,賦值,枚舉,函數調等)。
Proxy(代理)
Proxy是ES6加入的一個新特性,它可以「代理」 物件的原生行為,替換為執行自訂行為。
Proxy可以理解成在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以將外界的訪問過濾和改寫。 Proxy這個字的原意是代理,用在這裡表示由它來「代理」某些操作,可以譯為「代理」。
Proxy 物件用來為基礎運算(例如:屬性查找、賦值、列舉、方法呼叫等)定義使用者自訂行為。
建立一個Proxy物件:
#target:目標對象,可以是任意類型的對象,例如數組,函數,甚至是另一個代理對象。
handlert:處理器對象,包含了一組代理方法,分別控制所產生代理對象的各種行為。
Proxy物件的方法:
Proxy.revocable(target, handler):用來建立一個可撤銷的代理物件。
處理器物件一共有14種可代理方法:
#handler.getPrototypeOf():在讀取代理物件的原型時觸發該操作,例如在執行Object.getPrototypeOf(proxy) 時。
handler.setPrototypeOf():在設定代理物件的原型時觸發該操作,例如執行 Object.setPrototypeOf(proxy, null) 時。
handler.isExtensible():在判斷一個代理物件是否為可擴展時觸發該操作,例如在執行 Object.isExtensible(proxy) 時。
handler.preventExtensions():在讓一個代理物件不可擴充時觸發該操作,例如執行 Object.preventExtensions(proxy) 時。
handler.getOwnPropertyDescriptor():在取得代理物件某個屬性的屬性描述時觸發該動作,例如執行 Object.getOwnPropertyDescriptor(proxy, “foo”) 時。
handler.defineProperty():在定義代理物件某個屬性時的屬性描述時觸發該操作,例如在執行Object.defineProperty(proxy, “foo”, {})時。
handler.has():在判斷代理物件是否擁有某個屬性時觸發該操作,例如執行 “foo” in proxy 時。
handler.get():在讀取代理物件的某個屬性時觸發該操作,例如執行 proxy.foo 時。
handler.set():在給代理物件的某個屬性賦值時觸發該操作,例如在執行 proxy.foo = 1 時。
handler.deleteProperty():在刪除代理物件的某個屬性時觸發該操作,例如在執行 delete proxy.foo 時。
handler.enumerate():在遍歷代理物件的屬性時觸發該操作,例如執行 for(i in proxy){} 時。
handler.ownKeys():在取得代理物件的所有屬性鍵時觸發該操作,例如執行 Object.getOwnPropertyNames(proxy) 時。
handler.apply():在呼叫一個目標物件為函數的代理物件時觸發該操作,例如執行 proxy() 時。
handler.construct():在給一個目標物件為建構函式的代理物件建構實例時觸發此操作,例如執行new proxy() 時。
攔截屬性值的讀取操作:
#上面程式碼中,Proxy(代理)物件定義一個target和一個handle,handle實作了一個get捕捉方法。透過這個方法,被代理的物件對於未定義的屬性,不再回傳undefined,而是傳回一個42的數字。
攔截屬性值的賦值操作:
上面在程式碼中,設定了set的處理函數,如果我們偵聽的物件的屬性被更改,那麼這個處理程序就會被調用,同時透過參數能夠得知是哪個屬性被更改,更改為了什麼值。
同一個攔截器函數,可以設定攔截多個操作:
#Proxy.revocable方法用來建立一個可撤銷的代理物件,一旦某個代理物件被撤銷,它將變的幾乎完全不可用,在它身上執行任何的可代理操作都會拋出TypeError 異常。
Reflect(反射)
ES6 中引入的Reflect是另一個元程式設計的特性,它使得我們可以直接操縱物件的原生行為。 Reflect可操縱的行為與Proxy可代理的行為是一一對應的,這使得可以在Proxy的自訂方法中方便的使用Reflect呼叫原生行為。
Reflection(反射)促進元程式設計的一種很有價值的語言特性,它可以在程式執行時動態展現程式本身的特性。
Reflect 物件提供了14個靜態方法,它們的名字剛好和那14個代理處理器方法的名字相同,這14個方法中有幾個剛好在Object 物件身上也存在同名方法,雖然它們功能類似,但也存在細微差異。
Reflect.apply():對一個函數進行呼叫操作,同時可以傳入一個陣列作為呼叫參數。和 Function.prototype.apply() 功能類似。
Reflect.construct():對建構子進行 new 操作,相當於執行 new target(…args)。
Reflect.defineProperty():和 Object.defineProperty() 類似。
Reflect.deleteProperty():刪除物件的某個屬性,相當於執行 delete target[name]。
Reflect.enumerate():該方法會傳回一個包含有目標物件身上所有可枚舉的自身字串屬性以及繼承字串屬性的迭代器,for…in 操作遍歷到的正是這些屬性。
Reflect.get():取得物件身上某個屬性的值,類似 target[name]。
Reflect.getOwnPropertyDescriptor():類似 Object.getOwnPropertyDescriptor()。
Reflect.getPrototypeOf(): 類似 Object.getPrototypeOf()。
Reflect.has():判斷物件是否存在某個屬性,和 in 運算子 的函數完全相同。
Reflect.isExtensible():類似 Object.isExtensible()。
Reflect.ownKeys():傳回一個包含所有自身屬性(不包含繼承屬性)的陣列。
Reflect.preventExtensions():類似 Object.preventExtensions()。
Reflect.set():設定物件身上某個屬性的值,類似 target[name] = val。
Reflect.setPrototypeOf():類似 Object.setPrototypeOf()。
在上面程式碼中,Proxy方法攔截target物件的屬性賦值行為,採用Reflect.set方法將值賦值給物件的屬性。
為什麼要使用Reflect:
#將Object物件的一些明顯屬於語言內部的方法(例如Object.defineProperty),放到Reflect對像上。現階段,某些方法同時在Object和Reflect物件上部署,未來的新方法將只部署在Reflect物件上。
修改某些Object方法的回傳結果,讓其變得更合理。例如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會傳回false。
讓Object運算都變成函數行為。某些Object運算是指令式,例如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函式行為。
Reflect物件的方法與Proxy物件的方法一一對應,只要是Proxy物件的方法,就能在Reflect物件上找到對應的方法。這就讓Proxy物件可以方便地呼叫對應的Reflect方法,完成預設行為,作為修改行為的基礎。也就是說,不管Proxy怎麼修改預設行為,你總是可以在Reflect上取得預設行為。
在上面程式碼中,每一個Proxy物件的攔截操作(get、delete、has),內部都會呼叫對應的Reflect方法,保證原生行為能夠正常執行。新增的工作,就是將每一個操作輸出一行日誌。有了Reflect物件以後,很多操作會更易讀。
以上是ES6新特性開發微信小程式(6)的詳細內容。更多資訊請關注PHP中文網其他相關文章!