谈谈JavaScript中的双向数据绑定

双向数据绑定指的是将对象属性变化绑定到UI,或者反之。换句话说,如果我们有一个拥有name属性的user对象,当我们给user.name赋予一个新值是UI也会相应的显示新的名字。同样的,如果UI包括了一个输入字段用来输入用户名,输入一个新的值会导致user对象中的那么属性发生变化。

许多流行的客户端JavaScript框架例如Ember.js,AngularJS以及KnockoutJS都将双向数据绑定作为自己的头号特性。但是这并不意味着从零开始实现双向数据绑定就很困难,同样的当我们需要双向数据绑定时并不是只能够选择这些框架其中的一个。双向数据绑定底层的思想非常的基本,它可以被压缩成为三个步骤:

1.我们需要一个方法来识别哪个UI元素被绑定了相应的属性 2.我们需要监视属性和UI元素的变化 3.我们需要将所有变化传播到绑定的对象和元素

虽然实现的方法有很多,但是最简单也是最有效的途径是使用发布者-订阅者模式。思想很简单:我们可以使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的JavaScript对象以及DOM元素都将“订阅”一个发布者对象。任何时候如果JavaScript对象或者一个HTML输入字段被侦测到发生了变化,我们将代理事件到发布者-订阅者模式,这会反过来将变化广播并传播到所有绑定的对象和元素。

使用jQuery的简单实现

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

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);

       }

})   }

现在,无论我们什么时候想把模型的属性绑定到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的依赖会怎样呢?事实上,这并不是太困难(尤其是当我们限定只支持IE8以上的版本)。最终,我们需要使用原生的JavaScript来实现一个自定义的PubSub以及观察DOM事件。

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框架。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容