实现非常简单的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脚本代码跑起来。
Jan 09 Javascript
纯JAVASCRIPT图表动画插件Highcharts Examples
Apr 16 Javascript
jQuery插件Elastislide实现响应式的焦点图无缝滚动切换特效
Apr 12 Javascript
JQuery CheckBox(复选框)操作方法汇总
Apr 15 Javascript
jQuery UI库中dialog对话框功能使用全解析
Apr 23 Javascript
JS加载iFrame出现空白问题的解决办法
May 13 Javascript
Vue中的Props(不可变状态)
Sep 29 Javascript
video.js 一个页面同时播放多个视频的实例代码
Nov 27 Javascript
详解关于Vuex的action传入多个参数的问题
Feb 22 Javascript
js+html5 canvas实现ps钢笔抠图
Apr 28 Javascript
element-ui 中使用upload多文件上传只请求一次接口
Jul 19 Javascript
Vue 的 v-model用法实例
Nov 23 Vue.js
浅析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下过滤html代码的函数 提高程序安全性
2010/03/02 PHP
在wamp集成环境下升级php版本(实现方法)
2013/07/01 PHP
分享下php5类中三种数据类型的区别
2015/01/26 PHP
PHP获取photoshop写入图片文字信息的方法
2015/03/31 PHP
php返回相对时间(如:20分钟前,3天前)的方法
2015/04/14 PHP
yii框架无限极分类的实现方法
2017/04/08 PHP
表单项的name命名为submit、reset引起的问题
2007/12/22 Javascript
jquery中each遍历对象和数组示例
2014/08/05 Javascript
AngularJS实现星星等级评分功能
2016/09/24 Javascript
JS实现表单多文件上传样式美化支持选中文件后删除相关项
2016/09/30 Javascript
Bootstrap树形菜单插件TreeView.js使用方法详解
2016/11/01 Javascript
js实时监控文本框输入字数的实例代码
2018/01/18 Javascript
Element-UI Table组件上添加列拖拽效果实现方法
2018/04/14 Javascript
es6中reduce的基本使用方法
2019/09/10 Javascript
微信小程序pinker组件使用实现自动相减日期
2020/05/07 Javascript
小程序瀑布流组件实现翻页与图片懒加载
2020/05/19 Javascript
解决await在forEach中不起作用的问题
2021/02/25 Javascript
[00:47]DOTA2荣耀之路6:天火,天火!
2018/05/30 DOTA
[33:23]VG vs Pain 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
使用Python中的cookielib模拟登录网站
2015/04/09 Python
python PIL模块与随机生成中文验证码
2016/02/27 Python
浅谈pandas中DataFrame关于显示值省略的解决方法
2018/04/08 Python
django-xadmin根据当前登录用户动态设置表单字段默认值方式
2020/03/13 Python
捷克移动配件网上商店:ProMobily.cz
2019/03/15 全球购物
Java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类
2012/02/06 面试题
介绍一下Make? 为什么使用make
2013/12/08 面试题
采购员岗位职责
2013/11/15 职场文书
建筑工程技术应届生求职信
2013/11/17 职场文书
护士自我鉴定总结
2014/03/24 职场文书
元旦晚会活动总结
2014/07/09 职场文书
开场白怎么写
2015/06/01 职场文书
药房管理制度范本
2015/08/06 职场文书
唱歌比赛拉拉队口号
2015/12/25 职场文书
小学数学教学反思范文
2016/02/16 职场文书
导游词创作书写原则以及开场白技巧怎么学?
2019/09/25 职场文书
python实现简单石头剪刀布游戏
2021/10/24 Python