使用原生JavaScript实现数据绑定
发布在每天学点javascript2015年8月7日view:10725MVVM
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

使用原生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:改变getset

除了上面提到的使用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

评论
发表评论
1年前
添加了一枚【评注】:拼错了哦
2年前
赞了此文章!
2年前
赞了此文章!
2年前
添加了一枚【评注】:name -> firstname
2年前
赞了此文章!
3年前
赞了此文章!
3年前
添加了一枚【评注】:getter都写错。。四级没过吧?
3年前

@xiaorong61 我说怎么不对呢 谢谢啦

3年前
添加了一枚【评注】:结果是firstname,不是‘张小俊128’
3年前
添加了一枚【评注】:结果是firstname,不是‘张小俊128’
3年前

应该是 var log=console.log.bind(console)

3年前

@司徒正美 就是set和get这点东西支撑着整个框架?

3年前

新的评论功能很有新意 但是和想点击拷贝页面上的关键字的操作有点冲突了 ,能不能考虑 只在空白点击出现评论 而在文字上点击则不出现提示?

3年前
赞了此文章!
3年前
赞了此文章!
3年前

argular用的是脏值检查。

3年前

学习了!

3年前

avalon就是用set, get实现双向绑定的,旧式IE使用VBScript

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
每天学点javascript

javascript进阶级教程,循序渐进掌握javascript

我的收藏