摘要:寫在前面這篇文章講述了如何利用和實現雙向數據綁定,個人系早期玩家,寫這個小框架的時候也沒有參考等源代碼,之前了解過其他實現,但沒有直接參考其他代碼,如有雷同,純屬巧合。我們同時也應該支持事件機制,這里我們以最常用的方法作為例子實現。
寫在前面:這篇文章講述了如何利用Proxy和Reflect實現雙向數據綁定,個人系Vue早期玩家,寫這個小框架的時候也沒有參考Vue等源代碼,之前了解過其他實現,但沒有直接參考其他代碼,如有雷同,純屬巧合。
代碼下載地址:這里下載
綜述關于Proxy和Reflect的資料推薦阮老師的教程:http://es6.ruanyifeng.com/ 這里不做過多介紹。
實現雙向數據綁定的方法有很多,也可以參考本專欄之前的其他實現,我之所以選擇用Proxy和Reflect,一方面是因為可以大量節約代碼,并且簡化邏輯,可以讓我把更多的經歷放在其他內容的構建上面,另外一方面本項目直接基于ES6,用這些內容也符合面向未來的JS編程規范,第三點最后說。
由于這個小框架是自己在PolarBear這個咖啡館在一個安靜的午后開始寫成,暫且起名Polar,日后希望我能繼續完善這個小框架,給添加上更多有趣的功能。
首先我們可以看整體功能演示:
一個gif動圖,如果不能看,請點擊[這里的鏈接]
我們要做這樣一個小框架,核心是要監聽數據的改變,并且在數據的改變的時候進行一些操作,從而維持數據的一致。
我的思路是這樣的:
將所有的數據信息放在一個屬性對象中(this._data),之后給這個屬性對象用Proxy包裝set,在代理函數中我們更新屬性對象的具體內容,同時通知所有監聽者,之后返回新的代理對象(this.data),我們之后操作的都是新的代理對象。
對于input等表單,我們需要監聽input事件,在回調函數中直接設置我們代理好的數據對象,從而觸發我們的代理函數。
我們同時也應該支持事件機制,這里我們以最常用的click方法作為例子實現。
下面開始第一部分,我們希望我們之后使用這個庫的時候可以這樣調用:
name:{{name}} age:{{age}}note:{{note}}
沒錯,和Vue神似吧,所以這種調用方式應當為我們所熟悉。
我們需要建立一個Polar類,這個類的構造函數應該進行一些初始化操作:
constructor(configs){ this.root = this.el = document.querySelector(configs.el); this._data = configs.data; this._data.__bindings = {}; //創建代理對象 this.data = new Proxy(this._data, {set}); this.methods = configs.methods; this._compile(this.root); }
這里面的一部份內容是直接將我們傳入的configs按照屬性分別賦值,另外就是我們創建代理對象的過程,最后的_compile方法可以理解為一個私有的初始化方法。
實際上我把剩下的內容幾乎都放在_compile方法里面了,這樣理解起來方便,但是之后可能要改動。
我們還是先不能看我們代理的set該怎么寫,因為這個時候我們還要先繼續梳理思路:
假設我們這樣
看上文的__bindings。這個對象用來存儲所有綁定的dom節點信息,__bindings本身是一個對象,每一個有對應dom節點綁定的數據名稱都是它的屬性,對應一個數組,數組中的每一個內容都是一個綁定信息,這樣,我們在自己寫的set代理函數中,我們一個個調用過去,就可以更新內容了:
dataSet.__bindings[key].forEach(function(item){ //do something to update... });
我這里創建了一個用于構造調用的函數,這個函數用于創建存儲綁定信息的對象:
function Directive(el,polar,attr,elementValue){ this.el=el;//元素本身dom節點 this.polar = polar;//對應的polar實例 this.attr = attr;//元素的被綁定的屬性值,比如如果是文本節點就可以是nodeValue this.el[this.attr] = this.elementValue = elementValue;//初始化 }
這樣,我們的set可以這樣寫:
function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); var dataSet = receiver || target; dataSet.__bindings[key].forEach(function(item){ item.el[item.attr] = item.elementValue = value; }); return result; }
接下來可能還有一個問題:我們的{{name}}實際上只是節點的一部分,這并不是節點啊,另外我們是不是還可以這么寫:
關于這兩個問題,前者的答案是我們將{{name}}替換成一個文本節點,而為了應對后者的情況,我們需要將兩個被綁定數據中間和前后的內容,都變成新的文本節點,然后這些文本節點組成文本節點串。(這里多說一句,html5的normalize方法可以將多個文本節點合并成一個,如果不小心調用了它,那我們的程序就要GG了)
所以我們在_compile函數首先:
var _this = this; var nodes = root.children; var bindDataTester = new RegExp("{{(.*?)}}","ig"); for(let i=0;i這樣,我們的數據綁定階段就寫好了,接下來,我們處理這樣的情況。
這實際上是一個指令,我們只需要當識別到這一個指令的時候,做一些處理,即可:
if(node.hasAttribute(("p-model")) && node.tagName.toLocaleUpperCase()=="INPUT" || node.tagName.toLocaleUpperCase()=="TEXTAREA"){ node.addEventListener("input", (function () { var attributeValue = node.getAttribute("p-model"); if(_this._data.__bindings[attributeValue]) _this._data.__bindings[attributeValue].push(new Directive(node,_this,"value",_this.data[attributeValue])) ; else _this._data.__bindings[attributeValue] = [new Directive(node,_this,"value",_this.data[attributeValue])]; return function (event) { _this.data[attributeValue]=event.target.value } })()); }請注意,上面調用了一個IIFE,實際綁定的函數只有返回的函數那一小部分。
最后我們處理事件的情況:
實際上這比處理p-model還簡單,但是我們為了支持函數參數的情況,處理了一下傳入參數,另外我實際上將event始終作為一個參數傳遞,這也許并不是好的實踐,因為使用的時候還要多注意。
if(node.hasAttribute("p-click")) { node.addEventListener("click",function(){ var attributeValue=node.getAttribute("p-click"); var args=/(.*)/.exec(attributeValue); //允許參數 if(args) { args=args[0]; attributeValue=attributeValue.replace(args,""); args=args.replace(/[()""]/g,"").split(","); } else args=[]; return function (event) { _this.methods[attributeValue].apply(_this,[event,...args]); } }()); }現在我們已經將所有的代碼分析完了,是不是很清爽?代碼除去注釋約100行,所有源代碼可以在這里下載。這當然不能算作一個框架了,不過可以學習學習,這學期有時間的話,還要繼續完善,也歡迎大家一起探討。
一起學習,一起提高,做技術應當是直接的,有問題歡迎指出~
最后說的第三點:是自己還是一個學生,做這些內容也僅僅是出于興趣。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/50522.html
摘要:寫在前面這篇文章講述了如何利用和實現雙向數據綁定,個人系早期玩家,寫這個小框架的時候也沒有參考等源代碼,之前了解過其他實現,但沒有直接參考其他代碼,如有雷同,純屬巧合。我們同時也應該支持事件機制,這里我們以最常用的方法作為例子實現。 寫在前面:這篇文章講述了如何利用Proxy和Reflect實現雙向數據綁定,個人系Vue早期玩家,寫這個小框架的時候也沒有參考Vue等源代碼,之前了解過其...
摘要:與大多數全局對象不同,沒有構造函數。為什么要設計更加有用的返回值早期寫法寫法函數式操作早期寫法寫法可變參數形式的構造函數一般寫法寫法當然還有很多,大家可以自行到上查看什么是代理設計模式代理模式,為其他對象提供一種代理以控制對這個對象的訪問。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 19 篇。 如果你錯過了前面的章節,可以在這里找到它們: 想閱讀更多優質文章請...
摘要:只能劫持對象的屬性因此我們需要對每個對象的每個屬性進行遍歷。屬性對于怎么拼接到和上面說到了怎么使用做數據劫持,怎么結合訂閱發布,請結合數據雙向綁定探究對照著數據劫持的部分去替換看一下。 前言 2018年11月16日,關注vue的人都知道這個時間點發生了什么事兒吧。vue3.0更新內容 研究數據雙向綁定的大佬們都在開始猜測這個新機制了,用原生Proxy替換Object.definePro...
閱讀 830·2021-09-22 15:18
閱讀 1181·2021-09-09 09:33
閱讀 2758·2019-08-30 10:56
閱讀 1184·2019-08-29 16:30
閱讀 1488·2019-08-29 13:02
閱讀 1458·2019-08-26 13:55
閱讀 1643·2019-08-26 13:41
閱讀 1941·2019-08-26 11:56