实现非常简单的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 相关文章推荐
Javascript实现的分页函数
Feb 07 Javascript
javascript获取设置div的高度和宽度兼容任何浏览器
Sep 22 Javascript
JQuery事件e参数的方法preventDefault()取消默认行为
Sep 26 Javascript
倒记时60刷新网页的js代码
Feb 18 Javascript
Javascript快速排序算法详解
Dec 03 Javascript
jQuery地图map悬停显示省市代码分享
Aug 20 Javascript
JS实现上下左右对称的九九乘法表
Feb 22 Javascript
jQuery使用模式窗口实现在主页面和子页面中互相传值的方法
Mar 01 Javascript
详解Vue.js在页面加载时执行某个方法
Nov 20 Javascript
layui button 按钮弹出提示窗口,确定才进行的方法
Sep 06 Javascript
vue 解决mintui弹窗弹起来,底部页面滚动bug问题
Nov 12 Javascript
80行代码写一个Webpack插件并发布到npm
May 24 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_Flame(Version:Progress)的原代码
2006/10/09 PHP
php 图片加水印与上传图片加水印php类
2010/05/12 PHP
php购物网站支付paypal使用方法
2010/11/28 PHP
解析阿里云ubuntu12.04环境下配置Apache+PHP+PHPmyadmin+MYsql
2013/06/26 PHP
PHP开发框架Laravel数据库操作方法总结
2014/09/03 PHP
PDO::rollBack讲解
2019/01/29 PHP
PHP echo()函数讲解
2019/02/15 PHP
几款极品的javascript压缩混淆工具
2007/05/16 Javascript
JS中getYear()和getFullYear()区别分析
2014/07/04 Javascript
javascript+html5实现仿flash滚动播放图片的方法
2015/04/27 Javascript
jQuery实现伪分页的方法分享
2016/02/17 Javascript
微信小程序点击控件修改样式实例详解
2017/07/07 Javascript
利用javascript如何随机生成一定位数的密码
2017/09/22 Javascript
vue 虚拟dom的patch源码分析
2018/03/01 Javascript
html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】
2019/09/10 jQuery
微信小程序实现轨迹回放的示例代码
2019/12/13 Javascript
json_decode 索引为数字时自动排序问题解决方法
2020/03/28 Javascript
vue-路由精讲 二级路由和三级路由的作用
2020/08/06 Javascript
Vue前端判断数据对象是否为空的实例
2020/09/02 Javascript
浅谈nuxtjs校验登录中间件和混入(mixin)
2020/11/06 Javascript
[38:51]2014 DOTA2国际邀请赛中国区预选赛 Orenda VS LGD-CDEC
2014/05/22 DOTA
[00:43]魔廷新尊——痛苦女王至宝捆绑包
2020/06/12 DOTA
python实现排序算法
2014/02/14 Python
Python pickle类库介绍(对象序列化和反序列化)
2014/11/21 Python
Python 通过URL打开图片实例详解
2017/06/01 Python
python定时按日期备份MySQL数据并压缩
2019/04/19 Python
使用 Python 合并多个格式一致的 Excel 文件(推荐)
2019/12/09 Python
python argparse模块通过后台传递参数实例
2020/04/20 Python
pyecharts在数据可视化中的应用详解
2020/06/08 Python
基于python获取本地时间并转换时间戳和日期格式
2020/10/27 Python
HTML5 File接口在web页面上使用文件下载
2017/02/27 HTML / CSS
新大陆软件面试题
2016/11/24 面试题
2015年学生会干事工作总结
2015/04/09 职场文书
想创业成功,需要掌握这些要点
2019/12/06 职场文书
golang正则之命名分组方式
2021/04/25 Golang
解决Oracle数据库用户密码过期
2022/05/11 Oracle