轻松实现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 相关文章推荐
Discuz! 6.1_jQuery兼容问题
Sep 23 Javascript
JavaScript页面刷新与弹出窗口问题的解决方法
Mar 02 Javascript
Extjs中TabPane如何嵌套在其他网页中实现思路及代码
Jan 27 Javascript
jQuery实现动画效果的实例代码
May 07 Javascript
js取消单选按钮选中并判断对象是否为空
Nov 14 Javascript
js实现select组件的选择输入过滤代码
Oct 14 Javascript
AngularJS中$interval的用法详解
Feb 02 Javascript
JavaScript判断用户名和密码不能为空的实现代码
May 16 Javascript
深入浅析JavaScript函数前面的加号和叹号
Jul 09 Javascript
vue2 中如何实现动态表单增删改查实例
Jun 09 Javascript
vuejs前后端数据交互之从后端请求数据的实例
Aug 11 Javascript
node创建Vue项目步骤详解
Mar 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
CI框架中集成CKEditor编辑器的教程
2014/06/09 PHP
smarty内置函数config_load用法实例
2015/01/22 PHP
PHP借助phpmailer发送邮件
2015/05/11 PHP
PHP中call_user_func_array回调函数的用法示例
2016/11/26 PHP
PHP实现获取url地址中顶级域名的方法示例
2019/06/05 PHP
仿淘宝TAB切换搜索框搜索切换的相关内容
2014/09/21 Javascript
node.js中的fs.chown方法使用说明
2014/12/16 Javascript
jQuery无刷新分页完整实例代码
2015/10/27 Javascript
JS实现列表页面隔行变色效果
2017/03/25 Javascript
简易Vue评论框架的实现(父组件的实现)
2018/01/08 Javascript
nodejs 生成和导出 word的实例代码
2018/07/31 NodeJs
轻松学习JavaScript函数中的 Rest 参数
2019/05/30 Javascript
Node.js API详解之 os模块用法实例分析
2020/05/06 Javascript
浅谈Ant Design Pro 菜单自定义 icon
2020/11/17 Javascript
手动实现vue2.0的双向数据绑定原理详解
2021/02/06 Vue.js
原生JavaScript实现进度条
2021/02/19 Javascript
python 判断一个进程是否存在
2009/04/09 Python
使用Python的urllib2模块处理url和图片的技巧两则
2016/02/18 Python
Python正则表达式实现截取成对括号的方法
2017/01/06 Python
python使用 HTMLTestRunner.py生成测试报告
2017/10/20 Python
PyQt5每天必学之切换按钮
2020/08/20 Python
python 实现多维数组转向量
2019/11/30 Python
解决tensorboard多个events文件显示紊乱的问题
2020/02/15 Python
Expedia泰国:预订机票、酒店和旅游包(航班+酒店)
2016/09/27 全球购物
Nike荷兰官方网站:Nike.com (NL)
2018/04/19 全球购物
英国当代时尚和街头服饰店:18montrose
2018/12/15 全球购物
俄罗斯名牌服装网上商店:UNIQUE FABRIC
2019/07/25 全球购物
自动化专业本科毕业生求职信
2013/10/20 职场文书
竞选学习委员演讲稿
2014/09/01 职场文书
2015年世界艾滋病日活动总结
2015/03/24 职场文书
商务司机岗位职责
2015/04/10 职场文书
入党宣誓大会后的感想
2015/08/10 职场文书
导游词之沈阳植物园
2019/11/30 职场文书
如何利用JavaScript实现二叉搜索树
2021/04/02 Javascript
DSP接收机前端设想
2022/04/05 无线电
微信小程序纯CSS实现无限弹幕滚动效果
2022/09/23 HTML / CSS