JavaScript双向数据绑定原理与原生实现

作者:代码拾荒时间:2026-10-22 03:47:32浏览:298

本文深入解析JavaScript中双向数据绑定的核心原理,即UI与对象属性的同步机制。通过发布-订阅模式,文章演示了如何利用jQuery简化实现,并进一步提供不依赖框架的原生JavaScript解决方案,帮助开发者理解底层逻辑并构建轻量级数据绑定逻辑。

双向数据绑定原理与实现路径

双向数据绑定是指将对象属性的变化同步到用户界面,或者将用户界面的变化反向同步回对象属性。具体而言,若存在一个包含name属性的user对象,当该对象的name属性被赋予新值时,用户界面会相应地显示更新后的名字;反之,若界面包含用于输入用户名的字段,用户输入新值后,user对象中对应的属性也会随之改变。

诸如Ember.js、AngularJS和KnockoutJS等许多流行的客户端JavaScript框架均将双向数据绑定作为其核心特性。然而,这并不意味着从零开始实现双向数据绑定是一项艰巨的任务,也不代表在需要双向数据绑定功能时只能依赖上述框架。双向数据绑定的底层思想非常基础,可以概括为以下三个步骤:

第一步,需要一种方法来识别哪些用户界面元素与特定的对象属性建立了绑定关系。

第二步,需要能够监视对象属性以及用户界面元素的变化情况。

第三步,需要将所有检测到的变化传播到绑定的对象和对应的界面元素中。

尽管实现方式多种多样,但最简单且有效的途径是采用发布-订阅模式。其核心思想是:通过HTML代码中的自定义data属性来指明绑定关系。所有绑定的JavaScript对象和DOM元素都将“订阅”一个发布者对象。一旦检测到JavaScript对象或HTML输入字段发生变化,便通过代理事件触发发布-订阅模式,进而将变化广播并传播至所有绑定的对象和元素。

使用jQuery的简单实现

利用jQuery实现双向数据绑定非常直接且简单,因为该流行库能够让我们轻松订阅和发布DOM事件以及自定义事件:

基于jQuery的实现方案


function DataBinder(object_id){
 //使用一个jQuery对象作为简单的订阅者发布者
 var pubSub = jQuery({});

 //我们希望一个data元素可以在表单中指明绑定:data-bind-<object_id>="<property_name>"  

 var data_attr = "bind-" + object_id,
   message = object_id + ":change";

 //使用data-binding属性和代理来监听那个元素上的变化事件
 // 以便变化能够“广播”到所有的关联对象 

 jQuery(document).on("change","[data-" + data_attr + "]",function(evt){
  var input = jQuery(this);
  pubSub.trigger(message, [ $input.data(data_attr),$input.val()]);
 });

 //PubSub将变化传播到所有的绑定元素,设置input标签的值或者其他标签的HTML内容 

 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,text area,select")){
   $bound.val(new_val);
  }else{
   $bound.html(new_val);
  }
  });
 });

 return pubSub;
}

在此实验中,可以按照以下代码简单实现一个User模型:


function User(uid){
 var binder = new DataBinder(uid),

  user = {
   atttibutes: {},

   //属性设置器使用数据绑定器PubSub来发布变化 

   set: function(attr_name,val){
    this.attriures[attr_name] = val;
    binder.trigger(uid + ":change", [attr_name, val, this]);
   },

   get: function(attr_name){
    return this.attributes[attr_name];
   },

   _binder: binder
  };

  binder.on(uid +":change",function(vet,attr_name,new_val,initiator){
   if(initiator !== user){
    user.set(attr_name,new_val);
   }
  })
}

现在,无论何时需要将模型的属性绑定到用户界面的某一部分,只需在相应的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的依赖会怎样呢?事实上,这并非太困难(特别是当我们限定只支持IE8以上版本时)。最终,我们需要使用原生JavaScript来实现一个自定义的PubSub机制以及监听DOM事件。

原生JavaScript实现方案


function DataBinder(object_id){
 //创建一个简单地PubSub对象 

 var pubSub = {
  callbacks: {}.

  on: function(msg,calssback){
   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<lenli++){
    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.srcElemnt, //IE8兼容
   prop_name = target.getAttribute(data_attr);

   if(prop_name && prop_name !== ""){
    pubSub.publish(message,prop_name,target.value);
   }
 };

 //监听变化事件并代理到PubSub 
 if(document.addEventListener){
  document.addEventListener("change",changeHandler,false);
 }else{
  //IE8使用attachEvent而不是addEventListener  
  document.attachEvent("onchange",changeHandler);
 }

 //PubSub将变化传播到所有绑定元素 

 pubSub.on(message,function(vet,prop_name,new)_val){
  var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"),
    tah_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;
}

模型可以与前面的例子保持一致,除了在设置器中调用jQuery的trigger方法外,它需要通过调用自定义PubSub的publish方法来实现:


//在model的设置器中 

function User(uid){
//...

user = {
//...
set: function(attr_name,val){
 this.attribute[attr_name] = val;
 //使用“publish”方法 
 binder.publish(uid+ ":change", attr_name, val,this);
  }
 }

 //...
}  

再次,我们使用原生JavaScript代码实现了相同的结果,而不是依赖臃肿的JavaScript框架。

本文译自easy two way data-binding in JavaScript,原文地址http://www.lucaongaro.eu/blog/2012/12/02/easy-two-way-data-binding-in-javascript/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章