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