轻松实现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 相关文章推荐
js弹出层包含flash 不能关闭隐藏的2种处理方法
Jun 17 Javascript
Jquery获取元素的父容器对象示例代码
Feb 10 Javascript
JavaScript中对循环语句的优化技巧深入探讨
Jun 06 Javascript
jQuery基于扩展简单实现倒计时功能的方法
May 14 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
Feb 16 Javascript
JavaScript中Object基础内部方法图
Feb 05 Javascript
深入浅析Vue.js计算属性和侦听器
May 05 Javascript
Layui点击图片弹框预览的实现方法
Sep 16 Javascript
vue 自定义组件的写法与用法详解
Mar 04 Javascript
Vue优化:常见会导致内存泄漏问题及优化详解
Aug 04 Javascript
JS中队列和双端队列实现及应用详解
Sep 29 Javascript
微信小程序实现文件预览
Oct 22 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
Server.HTMLEncode让代码在页面里显示为源代码
2013/12/08 PHP
php设计模式之简单工厂模式详解
2014/09/04 PHP
PHP+Ajax实时自动检测是否联网的方法
2015/07/01 PHP
详解PHP的Yii框架中日志的相关配置及使用
2015/12/08 PHP
基于PHP实现通过照片获取ip地址
2016/04/26 PHP
jquery ajax return没有返回值的解决方法
2011/10/20 Javascript
JS二维数组的定义说明
2014/03/03 Javascript
快速掌握Node.js之Window下配置NodeJs环境
2016/03/21 NodeJs
javascript实现一个网页加载进度loading
2017/01/04 Javascript
JavaScript中双向数据绑定详解
2017/05/03 Javascript
iview的table组件自带的过滤器实现
2019/07/12 Javascript
基于JavaScript 实现拖放功能
2019/09/12 Javascript
从0搭建vue-cli4脚手架
2020/06/17 Javascript
vue-router之解决addRoutes使用遇到的坑
2020/07/19 Javascript
JavaScript事件循环及宏任务微任务原理解析
2020/09/02 Javascript
Python使用百度API上传文件到百度网盘代码分享
2014/11/08 Python
python中print的不换行即时输出的快速解决方法
2016/07/20 Python
使用django-guardian实现django-admin的行级权限控制的方法
2018/10/30 Python
Window环境下Scrapy开发环境搭建
2018/11/18 Python
Python 使用list和tuple+条件判断详解
2019/07/30 Python
python文字和unicode/ascll相互转换函数及简单加密解密实现代码
2019/08/12 Python
CSS超出文本指定宽度用省略号代替和文本不换行
2016/05/05 HTML / CSS
HTML5 manifest离线缓存的示例代码
2018/08/08 HTML / CSS
英国最大的奢侈珠宝和手表网站:C W Sellors
2017/02/10 全球购物
Sephora丝芙兰马来西亚官方网站:国际化妆品购物
2018/03/15 全球购物
ReVive利维肤美国官网:RéVive Skincare
2018/04/18 全球购物
寻找完美的房车租赁:RVShare
2019/02/23 全球购物
英国的领先快速时尚零售商:In The Style
2019/03/25 全球购物
JSP和EJB可以共享HttpSession么?EJB里面可以改变session里面的内容
2013/06/05 面试题
求职简历的自我评价
2014/01/31 职场文书
驾驶员培训方案
2014/05/01 职场文书
信用卡工作证明范本
2015/06/19 职场文书
呐喊读书笔记
2015/06/30 职场文书
新郎婚礼致辞
2015/07/27 职场文书
nginx刷新页面出现404解决方案(亲测有效)
2022/03/18 Servers
Golang 实现 WebSockets 之创建 WebSockets
2022/04/24 Golang