实现非常简单的js双向数据绑定


Posted in Javascript onNovember 06, 2015

双向数据绑定指的就是,绑定对象属性的改变到用户界面的变化的能力,反之亦然。换种说法,如果我们有一个user对象和一个name属性,一旦我们赋了一个新值给user.name,在UI上就会显示新的姓名了。同样地,如果UI包含了一个输入用户姓名的输入框,输入一个新值就应该会使user对象的name属性做出相应的改变。

很多热门的JS框架客户端如Ember.js,Angular.js 或者KnockoutJS ,都在最新特性上刊登了双向数据绑定。这并不意味着从零实现它很难,也不是说需要这些功能的时候,采用这些框架是唯一的选择。下面的想法实际上很基础,可以被认为是3步走计划:

我们需要一个UI元素和属性相互绑定的方法
我们需要监视属性和UI元素的变化
我们需要让所有绑定的对象和元素都能感知到变化

还是有很多方法能够实现上面的想法,有一个简单有效的方法就是使用PubSub模式。 这个思路很简单:我们使用数据特性来为HTML代码进行绑定,所有被绑定在一起的JavaScript对象和DOM元素都会订阅一个PubSub对象。只要JavaScript对象或者一个HTML输入元素监听到数据的变化时,就会触发绑定到PubSub对象上的事件,从而其他绑定的对象和元素都会做出相应的变化。

用jQuery做一个简单的实现

对于DOM事件的订阅和发布,用jQuery实现起来是非常简单的,接下来我们就是用Jquery比如下面:

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 change events on elements with the data-binding attribute and proxy
 // them to the PubSub, so that the change is "broadcasted" to all connected objects
 jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) {
 var $input = jQuery( this );
 pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] );
 });
 // PubSub propagates changes to all bound elements, 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("input, textarea, select") ) {
  $bound.val( new_val );
  } else {
  $bound.html( new_val );
  }
 });
 });
 return pubSub;
}

对于上面这个实现来说,下面是一个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 the PubSub
 binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) {
 if ( initiator !== user ) {
  user.set( attr_name, new_val );
 }
 });
 return user;
}

现在我们如果想要将User模型属性绑定到UI上,我们只需要将适合的数据特性绑定到对应的HTML元素上。

// javascript
var user = new User( 123 );
user.set( "name", "Wolfgang" );
// html
<input type="number" data-bind-123="name" />

这样输入值会自动映射到user对象的name属性,反之

亦然。到此这个简单实现就完成啦!

不需要jQuery的实现

在如今的大多数项目里,可能已经使用了jQuery,因此上面的例子完全可以接受。不过,如果我们需要试着向另一个极端做,并且还删除对jQuery的依赖,那么怎么做呢?好,证实一下这么做并不难(尤其是在我们限制只支持IE 8及以上版本的情况下)。最终,我们必须使用一般的javascript实现一个定制的PubSub并且保留了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 = , 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, // IE 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 {
 // IE 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 = , 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;
}

除了设置器里调用 jQuery的trigger方法外,模型可以保持一样。调用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完成了我们想要的结果。

以上内容就是关于js双向数据绑定的相关教程,希望对大家学习有所帮助。

Javascript 相关文章推荐
jquery和javascript中如何将一元素的内容赋给另一元素
Jan 09 Javascript
修改或扩展jQuery原生方法的代码实例
Jan 13 Javascript
Node.js本地文件操作之文件拷贝与目录遍历的方法
Feb 16 Javascript
微信jssdk用法汇总
Jul 16 Javascript
AngularJS入门教程引导程序
Aug 18 Javascript
Vue.js每天必学之内部响应式原理探究
Sep 07 Javascript
AngularJS实现在ng-Options加上index的解决方法
Nov 03 Javascript
JavaScript中动态向表格添加数据
Jan 24 Javascript
JavaScript之生成器_动力节点Java学院整理
Jun 30 Javascript
浅谈Vue服务端渲染框架Nuxt的那些事
Dec 21 Javascript
微信小程序实现星级评价效果
Dec 28 Javascript
Node.js API详解之 dns模块用法实例分析
May 15 Javascript
浅析javascript中的事件代理
Nov 06 #Javascript
详解javascript中的事件处理
Nov 06 #Javascript
jQuery插件实现静态HTML验证码校验
Nov 06 #Javascript
jQuery Real Person验证码插件防止表单自动提交
Nov 06 #Javascript
jQuery实现非常实用漂亮的select下拉菜单选择效果
Nov 06 #Javascript
javascript如何实现暂停功能
Nov 06 #Javascript
JavaScript实现带缓冲效果的随屏滚动漂浮广告代码
Nov 06 #Javascript
You might like
PHP syntax error, unexpected $end 错误的一种原因及解决
2008/10/25 PHP
PHP解决URL中文GBK乱码问题的两种方法
2014/06/03 PHP
2个Codeigniter文件批量上传控制器写法例子
2014/07/25 PHP
ThinkPHP模板输出display用法分析
2014/11/26 PHP
WordPress中邮件的一些修改和自定义技巧
2015/12/15 PHP
PHP缓存工具XCache安装与使用方法详解
2018/04/09 PHP
Track Image Loading效果代码分析
2007/08/13 Javascript
jQuery如何取id有.的值一般的方法是取不到的
2014/04/18 Javascript
JavaScript 基本概念
2015/01/20 Javascript
jQuery mobile 移动web(4)
2015/12/20 Javascript
理解javascript正则表达式
2016/03/08 Javascript
使用javascript插入样式
2016/03/14 Javascript
基于BootStrap Metronic开发框架经验小结【九】实现Web页面内容的打印预览和保存操作
2016/05/12 Javascript
jQuery扩展+xml实现表单验证功能的方法
2016/12/25 Javascript
浅谈angularJS的$watch失效问题的解决方案
2017/08/11 Javascript
微信小程序progress组件使用详解
2018/01/31 Javascript
vue点击input弹出带搜索键盘并监听该元素的方法
2018/08/25 Javascript
微信小程序+云开发实现欢迎登录注册
2019/05/24 Javascript
vue的滚动条插件实现代码
2019/09/07 Javascript
uin-app+mockjs实现本地数据模拟
2020/08/26 Javascript
vue 根据选择的月份动态展示日期对应的星期几
2021/02/06 Vue.js
python logging类库使用例子
2014/11/22 Python
在Python的Flask框架中验证注册用户的Email的方法
2015/09/02 Python
python查询mysql,返回json的实例
2018/03/26 Python
Python中跳台阶、变态跳台阶与矩形覆盖问题的解决方法
2018/05/19 Python
python如何解析配置文件并应用到项目中
2019/06/27 Python
CSS3制作日历实现代码
2012/01/21 HTML / CSS
泰国王权免税店官方网站:KingPower
2019/03/11 全球购物
美国最大和最受信任的二手轮胎商店:Bestusedtires.com
2020/06/02 全球购物
医院实习介绍信
2014/01/12 职场文书
项目建议书格式
2014/03/12 职场文书
初三学生个人自我评定
2014/04/06 职场文书
出纳试用期自我鉴定范文
2014/09/16 职场文书
思想品德课教学反思
2016/02/24 职场文书
会议开幕致辞怎么写
2016/03/03 职场文书
Redis调用Lua脚本及使用场景快速掌握
2022/03/16 Redis