html或原生js是单一对应绑定的,原生js数据绑定
雙向數據綁定是非常重要的特性 —— 將JS模型與HTML視圖對應,能減少模板編譯時間同時提高用戶體驗。我們將學習在不使用框架的情況下,使用原生JS實現雙向綁定 —— 一種為Object.observe_(譯注:現已廢棄,作者寫博客時為14年11月),另一種為覆蓋get / set。PS: 第二種更好,詳情請參閱底部的TL;DR(譯注:too long;don't read. 直譯為“太長,不想看”,意譯為“簡單粗暴來吧”)_。
1: Object.observe 和 DOM.onChange
Object.observe()是一種新特性,其在ES7中實現,但在最新的Chrome中已可用 —— 允許對JS對象進行響應式更新。簡單說就是 —— 只要對象(的屬性)發生變化就調用回調函數。
一般用法為:
log = console.log
user = {}
Object.observe(user, function(changes){
changes.forEach(function(change) {
user.fullName = user.firstName + " " + user.lastName;
});
});
user.firstName = 'Bill';
user.lastName = 'Clinton';
user.fullName // 'Bill Clinton'
復制代碼
這很方便,且能實現響應式編程 —— 保證所有內容都是最新的。
如下:
//
user = {};
div = $("#foo");
Object.observe(user, function(changes){
changes.forEach(function(change) {
var fullName = (user.firstName || "") + " " + (user.lastName || "");
div.text(fullName);
});
});
user.firstName = 'Bill';
user.lastName = 'Clinton';
div.text() //Bill Clinton
復制代碼
如上,我們自己實現了模型到數據的綁定!封裝一下(譯注:此處原文為Let’s DRY ourselves with a helper function. DRY即 don't repeat yourself):
//
function bindObjPropToDomElem(obj, property, domElem) {
Object.observe(obj, function(changes){
changes.forEach(function(change) {
$(domElem).text(obj[property]);
});
});
}
user = {};
bindObjPropToDomElem(user,'name',$("#foo"));
user.name = 'William'
$("#foo").text() //'William'
復制代碼
換一種方式 —— 將DOM元素與JS值綁定起來。簡單的方法是使用jQuery.change
//
$("#foo").val("");
function bindDomElemToObjProp(domElem, obj, propertyName) {
$(domElem).change(function() {
obj[propertyName] = $(domElem).val();
alert("user.name is now "+user.name);
});
}
user = {}
bindDomElemToObjProp($("#foo"), user, 'name');
//enter 'obama' into input
user.name //Obama.
復制代碼
簡直不要太方便,在實際開發時,可以將兩者結合,通過函數來創建一個雙向數據綁定:
function bindObjPropToDomElem(obj, property, domElem) {
Object.observe(obj, function(changes){
changes.forEach(function(change) {
$(domElem).text(obj[property]);
});
});
}
function bindDomElemToObjProp(obj, propertyName, domElem) {
$(domElem).change(function() {
obj[propertyName] = $(domElem).val();
console.log("obj is", obj);
});
}
function bindModelView(obj, property, domElem) {
bindObjPropToDomElem(obj, property, domElem)
bindDomElemToObjProp(obj, propertyName, domElem)
}
復制代碼
注意:在雙向綁定時,需正確進行DOM操作,因為不同的DOM元素(input,div,textarea,select)有不同的取值方式(text,val)。同時注意:雙向數據綁定并不是必須的 —— “輸出型”元素一般不需要視圖到模型的綁定,而“輸入型”元素一般不需要模型到視圖的綁定。
下面為第二種方式:
2: 深入'get'和'set'屬性
上面的解決方法并不完美。比如直接的修改并不會自動觸發jQuery的“change”事件 —— 例如,直接通過代碼對DOM進行修改,比如以下代碼不起作用:
$("#foo").val('Putin')
user.name //still Obama. Oops.
復制代碼
現在,我們來用一種更激進的方式實現 —— 重寫getter和setter。因為我們不僅要監測變化,我們將重寫JS最底層的功能,即get/setting變量的能力,所以不那么“安全”。后面我們將會看到,這種元編程的方式有多強大。
那么,如果我們可以重寫get和set對象值的方法會怎么樣呢?這也是數據綁定的實質。用 Object.defineProperty() 即可實現.
其實,以前就有已廢棄且非標準實現方式,但通過Object.defineProperty的實現方式更好(最重要的是標準),如下所示:
user = {}
nameValue = 'Joe';
Object.defineProperty(user, 'name', {
get: function() { return nameValue },
set: function(newValue) { nameValue = newValue; },
configurable: true //to enable redefining the property later
});
user.name //Joe
user.name = 'Bob'
user.name //Bob
nameValue //Bob
復制代碼
現在user.name是nameValue的別名。但可做的不僅僅是創建新的變量名 - 我們可以通過它來保證模型和視圖的一致。如下:
//
Object.defineProperty(user, 'name', {
get: function() { return document.getElementById("foo").value },
set: function(newValue) { document.getElementById("foo").value = newValue; },
configurable: true //to enable redefining the property later
});
復制代碼
user.name現在綁定到#foo元素。這種底層的方式非常簡潔 —— 通過定義(或擴展)變量屬性的get / set實現。由于實現非常簡潔,因此可以根據情況輕松擴展/修改代碼 —— 僅綁定或擴展get / set中的一個,比如綁定其他數據類型。
可封裝如下:
function bindModelInput(obj, property, domElem) {
Object.defineProperty(obj, property, {
get: function() { return domElem.value; },
set: function(newValue) { domElem.value = newValue; },
configurable: true
});
}
復制代碼
使用:
user = {};
inputElem = document.getElementById("foo");
bindModelInput(user,'name',inputElem);
user.name = "Joe";
alert("input value is now "+inputElem.value) //input is now 'Joe';
inputElem.value = 'Bob';
alert("user.name is now "+user.name) //model is now 'Bob';
復制代碼
注意:上面的domElem.value只對input元素有效。(可在bindModelInput中擴展,對不同的DOM類型使用對應的方法來設置它的值)。
思考:
注意:上面的實現中,在某些場景下,視圖可認為是符合SPOT (single point of truth )原則的,但該原則常常被忽視(因為雙向數據綁定也就意味著等價)。然而,深究下去可能就會發現問題了,在實際開發中也會遇到。 —— 比如,當刪除DOM元素時,關聯的模型會自動注銷么?答案是不會。bindModelInput函數在domElem元素上創建了一個閉包,使DOM元素常駐在內存中
—— 并保持模型與模型的綁定關系 —— 即使DOM元素被移除。即使視圖被移除了,但模型依舊存在。反之一樣 —— 若模型被移除了,視圖依然能夠正常顯示。在某些刷新視圖和模型無效的情況下,理解這些內部原理就能找到原因了。
(譯注:SPOT簡單翻譯為“單點原則”,即引起變化最好的是由單一入口引起的,而不是由多個入口引起的,比如一個函數,其返回結果最好僅由參數決定,這樣輸入和輸出才能一致,而不會由于其他變化導致用一個輸入會出現不同的輸出)
這種自己實現的數據綁定方法與Knockout或Angular等框架的數據綁定相比,有一些優點,例如:
理解:一旦掌握數據綁定的源碼,不僅理解更深入,而且也能對其進行擴展和修改。
性能:不要將所有東西都綁定在一起,只綁定所需的,避免監測過多對象
避免鎖定:若所用的框架不支持數據綁定,則自行實現的數據綁定更強大
缺點是由于不是真正的綁定(沒有臟檢查),有些情況會失敗 —— 視圖更新時不會觸發模型中的數據,所以當試著同步視圖中的兩個DOM元素時將會失敗。也就是說,將兩個元素綁定到同一個模型上時,只有更新模型,則兩個元素才會被正確更新。可以通過自定義一個更新函數來實現:
//
//
input1 = document.getElementById('input1')
input2 = document.getElementById('input2')
user = {}
Object.defineProperty(user, 'name', {
get: function() { return input1.value; },
set: function(newValue) { input1.value = newValue; input2.value = newValue; },
configurable: true
});
input1.onchange = function() { user.name = user.name } //sync both inputs.
復制代碼
TL;DR:
當需要使用原生JS創建模型和視圖的雙向數據綁定時,如下:
function bindModelInput(obj, property, domElem) {
Object.defineProperty(obj, property, {
get: function() { return domElem.value; },
set: function(newValue) { domElem.value = newValue; },
configurable: true
});
}
//
user = {}
bindModelInput(user,'name',document.getElementById('foo')); //hey presto, we now have two-way data binding.
復制代碼
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的html或原生js是单一对应绑定的,原生js数据绑定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ensp删除所有命令_HCIA学习笔记—
- 下一篇: 妖帝q群机器人_有关酷Q 晨风机器人,契