轻松实现javascript数据双向绑定


Posted in Javascript onNovember 11, 2015

双向数据绑定指的是当对象的属性发生变化时能够同时改变对应的UI,反之亦然。换句话说,如果我们有一个user对象,这个对象有一个name属性,无论何时你对user.name设置了一个新值,UI也会展示这个新的值。同样的,如果UI包含一个用于数据用户名字的输入框,输入一个新值也会导致user对象的name属性发生相应的改变。

许多流行的javascript框架,像Ember.js,Angular.js或者KnockoutJS都会把双向数据绑定作为其中的主要特性来宣传。这并不意味着从头开始实现它很难,也不意味着当我们需要这种功能的时候,使用这些框架是我们唯一的选择。内部的潜在思想事实上是相当基础的,实现它可以归纳为以下三点:

  • 我们需要一种方式确定哪个UI元素绑定在哪个属性上。
  • 我们需要监控属性和UI的变化
  • 我们需要把所有绑定的对象和UI元素的变化传播出去。

尽管有好多种方式去实现这几点,一种简单高效的方法是我们通过发布订阅者模式来实现。方法很简单:我们可以使用定制的data属性作为HTML代码中需要绑定的属性。所有的绑定在一起的Javascript对象和DOM元素将会订阅这个发布订阅对象。任何时候我们检测到无论是Javascript对象亦或是HTML的input元素的变化,我们都是把事件代理传递给发布订阅对象,然后通过它把所有发生在绑定的对象和元素的的变化传递和广播出去。

一个用jQuery实现的简单例子

通过jQuery实现我们上面讨论的东西是相当简单明了的,因为作为一个流行的库,它让我们很简单的实现订阅和发布DOM事件,同时我们也可以定制一个:

function DataBinder(object_id){
  // Use a jQuery object as simple PubSub
  var pubSub=jQuery({});

  // We expect a `data` element specifying the binding
  // in the form:data-bind-<object_id>="<property_name>"
  var data_attr="bind-"+object_id,
    message=object_id+":change";

  // Listen to chagne events on elements with data-binding attribute and proxy
  // then to the PubSub, so that the change is "broadcasted" to all connected objects
  jQuery(document).on("change","[data-]"+data_attr+"]",function(eve){
    var $input=jQuery(this);

    pubSub.trigger(message,[$input.data(data_attr),$input.val()]);
  });

  // PubSub propagates chagnes to all bound elemetns,setting value of
  // input tags or HTML content of other tags
  pubSub.on(message,function(evt,prop_name,new_val){
    jQuery("[data-"+data_attr+"="+prop_name+"]").each(function(){
      var $bound=jQuery(this);

      if($bound.is("")){
        $bound.val(new_val);
      }else{
        $bound.html(new_val);
      }
    });
  });
  return pubSub;
}

至于javascript对象,下面是最小化的user数据模型实现的例子:

function User(uid){
  var binder=new DataBinder(uid),
    
    user={
      attributes:{},
      // The attribute setter publish changes using the DataBinder PubSub
      set:function(attr_name,val){
        this.attributes[attr_name]=val;
        binder.trigger(uid+":change",[attr_name,val,this]);
      },

      get:function(attr_name){
        return this.attributes[attr_name];
      },
    
      _binder:binder
    };

  // Subscribe to PubSub
  binder.on(uid+":change",function(evt,attr_name,new_val,initiator){
    if(initiator!==user){
      user.set(attr_name,new_val);
    }
  });

  return user;
}

现在,无论何时我们想要绑定一个对象的属性到UI上,我们只要在对应的HTML元素上设置合适的data属性。

// javascript 
var user=new User(123);
user.set("name","Wolfgang");

// html
<input type="number" data-bind-123="name" />

input输入框上值得变化会自动的映射到user的name属性,反之亦然。大功告成!

不需要jQuery的实现方式

现在的大部分项目一般jQuery都已经在使用啦,所以上面的例子是完全可以接受的。但是如果我们需要完全不依赖jQuery,那么该怎么实现呢?好吧,事实上其实也不难办到(特别是当我们把对IE的支持只提供IE8以上的支持)。最后,我们只是要通过发布订阅者模式来观察DOM事件而已。

function DataBinder( object_id ) {
 // Create a simple PubSub object
 var pubSub = {
  callbacks: {},

  on: function( msg, callback ) {
   this.callbacks[ msg ] = this.callbacks[ msg ] || [];
   this.callbacks[ msg ].push( callback );
  },

  publish: function( msg ) {
   this.callbacks[ msg ] = this.callbacks[ msg ] || []
   for ( var i = 0, len = this.callbacks[ msg ].length; i < len; i++ ) {
    this.callbacks[ msg ][ i ].apply( this, arguments );
   }
  }
 },

 data_attr = "data-bind-" + object_id,
 message = object_id + ":change",

 changeHandler = function( evt ) {
  var target = evt.target || evt.srcElement, // IE8 compatibility
    prop_name = target.getAttribute( data_attr );

  if ( prop_name && prop_name !== "" ) {
   pubSub.publish( message, prop_name, target.value );
  }
 };

 // Listen to change events and proxy to PubSub
 if ( document.addEventListener ) {
  document.addEventListener( "change", changeHandler, false );
 } else {
  // IE8 uses attachEvent instead of addEventListener
  document.attachEvent( "onchange", changeHandler );
 }

 // PubSub propagates changes to all bound elements
 pubSub.on( message, function( evt, prop_name, new_val ) {
var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"),
  tag_name;

for ( var i = 0, len = elements.length; i < len; i++ ) {
 tag_name = elements[ i ].tagName.toLowerCase();

 if ( tag_name === "input" || tag_name === "textarea" || tag_name === "select" ) {
  elements[ i ].value = new_val;
 } else {
  elements[ i ].innerHTML = new_val;
 }
}
 });

 return pubSub;
}

