轻松实现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 相关文章推荐
javascript attachEvent和addEventListener使用方法
Mar 19 Javascript
一个网马的tips实现分析
Nov 28 Javascript
JS 日期比较大小的简单实例
Jan 13 Javascript
jQuery.extend()、jQuery.fn.extend()扩展方法示例详解
May 08 Javascript
举例讲解jQuery对DOM元素的向上遍历、向下遍历和水平遍历
Jul 07 Javascript
Ajax使用原生态JS验证用户名是否存在
May 26 Javascript
react-native使用react-navigation进行页面跳转导航的示例
Sep 07 Javascript
vue监听对象及对象属性问题
Aug 20 Javascript
原生JS实现DOM加载完成马上执行JS代码的方法
Sep 07 Javascript
angularJs中ng-model-options设置数据同步的方法
Sep 30 Javascript
微信小程序实现元素渐入渐出动画效果封装方法
May 18 Javascript
使用vue3重构拼图游戏的实现示例
Jan 25 Vue.js
谈谈对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
配置支持SSI
2006/11/25 PHP
php array_pop()数组函数将数组最后一个单元弹出(出栈)
2011/07/12 PHP
thinkphp文件处理类Dir.class.php的用法分析
2014/12/08 PHP
kindeditor 加入七牛云上传的实例讲解
2017/11/12 PHP
laravel7学习之无限级分类的最新实现方法
2020/09/30 PHP
禁止拷贝网页内容的js代码
2014/01/22 Javascript
使用jquery.upload.js实现异步上传示例代码
2014/07/29 Javascript
jQuery幻灯片特效代码分享--鼠标滑过按钮时切换(2)
2020/11/18 Javascript
Jquery技巧(必须掌握)
2016/03/16 Javascript
微信小程序 石头剪刀布实例代码
2017/01/04 Javascript
js实现定时进度条完成后切换图片
2017/01/04 Javascript
使用requirejs模块化开发多页面一个入口js的使用方式
2017/06/14 Javascript
基于JavaScript中标识符的命名规则介绍
2018/01/06 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
2018/09/21 Javascript
浅谈vue引用静态资源需要注意的事项
2018/09/28 Javascript
vue使用nprogress实现进度条
2019/12/09 Javascript
Vue+Vuex实现自动登录的知识点详解
2020/03/04 Javascript
如何在面试中手写出javascript节流和防抖函数
2020/10/22 Javascript
在树莓派2或树莓派B+上安装Python和OpenCV的教程
2015/03/30 Python
Python实现将16进制字符串转化为ascii字符的方法分析
2017/07/21 Python
Python和Java进行DES加密和解密的实例
2018/01/09 Python
numpy数组之存取文件的实现示例
2019/05/24 Python
Python学习之os模块及用法
2020/06/03 Python
Python加速程序运行的方法
2020/07/29 Python
Python 实现国产SM3加密算法的示例代码
2020/09/21 Python
jupyter notebook远程访问不了的问题解决方法
2021/01/11 Python
用Python制作音乐海报
2021/01/26 Python
10种CSS3实现的loading动画,挑一个走吧?
2020/11/16 HTML / CSS
模具设计与制造专业应届生求职信
2013/10/18 职场文书
协议书与合同的区别
2014/04/18 职场文书
启动仪式策划方案
2014/06/14 职场文书
教师拔河比赛广播稿
2014/10/14 职场文书
写给孩子的新学期寄语
2015/02/27 职场文书
2015年中秋节演讲稿
2015/03/20 职场文书
Python代码,能玩30多款童年游戏!这些有几个是你玩过的
2021/04/27 Python
Android Rxjava3 使用场景详解
2022/04/07 Java/Android