這樣就消除了以前Vue2.x中基於Object.defineProperty實現的諸多限制:無法監控屬性的添加和刪除、數組索引和長度的變化,可以支持Map、Set、WeakMap和WeakSet!
作為“前端工程師”,安利壹波代理是很有必要的!!
MDN中描述了代理對象用於定義基本操作的自定義行為(如屬性查找、賦值、枚舉、函數調用等。).
官方的描述總是言簡意賅,以至於說不清楚。...
其實在操作目標對象之前就提供了攔截,可以過濾重寫外部操作,修改壹些操作的默認行為,這樣我們就可以通過操作對象的代理對象間接操作對象,而不是直接操作對象本身,達到預期的目的~
什麽?妳還沒說清楚嗎?我們來看壹個例子,壹目了然~
在上面的例子中,我們預先定義了壹個對象obj,通過代理構造函數生成了壹個Proxyobj對象,並再次修改了它的set (write)和get (read)行為。
當我們訪問對象中的原始屬性時,我們會返回原始屬性中對應的值,如果我們試圖訪問壹個不存在的屬性,我們會返回0,即當我們訪問proxyObj.a時,原始對象有屬性A,所以我們會返回1,當我們試圖訪問對象中不存在的屬性B時,我們不會返回undefined。相反,它返回0,當我們試圖設置壹個新的屬性值時,它總是返回888。所以即使我們給proxyObj.a賦值666,也不會生效,仍然會返回888!
ES6本身提供的代理語法很簡單,其用法如下:
let proxy = new Proxy(目標,處理程序);
參數target是proxy包裝的目標對象(可以是任何類型的對象,包括原生數組、函數甚至另壹個Proxy),參數handler也是壹個對象,其屬性是定義代理在執行操作時的行為的函數,即自定義行為。
代理的基本用法同上,只是處理程序對象不同。處理程序可以是空對象{},這意味著代理操作是目標操作,即:
但是需要註意的是,handler不能設置為null,會拋出壹個錯誤——不能用非對象作為目標或者handler來創建代理!
如果Proxy起作用,我們就不能操作原對象的對象,也就是目標對象(上例中obj的對象),必須操作代理實例(上例中proxyObj的對象),否則無法達到預期的效果。在最初的例子中,在我們設置了get方法之後,我們試圖繼續從原始對象obj中讀取壹個不存在的屬性b,結果仍然返回undefined:
對於可設置但未設置攔截的操作,代理對象的處理結果也會作用於原目標對象。怎麽理解呢?或者在初始的例子中,我們重新定義了set方法,所有的屬性設置都返回到888,並沒有截取或者處理壹個特殊的屬性(這裏指的是Obj的A屬性),所以通過proxyObj.a = 666的運算結果也會作用在原來的目標對象(obj對象)上,所以obj對象的A的值也會變成888!
目前,ES6中的代理提供了13種代理操作。這裏我會總結整理壹些常用的API。想了解其他方法的同學可以自己去官網查壹下:
- handler.get(目標、屬性、接收方)
用於攔截讀取對象屬性的操作。target是指目標對象,property是獲取的屬性的名稱,receiver是代理或者繼承代理的對象,通常是代理實例。
我們攔截了壹個空對象的read get操作,在獲取內部屬性的時候會輸出get ${prop}並返回10;
代理對象的a屬性由代理對象提供,所以接收方指向代理對象,所以proxy.a === proxy返回true。
註意,如果要訪問的目標屬性不可寫,不可配置,那麽返回值必須和目標屬性相同,即不能修改,否則會拋出異常~
無法寫入或配置上述obj對象中的A屬性。我們通過proxy創建了壹個Proxy的實例,並截獲了它的get操作。當我們輸出proxy.a時,會拋出壹個異常。此時,如果我們將get方法的返回值修改為與目標屬性的值相同,即10,
可以排除例外~
- handler.set(目標、屬性、值、接收者)
用於攔截設置屬性值的操作。與get方法相比,參數多了壹個值,就是要設置的屬性值~
在嚴格模式下,set方法需要返回壹個布爾值。如果返回true,則意味著屬性設置成功。如果它返回false並且屬性設置操作失敗,將引發TypeError。
上面,通過修改set方法,我們限制了目標對象中count屬性的賦值。我們要求count屬性的賦值必須是數字類型的數據。如果不是,則返回壹個錯誤。該變量不是整數。第壹次我們將字符串' 10 '賦給count,拋出了壹個異常,第二次我們將它賦給數字10,打印成功。所以可以用set的方法做壹些數據驗證!
同樣,如果目標屬性不可寫且不可配置,則不能更改其值,即賦值無效,如下所示:
我們將上述obj對象中的count屬性設置為不可修改,默認值為10,所以即使賦值為20,結果也不變!
- handler.apply(target,thisArg,argumentsList)
用於攔截函數的調用,* * *有三個參數,即目標對象(function),調用時的上下文對象thisArg和調用時的參數數組argumentsList。這個方法可以返回任何值。
Target必須是function對象,否則會拋出TypeError
實際上,apply還會攔截目標對象的Function.prototype.apply()和Function.prototype.call(),以及Reflect.apply()操作,如下所示:
- handler.construct(target,argumentsList,newTarget)
該構造用於截取新運算符。為了讓new操作符在生成的代理對象上生效,用於初始化代理本身的目標對象必須有壹個[[Construct]]內部方法;它接收三個參數,即目標對象target、構造函數參數列表argumentsList和對象首次實例化時new命令使用的構造函數,即以下示例中的P。
另外,方法必須返回壹個對象,否則會拋出異常!
- handler.has(目標,屬性)
has方法可以看作是in操作的壹個鉤子。當我們判斷物體是否具有某種屬性時,這個方法就生效了。典型的操作是在。該方法更改為接收兩個參數目標對象和要檢查的屬性屬性,並返回壹個布爾值。
在上面的例子中,我們使用了has方法來隱藏屬性以下劃線_開頭的私有屬性,這樣在判斷時會返回false,這樣就不會被In運算符發現~
需要註意的是,如果目標對象本身的壹個屬性不能被配置,它就不能被代理隱藏;如果目標對象是不可擴展的對象,它不能被代理隱藏,否則將拋出TypeError。
上面介紹了這麽多,是對代理的初步了解,所以我們可以通過代理手動實現壹個非常簡單的數據雙向綁定(見我上壹篇文章最後的Object.defineProperty()的實現方法)~
主要看功能的實現,所以我只是在布局上揮揮手~
頁面結構如下:
主要取決於邏輯部分:
上面我們通過代理創建了壹個myText實例,通過攔截myText中Text屬性的set方法來更新視圖變化,從而實現了壹個非常簡單的雙向數據綁定~
說到這裏,Proxy終於上手了。雖然它的語法很簡單,但要充分發揮它的價值並不容易。再加上自身代理的兼容性,我們實際應用開發中使用的場景並不多,但不代表不實用。在我看來,可以用於二次數據處理,數據合法性驗證,甚至功能代理,更有使用價值。
況且Vue3.0都準備發布了,妳還不打算讓它學?
加油!