数据模型可以保持不变,除了在setter中对jQuery中trigger方法的调用,我们可以通过我们在PubSub中自定义的publish方法来代替。

// In the model's setter:
function User( uid ) {
 // ...

 user = {
  // ...
  set: function( attr_name, val ) {
     this.attributes[ attr_name ] = val;
     // Use the `publish` method
     binder.publish( uid + ":change", attr_name, val, this );
  }
 }

 // ...
}

通过实例讲解,并又一次通过一百行不到,又可维护的纯javascript完成了我们想要的结果,希望对大家实现javascript数据双向绑定有所帮助。

Javascript 相关文章推荐
jMessageBox 基于jQuery的窗口插件
Dec 09 Javascript
jQuery与其它库冲突的解决方法
Jun 25 Javascript
JQuery中对服务器控件 DropdownList, RadioButtonList, CheckboxList的操作总结
Jun 28 Javascript
JavaScript高级程序设计 读书笔记之十 本地对象Date日期
Feb 27 Javascript
jQuery浏览器CSS3特写兼容实例
Jan 19 Javascript
微信小程序--组件(swiper)详细介绍
Jun 13 Javascript
JS实现的按钮点击颜色切换功能示例
Oct 19 Javascript
微信小程序数字滚动插件使用详解
Feb 02 Javascript
利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法
Mar 29 Javascript
vue-router的两种模式的区别
May 30 Javascript
JS控制只能输入数字并且最多允许小数点两位
Nov 24 Javascript
Layui弹框中数据表格中可双击选择一条数据的实现
May 06 Javascript
谈谈对offsetleft兼容性的理解
Nov 11 #Javascript
详解 javascript中offsetleft属性的用法
Nov 11 #Javascript
jquery事件的ready()方法使用详解
Nov 11 #Javascript
浅谈使用MVC模式进行JavaScript程序开发
Nov 10 #Javascript
每天一篇javascript学习小结(基础知识)
Nov 10 #Javascript
jQuery+CSS3实现3D立方体旋转效果
Nov 10 #Javascript
JavaScript中利用各种循环进行遍历的方式总结
Nov 10 #Javascript
You might like
乐信RP2100的电路分析和打磨
2021/03/02 无线电
怎样辨别一杯好咖啡
2021/03/03 新手入门
php 将字符串按大写字母分隔成字符串数组
2010/04/30 PHP
PHP,ASP.JAVA,JAVA代码格式化工具整理
2010/06/15 PHP
phpMyAdmin 链接表的附加功能尚未激活的问题
2010/08/01 PHP
浅析php中三个等号(===)和两个等号(==)的区别
2013/08/06 PHP
利用PHP脚本在Linux下用md5函数加密字符串的方法
2015/06/29 PHP
PHP+redis实现添加处理投票的方法
2015/11/14 PHP
深入解析WordPress中加载模板的get_template_part函数
2016/01/11 PHP
Yii全局函数用法示例
2017/01/22 PHP
JavaScript输入邮箱自动提示实例代码
2014/01/13 Javascript
jquery实现用户信息修改验证输入方法汇总
2015/07/18 Javascript
使用jsonp实现跨域获取数据实例讲解
2016/12/25 Javascript
jQuery插件zTree实现删除树子节点的方法示例
2017/03/08 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
js判断文件类型大小并给出提示的实现方法
2018/01/03 Javascript
浅谈webpack组织模块的原理
2018/03/10 Javascript
微信小程序实现用table显示数据库反馈的多条数据功能示例
2019/05/07 Javascript
[05:15]DOTA2英雄梦之声_第16期_灰烬之灵
2014/06/21 DOTA
[53:29]完美世界DOTA2联赛循环赛 DM vs Matador BO2第二场 11.04
2020/11/05 DOTA
python原始套接字编程示例分享
2014/02/21 Python
python实现删除文件与目录的方法
2014/11/10 Python
python实现井字棋游戏
2020/03/30 Python
python实现发送邮件功能代码
2017/12/14 Python
python字符串Intern机制详解
2019/07/01 Python
python中for循环变量作用域及用法详解
2019/11/05 Python
python每5分钟从kafka中提取数据的例子
2019/12/23 Python
详解用Pytest+Allure生成漂亮的HTML图形化测试报告
2020/03/31 Python
Python命令行参数定义及需要注意的地方
2020/11/30 Python
纯css3实现走马灯效果
2014/12/26 HTML / CSS
初中生自我评价
2014/02/01 职场文书
男方婚礼答谢词
2015/01/20 职场文书
赔偿协议书怎么写
2015/01/28 职场文书
2015年度内部审计工作总结
2015/05/20 职场文书
功夫熊猫观后感
2015/06/10 职场文书
大学生,三分钟即兴演讲稿
2019/07/22 职场文书