摘要:綁定實現的歷史綁定的基礎是事件。但臟檢查機制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機制的嘗試,在中引入。掙扎了一段時間后谷歌團隊宣布收回的提議,并在中完全刪除了實現。自然全軍覆沒其他各大瀏覽器實現的時間也較晚。
綁定實現的歷史
綁定的基礎是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發 MVVM 框架的首要問題。主流框架的處理有一下三大類:
另外開發一套 API。典型框架:Backbone.js
Backbone 有自己的 模型類 和 集合類。這樣做雖然框架開發簡單運行效率也高,但開發者不得不使用這套 API 操作 viewModel,導致上手復雜、代碼繁瑣。
臟檢查機制。典型框架:angularjs
特點是直接使用 JS 原生操作對象的語法操作 viewModel,開發者上手簡單、代碼簡單。但臟檢查機制隨之帶來的就是性能問題。這點在我另外的一篇博文 《Angular 1 深度解析:臟數據檢查與 angular 性能優化》 有詳細講解這里不另加贅述。
替換屬性。典型框架:vuejs
vuejs 把開發者定義的 viewModel 對象(即 data 函數返回的對象)中所有的(除某些前綴開頭的)成員替換為屬性。這樣既可以使用 JS 原生操作對象的語法,又是主動觸發 propertyChange 事件,效率也高。但這種方法也有一些限制,后文會分析。
Object.observe 是谷歌對于簡化雙向綁定機制的嘗試,在 Chrome 49 中引入。然而由于性能等問題,并沒有被其他各大瀏覽器及 ES 標準所接受。掙扎了一段時間后谷歌 Chrome 團隊宣布收回 Object.observe 的提議,并在 Chrome 50 中完全刪除了 Object.observe 實現。
ProxyProxy(代理)是 ES2015 加入的新特性,用于對某些基本操作定義自定義行為,類似于其他語言中的面向切面編程。它的其中一個作用就是用于(部分)替代 Object.observe 以實現雙向綁定。
例如有一個對象
let viewModel = {};
可以構造對應的代理類實現對 viewModel 的屬性賦值操作的監聽:
viewModel = new Proxy(viewModel, { set(obj, prop, value) { if (obj[prop] !== value) { obj[prop] = value; console.log(`${prop} 屬性被改為 ${value}`); } return true; } });
這時所有對 viewModel 的屬性賦值的操作都不會直接生效,而是將這個操作轉發給 Proxy 中注冊的 set 方法,其中的參數 obj 是原始對象(注意不能直接用 a,否則還會觸發代理函數,造成無限遞歸),prop 是被賦值的屬性名,value 是待賦的值。
如果有:
viewModel.test = 1;
這時就會輸出 test 屬性被改為 1。
用 Proxy 實現簡單的單向綁定。有了 Proxy 就可以得知 viewModel 中屬性的變更了,還需要更新頁面上綁定此屬性的元素。
簡單起見,我們用 this 表示 viewModel 本身,使用 this.XXX 就表示依賴 XXX 屬性。有 DOM 如下:
首先要獲得所有使用了單向綁定的元素:
const bindingElements = [...document.querySelectorAll("[my-bind]")];
獲取綁定表達式:
bindingElements.forEach(el => { const expression = el.getAttribute("my-bind"); });
由于獲得的表達式是個字符串,需要構造一個函數去執行它,得到表達式的結果:
const expression = el.getAttribute("my-bind"); const result = new Function(""use strict"; return " + expression).call(viewModel);
代碼中會動態創建一個函數,內容就是將字符串解析執行后將其結果返回(類似 eval,但更安全)。將結果放到頁面上就可以了:
el.textContent = result;
與上文的 viewModel 結合起來:
const bindingElements = [...document.querySelectorAll("[my-bind]")]; window.viewModel = new Proxy({}, { // 設置全局變量方便調試 set(obj, prop, value) { if (obj[prop] !== value) { obj[prop] = value; bindingElements.forEach(el => { const expression = el.getAttribute("my-bind"); const result = new Function(""use strict"; return " + expression) .call(obj); el.textContent = result; }); } return true; } });
如果實際放在瀏覽器中運行的話,改變 viewModel 中屬性的值就會觸發頁面的更新。
示例中寫了循環會更新所有綁定元素,比較好的方式是只更新對當前變更屬性有依賴的元素。這時就要分析綁定表達式的屬性依賴。
簡單起見可以使用正則表達式解析屬性依賴:
let match; while (match = /this(?:.(w+))+/g.exec(expression)) { match[1] // 屬性依賴 }添加事件綁定
事件綁定即綁定原生事件,在事件觸發時執行綁定表達式,表達式調用 viewModel 中的某個回調函數。
以 click 事件為例。依然是獲取所有綁定了 click 事件的元素,并執行表達式(表達式的值被丟棄)。與單項綁定不同的是:執行表達式需要傳入事件的 event 參數。
[...document.querySelectorAll("[my-click]")].forEach(el => { const expression = el.getAttribute("my-click"); const fn = new Function("$event", ""use strict"; " + expression); el.addEventListener("click", event => { fn.call(viewModel, event); }); });
Function 對象的構造函數,前 n-1 個參數是生成的函數對象的參數名,最后一個是函數體。代碼中構造了包含一個 $event 參數的函數,函數體就是直接執行綁定表達式。
雙向綁定雙向綁定就是單項綁定和事件綁定的結合體。綁定元素的 input 事件來修改 viewModel 的屬性,然后再單項綁定元素的 value 屬性修改元素的值。
這里是一個較為完整的示例:http://sandbox.runjs.cn/show/...。完整的代碼放在我的 GitHub 倉庫
使用 Proxy 實現雙向綁定的優缺點相較于 vuejs 的屬性替換,Proxy 實現的綁定至少有如下三個優點:
無需預先定義待綁定的屬性。
vuejs 要做屬性(getter, setter 方法)替換,首先需要知道有哪些屬性需要替換,這樣導致必須預先定義需要替換的屬性,也就是 vuejs 中的 data 方法。vuejs 中 data 方法必須定義完整所有綁定屬性,否則對應綁定不能正常工作。
Vue 不能檢測到對象屬性的添加或刪除:Property or method "XXX" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
而 Proxy 不需要,因為它監聽的是整個對象。
對數組相性良好。
雖說數組里的方法可以替換(push、pop等),但是數組下標卻不能替換為屬性,以致必須搞出一個 set 方法用于對數組下標賦值。
更容易調試的 viewModel 對象。
由于 vuejs 把對象中的所有成員全部替換成了屬性,如果想直接用 Chrome 的原生調試工具查看屬性值,你不得不挨個去點屬性后面的 (...):因為獲取屬性的值其實是執行了屬性的 get 方法,執行一個方法可能會產生副作用,Chrome 把這個決定權留給開發者。
Proxy 對象不需要。Proxy 的 set 方法只是一層包裝,Proxy 對象自身維護原始對象的值,自然也可以直接拿出原始值給開發者看。查看一個 Proxy 對象,只需要展開其內置屬性 [[Target]] 即可看到原始對象的所有成員的值。你甚至還可以看到包裝原始對象的哪些 get、set 函數——如果你感興趣的話。
雖說使用 Proxy 實現雙向綁定的優點很明顯,但是缺點也很明顯:Proxy 是 ES2015 的特性,它無法被編譯為 ES5,也無法 Polyfill。IE 自然全軍覆沒;其他各大瀏覽器實現的時間也較晚:Chrome 49、Safari 10。瀏覽器兼容性極大的限制了 Proxy 的使用。但是我相信,隨著時間的推移,基于 Proxy 的前端 MVVM 框架也會出現在開發者眼前。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/51073.html
摘要:綁定實現的歷史綁定的基礎是事件。但臟檢查機制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機制的嘗試,在中引入。掙扎了一段時間后谷歌團隊宣布收回的提議,并在中完全刪除了實現。自然全軍覆沒其他各大瀏覽器實現的時間也較晚。 綁定實現的歷史 綁定的基礎是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發 MVVM 框架的首要問題。主流框架的處理有一下三...
摘要:原來是在改變數據時,還要手動。現在只需要直接改變數據,會自動,更新元素。參考資料現代前端技術解析,張成文,年月第版,和的圖示,阮一峰,年月日, author: 陳家賓 email: 617822642@qq.com date: 2018/3/1 MVVM 背景 都說懶惰使人進步,MVVM 的進化史,正印證了這句話,是一步步讓開發人員更懶惰更簡單的歷史: 直接 DOM 操作 -> MVC...
摘要:的數據劫持版本內部使用了來實現數據與視圖的雙向綁定,體現在對數據的讀寫處理過程中。這樣就形成了數據的雙向綁定。 MVVM由以下三個內容組成 View:視圖模板 Model:數據模型 ViewModel:作為橋梁負責溝通View和Model,自動渲染模板 在JQuery時期,如果需要刷新UI時,需要先取到對應的DOM再更新UI,這樣數據和業務的邏輯就和頁面有強耦合。 在MVVM中,U...
摘要:套數據,實現界面先把計算屬性這個注釋掉,后面進行實現計算屬性然后在函數中增加一個編譯函數,號表示是添加的函數添加一個編譯函數上面我們添加了一個的構造函數。 Proxy、Reflect的簡單概述 Proxy 可以理解成,在目標對象之前架設一層攔截,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這里表示由它...
閱讀 533·2023-04-26 01:39
閱讀 4485·2021-11-16 11:45
閱讀 2610·2021-09-27 13:37
閱讀 882·2021-09-01 10:50
閱讀 3579·2021-08-16 10:50
閱讀 2217·2019-08-30 15:55
閱讀 2979·2019-08-30 15:55
閱讀 2259·2019-08-30 14:07