雙向數(shù)據(jù)綁定簡述
雙向數(shù)據(jù)綁定,可以將JS對(duì)象的屬性綁定到DOM節(jié)點(diǎn)上活箕,實(shí)現(xiàn)JS對(duì)象跟DOM節(jié)點(diǎn)的同名屬性的關(guān)聯(lián),改變一方時(shí)可款,另一方也會(huì)得到更新育韩。
雙向數(shù)據(jù)綁定的思想大致如下:
一、將DOM節(jié)點(diǎn)的屬性跟JS對(duì)象的屬性建立關(guān)聯(lián)
二闺鲸、監(jiān)聽JS屬性跟DOM元素的變化
三筋讨、同時(shí)修改JS對(duì)象跟DOM元素
常見的實(shí)現(xiàn)數(shù)據(jù)綁定的做法有如下幾種:
一、發(fā)布-訂閱模式(backbone.js)
二摸恍、臟值檢查(angular.js)
三悉罕、數(shù)據(jù)劫持(vue.js)
發(fā)布訂閱模式實(shí)現(xiàn)
發(fā)布訂閱模式詳見這篇文章,原理是一種一對(duì)多的關(guān)系立镶,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽發(fā)布者對(duì)象壁袄,當(dāng)發(fā)布者發(fā)生改變時(shí),所有觀察者也會(huì)得到通知媚媒。
實(shí)現(xiàn)原理
通過發(fā)布訂閱模式實(shí)現(xiàn)數(shù)據(jù)雙向綁定的原理如下:
一嗜逻、當(dāng)model發(fā)送改變時(shí),觸發(fā)model change事件缭召,然后通過相應(yīng)的事件處理函數(shù)更新栈顷。
二、當(dāng)界面更新時(shí)恼琼,觸發(fā)UI change事件妨蛹,然后通過相應(yīng)的事件處理函數(shù)更新model,以及綁定在model上的其他界面控件晴竞。
依據(jù)這個(gè)思路,可以定義ui-update-event和model-update-event兩個(gè)事件狠半。下面將分別介紹噩死。
具體實(shí)現(xiàn)
直接上代碼~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>發(fā)布訂閱模式實(shí)現(xiàn)數(shù)據(jù)雙向綁定</title>
<style>
#inputId {
border:1px solid #ccc;
width:200px;
height:24px;
}
#modelView {
border:1px solid black;
width:200px;
height:24px;
margin-top:20px;
margin-bottom:20px;
}
</style>
</head>
<body>
<input type="text" id="inputId" d-binding="user.name"/>
<div id="modelView" d-binding="user.name"></div>
<button id="btn">model的變化導(dǎo)致view的變化</button>
<script>
// 發(fā)布訂閱原型
var pubSub = {
allCallbacks: [],
// 增加訂閱者
on: function(eventName, callback) {
// 如果沒有訂閱過該消息,給這個(gè)消息創(chuàng)建一個(gè)緩存列表
if(!this.allCallbacks[eventName]) {
this.allCallbacks[eventName] = [];
}
this.allCallbacks[eventName].push(callback);
},
// 發(fā)布消息
public: function() {
var eventName = Array.prototype.shift.call(arguments);
// 取出該消息對(duì)應(yīng)的回調(diào)函數(shù)集合
var callbacks = this.allCallbacks[eventName];
if (!callbacks || callbacks.length === 0) {
return false;
}
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
callback.apply(this, arguments);
}
}
};
var DataBinder = (function () {
function changeHandler(e) {
var target = e.target || e.srcElement;
var attrName = target.getAttribute("d-binding");
if (attrName && attrName !== "") {
// 發(fā)布消息
pubSub.public("ui-update-event", attrName, target.value);
}
};
// 監(jiān)聽視圖層的事件變化
if (document.addEventListener) {
document.addEventListener('keyup', changeHandler, false);
document.addEventListener('change', changeHandler, false);
} else {
document.attachEvent("onkeyup", changeHandler);
document.attachEvent("onchange", changeHandler);
}
// 監(jiān)聽模型上的變化神年,并把變化傳播到所有綁定的元素上
pubSub.on("model-update-event", function(attrName, newVal) {
var elements = document.querySelectorAll('[d-binding="' + attrName + '"]');
var tagName;
for (var i = 0, ilen = elements.length; i < ilen; i++) {
tagName = elements[i].tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
elements[i].value = newVal;
} else {
elements[i].innerHTML = newVal;
}
}
});
return {
modelName : "",
initModel : function (modelName) {
var self = this;
self.modelName = modelName;
pubSub.on("ui-update-event", function(attrName, propValue){
var propPathArr = attrName.split(".");
self.updateModelData(propPathArr[1], propValue);
});
return Object.create(this);
},
loadModelData : function (modelData) {
for (prop in modelData) {
this.updateModelData(prop, modelData[prop]);
}
},
updateModelData : function (propName, propValue) {
eval(this.modelName)[propName] = propValue;
pubSub.public("model-update-event", this.modelName + '.' + propName, propValue);
}
}
})();
var user = DataBinder.initModel("user");
user.loadModelData({
'name' : 1
});
// 測(cè)試模型的變化到 視圖層的變化
var btn = document.getElementById("btn");
var inputId = document.getElementById("inputId");
btn.onclick = function() {
var value = inputId.value;
user.updateModelData("name", parseInt(value) + 1);
};
</script>
</body>
</html>
ui-update-event事件
對(duì)于所有支持雙向綁定的頁面控件已维,當(dāng)值發(fā)生改變時(shí),就會(huì)觸發(fā)ui-update-event事件更新model已日,以及綁定在model上的其他控件垛耳。
觸發(fā)ui-update-event時(shí),先執(zhí)行
pubSub.on("ui-update-event", function(attrName, propValue){
var propPathArr = attrName.split(".");
self.updateModelData(propPathArr[1], propValue);
});
通過updateModelData方法去執(zhí)行model-update-event,從而更新model堂鲜。
updateModelData : function (propName, propValue) {
eval(this.modelName)[propName] = propValue;
pubSub.public("model-update-event", this.modelName + '.' + propName, propValue);
model-update-event
對(duì)于model這一層栈雳,當(dāng)model發(fā)生改變時(shí),會(huì)觸發(fā)model-update-event的監(jiān)聽事件
pubSub.on("model-update-event", function(attrName, newVal) {
var elements = document.querySelectorAll('[d-binding="' + attrName + '"]');
var tagName;
for (var i = 0, ilen = elements.length; i < ilen; i++) {
tagName = elements[i].tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
elements[i].value = newVal;
} else {
elements[i].innerHTML = newVal;
}
}
});
從而修改了DOM元素的值缔莲。
屬性劫持
Object.defineProperty()方法直接在對(duì)象上定義一個(gè)新屬性哥纫,或修改對(duì)象上的現(xiàn)有屬性,并返回該對(duì)象痴奏。
關(guān)于Object.defineProperty()的介紹如下:
Object.defineProperty(obj, prop, descriptor)
參數(shù)
obj:定義屬性的對(duì)象
prop:要定義或修改的屬性的名稱蛀骇。
descriptor:定義或修改屬性的描述符。
返回值:傳遞給函數(shù)的對(duì)象读拆。
注意:數(shù)據(jù)描述符和訪問器描述符擅憔,不能同時(shí)存在(value,writable 和 get,set)
get:函數(shù)return將被用作屬性的值。
set:該函數(shù)將僅接收參數(shù)賦值給該屬性的新值檐晕。(在屬性改變時(shí)調(diào)用)
使用Object.defineProperty()實(shí)現(xiàn)雙向數(shù)據(jù)綁定
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用Object.defineProperty實(shí)現(xiàn)簡單的雙向數(shù)據(jù)綁定</title>
</head>
<body>
<input type="text" id="input" />
<div id="div"></div>
<script>
var obj = {};
var inputVal = document.getElementById("input");
var div = document.getElementById("div");
Object.defineProperty(obj, "name", {
set: function(newVal) {
inputVal.value = newVal;
div.innerHTML = newVal;
}
});
inputVal.addEventListener('input', function(e){
obj.name = e.target.value;
});
</script>
</body>
</html>
當(dāng)在input輸入框輸入值的時(shí)候暑诸,div也會(huì)顯示對(duì)應(yīng)的值,實(shí)現(xiàn)了UI更改model的效果~~~
當(dāng)在控制臺(tái)輸入 obj.name="輸入任意值"并按回車鍵運(yùn)行時(shí)棉姐,input輸入框的值也會(huì)跟著變屠列,這就實(shí)現(xiàn)了model更改UI的效果~~~
可見,Object.defineProperty()實(shí)現(xiàn)雙向綁定比發(fā)布訂閱模式簡單得多~~~
臟值檢查
是通過臟值檢測(cè)的方式比對(duì)數(shù)據(jù)是否有變更伞矩,來決定是否更新視圖笛洛,最簡單的方式就是通過 setInterval()定時(shí)輪詢檢測(cè)數(shù)據(jù)的變動(dòng)。
臟值檢查實(shí)現(xiàn)較為復(fù)雜乃坤,暫時(shí)沒時(shí)間進(jìn)行研究~~~