轻松实现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 相关文章推荐
node.js调用C++开发的模块实例
Jul 03 Javascript
jQuery滚动加载图片实现原理
Dec 14 Javascript
javascript中eval解析JSON字符串
Feb 27 Javascript
Bootstrap零基础入门教程(二)
Jul 18 Javascript
Javascript之面向对象--封装
Dec 02 Javascript
Ajax和Comet技术总结
Feb 19 Javascript
JS如何判断浏览器类型和详细区分IE各版本浏览器
Mar 04 Javascript
jQuery EasyUI之验证框validatebox实例详解
Apr 10 jQuery
jquery.picsign图片标注组件实例详解
Feb 02 jQuery
详解vue-cli+es6引入es5写的js(两种方法)
Apr 19 Javascript
浅析vue中的provide / inject 有什么用处
Nov 10 Javascript
如何使用webpack打包一个库library的方法步骤
Dec 18 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
PHP4中实现动态代理
2006/10/09 PHP
Windows下IIS6/Apache2.2.4+MySQL5.2+PHP5.2.1安装配置方法
2007/05/03 PHP
Linux下安装oracle客户端并配置php5.3
2014/10/12 PHP
PHP魔术方法的使用示例
2015/06/23 PHP
php和html的区别点详细总结
2019/09/24 PHP
laravel 解决Validator使用中出现的问题
2019/10/25 PHP
javascript显示选择目录对话框的代码
2008/11/10 Javascript
让input框实现类似百度的搜索提示(基于jquery事件监听)
2014/01/31 Javascript
jQuery实现鼠标滑过Div层背景变颜色的方法
2015/02/17 Javascript
JavaScript判断数组是否存在key的简单实例
2016/08/03 Javascript
Bootstrap导航条鼠标悬停下拉菜单
2017/01/04 Javascript
javascript构造函数以及原型对象的理解
2017/01/13 Javascript
js实现带缓动动画的导航栏效果
2017/01/16 Javascript
Mac系统下Webstorm快捷键整理大全
2017/05/28 Javascript
JS简单实现点击跳转登陆邮箱功能的方法
2017/10/31 Javascript
vue mint-ui tabbar变组件使用
2018/05/04 Javascript
利用jsonp解决js读取本地json跨域的问题
2018/12/11 Javascript
Python下singleton模式的实现方法
2014/07/16 Python
Python实现给文件添加内容及得到文件信息的方法
2015/05/28 Python
Python的Django框架中URLconf相关的一些技巧整理
2015/07/18 Python
Python构造自定义方法来美化字典结构输出的示例
2016/06/16 Python
利用Python实现命令行版的火车票查看器
2016/08/05 Python
Collatz 序列、逗号代码、字符图网格实例
2017/06/22 Python
批量将ppt转换为pdf的Python代码 只要27行!
2018/02/26 Python
使用python画个小猪佩奇的示例代码
2018/06/06 Python
keras 自定义loss model.add_loss的使用详解
2020/06/22 Python
Pycharm导入anaconda环境的教程图解
2020/07/31 Python
Python如何在bool函数中取值
2020/09/21 Python
用python写PDF转换器的实现
2020/10/29 Python
Python Pandas list列表数据列拆分成多行的方法实现
2020/12/14 Python
意大利团购网站:Groupon意大利
2016/10/11 全球购物
欢迎新生标语
2014/10/06 职场文书
员工离职感谢信
2015/01/22 职场文书
入党积极分子党小组意见
2015/06/02 职场文书
六一亲子活动感想
2015/08/07 职场文书
多属性、多分类MySQL模式设计
2021/04/05 MySQL