使用原生JavaScript实现数据绑定
一直以来,双向数据绑定都是一个非常有用的特性,它能够让你的JS模型和HTML视图保持一致,这样可以大大减少代码量并增强视图功能。在本文中,我们将使用原生JavaScript,而无需使用任何框架,以两种方式来实现双向数据绑定 – 一种方式是使用新技术(Object.observe),另一种是使用目前已有的概念(重载 get/set)。
1:Object.observe && DOM.onchange
对于绝大多数JavaScript API来所,Object.observe() 绝对算得上是镇上新来的姑娘。虽然这个API是ES7中的一部分,但是令人激动的是它已经在Chrome的稳定版本总实现了。它能够允许我们以更具交互性的形式‘来更新一个JS对象的变化。或者说得通俗一些 – 当一个对象(或者它的属性)发生变化时,都会自动触发一个回调函数。
下面是一个最简单的例子:
var log = console.log;
user = {};
Object.observe(user,function(changes){
changes.forEach(function(change){
log(change.name);
log(change.type);
});
});
user.firstname = '张小俊128';
//张小俊123
//add
使用起来非常简单直观,下面是一个稍微复杂一点的例子:
//<input id="foo">
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
太棒了,到这里我们已经实现了模型到视图的数据绑定。现在我们来把复杂的部分抽象成一个辅助函数:
//<input id="foo">
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
:
//<input id="foo">
$("#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');
//在input中输入 'obama'
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元素(input,div,textarea,select)中值的引用方式不同(text,val)。
2:改变get
和set
除了上面提到的使用Object.observe之外,我们还可以使用对象的geteer和setter来实现双向数据绑定。相比Object.observe来说,浏览器目前对于getter和setter的支持程度要好得多。在这里,我们需要使用Object.defineProperty方法。例如下面的例子:
user = {}
nameValue = 'Joe';
Object.defineProperty(user, 'name', {
get: function() { return nameValue },
set: function(newValue) { nameValue = newValue; },
configurable: true // 允许在稍后重定义这个属性
});
user.name //Joe
user.name = 'Bob'
user.name //Bob
nameValue //Bob
在上面的例子中,我们可以将user.name看做是nameValue的一个别名。但是我们可以做到更多,比如说将数据在模型和视图之间进行绑定。例如:
//<input id="foo">
Object.defineProperty(user, 'name', {
get: function() { return document.getElementById("foo").value },
set: function(newValue) { document.getElementById("foo").value = newValue; },
configurable: true // 让我们可以在之后重新定义这个属性
});
现在,user.name
绑定到了input元素#foo
。
总结一下,我们可以像这样来使用set/get方法来实现数据双向绑定:
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元素现在的值是'Joe'
inputElem.value = 'Bob';
alert("user.name is now "+user.name) //现在model中的value是Bob
需要注意的一点是上面的用法并不是真正的绑定,例如有时在一个input发生变化时,将变化反映到另一个绑定到同一个model的input上。这个时候,我们就需要添加一个新的自定义’接触器’。如下面的例子所示:
//<input id='input1'>
//<input id='input2'>
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 } // 同步两个input
总结
上面两种方法都能够实现数据双向绑定,但是目前来说,使用set/get是更好的方式,因为浏览器对set/get的支持度要更好。
本文参考自Native JavaScript Data-Binding,原文地址http://www.sellarafaeli.com/blog/native_javascript_data_binding