JS雙向數(shù)據(jù)綁定

雙向數(shù)據(jù)綁定簡述

雙向數(shù)據(jù)綁定,可以將JS對(duì)象的屬性綁定到DOM節(jié)點(diǎn)上活箕,實(shí)現(xiàn)JS對(duì)象跟DOM節(jié)點(diǎn)的同名屬性的關(guān)聯(lián),改變一方時(shí)可款,另一方也會(huì)得到更新育韩。

雙向數(shù)據(jù)綁定的思想大致如下:
一、將DOM節(jié)點(diǎn)的屬性跟JS對(duì)象的屬性建立關(guān)聯(lián)
二闺鲸、監(jiān)聽JS屬性跟DOM元素的變化
三筋讨、同時(shí)修改JS對(duì)象跟DOM元素

常見的實(shí)現(xiàn)數(shù)據(jù)綁定的做法有如下幾種:
一、發(fā)布-訂閱模式(backbone.js)
二摸恍、臟值檢查(angular.js)
三悉罕、數(shù)據(jù)劫持(vue.js)

發(fā)布訂閱模式實(shí)現(xiàn)

發(fā)布訂閱模式詳見這篇文章,原理是一種一對(duì)多的關(guān)系立镶,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽發(fā)布者對(duì)象壁袄,當(dāng)發(fā)布者發(fā)生改變時(shí),所有觀察者也會(huì)得到通知媚媒。

實(shí)現(xiàn)原理

通過發(fā)布訂閱模式實(shí)現(xiàn)數(shù)據(jù)雙向綁定的原理如下:
一嗜逻、當(dāng)model發(fā)送改變時(shí),觸發(fā)model change事件缭召,然后通過相應(yīng)的事件處理函數(shù)更新栈顷。
二、當(dāng)界面更新時(shí)恼琼,觸發(fā)UI change事件妨蛹,然后通過相應(yīng)的事件處理函數(shù)更新model,以及綁定在model上的其他界面控件晴竞。

依據(jù)這個(gè)思路,可以定義ui-update-event和model-update-event兩個(gè)事件狠半。下面將分別介紹噩死。

具體實(shí)現(xiàn)

直接上代碼~~~

<!DOCTYPE html>
<html>
    <head>
      <meta charset="utf-8"/>
      <title>發(fā)布訂閱模式實(shí)現(xiàn)數(shù)據(jù)雙向綁定</title>
      <style>
        #inputId {
          border:1px solid #ccc;
          width:200px;
          height:24px;
        }
        #modelView {
          border:1px solid black;
          width:200px;
          height:24px;
          margin-top:20px;
          margin-bottom:20px;
        }
      </style>
  </head>     
  <body>
        <input type="text" id="inputId" d-binding="user.name"/>
        <div id="modelView" d-binding="user.name"></div>
        <button id="btn">model的變化導(dǎo)致view的變化</button>
        <script>
            // 發(fā)布訂閱原型
            var pubSub = {
                allCallbacks: [],
                // 增加訂閱者
                on: function(eventName, callback) {
                    // 如果沒有訂閱過該消息,給這個(gè)消息創(chuàng)建一個(gè)緩存列表
                    if(!this.allCallbacks[eventName]) {
                    this.allCallbacks[eventName] = [];
                    }
                    this.allCallbacks[eventName].push(callback);
                },
                // 發(fā)布消息
                public: function() {
                    var eventName = Array.prototype.shift.call(arguments);
                    // 取出該消息對(duì)應(yīng)的回調(diào)函數(shù)集合
                    var callbacks = this.allCallbacks[eventName];
                    if (!callbacks || callbacks.length === 0) {
                        return false;
                    }
                    for (var i = 0; i < callbacks.length; i++) {
                        var callback = callbacks[i];
                        callback.apply(this, arguments);
                    }
                }
            };
            var DataBinder = (function () {
                function changeHandler(e) {
                    var target = e.target || e.srcElement;
                    var attrName = target.getAttribute("d-binding");
                    if (attrName && attrName !== "") {
                        // 發(fā)布消息
                        pubSub.public("ui-update-event", attrName, target.value);
                    }
                };
                // 監(jiān)聽視圖層的事件變化
              if (document.addEventListener) {
                    document.addEventListener('keyup', changeHandler, false);
                    document.addEventListener('change', changeHandler, false);
                } else {
                    document.attachEvent("onkeyup", changeHandler);
                  document.attachEvent("onchange", changeHandler);
                }
                // 監(jiān)聽模型上的變化神年,并把變化傳播到所有綁定的元素上
                pubSub.on("model-update-event", function(attrName, newVal) {
                    var elements = document.querySelectorAll('[d-binding="' + attrName + '"]');
                    var tagName;
                    for (var i = 0, ilen = elements.length; i < ilen; i++) {
                        tagName = elements[i].tagName.toLowerCase();
                        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
                            elements[i].value = newVal;
                        } else {
                            elements[i].innerHTML = newVal;
                        }
                    }
                });
                return {
                  modelName : "",
                  initModel : function (modelName) {
                    var self = this;
                    self.modelName = modelName;
                    pubSub.on("ui-update-event", function(attrName, propValue){
                      var propPathArr = attrName.split(".");
                      self.updateModelData(propPathArr[1], propValue);
                    });
                    return Object.create(this);
                  },
                  loadModelData : function (modelData) {
                    for (prop in modelData) {
                      this.updateModelData(prop, modelData[prop]);
                    }
                  },
                  updateModelData : function (propName, propValue) {
                    eval(this.modelName)[propName] = propValue;
                    pubSub.public("model-update-event", this.modelName + '.' + propName, propValue);
                  }
                }
            })();

            var user = DataBinder.initModel("user");
            user.loadModelData({
              'name' : 1
            });
            // 測(cè)試模型的變化到 視圖層的變化 
            var btn = document.getElementById("btn");
            var inputId = document.getElementById("inputId");

            btn.onclick = function() {
              var value = inputId.value;
              user.updateModelData("name", parseInt(value) + 1);
            };

        </script>
  </body>
</html>

ui-update-event事件

對(duì)于所有支持雙向綁定的頁面控件已维,當(dāng)值發(fā)生改變時(shí),就會(huì)觸發(fā)ui-update-event事件更新model已日,以及綁定在model上的其他控件垛耳。
觸發(fā)ui-update-event時(shí),先執(zhí)行

pubSub.on("ui-update-event", function(attrName, propValue){
    var propPathArr = attrName.split(".");
    self.updateModelData(propPathArr[1], propValue);
});

通過updateModelData方法去執(zhí)行model-update-event,從而更新model堂鲜。

updateModelData : function (propName, propValue) {
    eval(this.modelName)[propName] = propValue;
    pubSub.public("model-update-event", this.modelName + '.' + propName, propValue);

model-update-event

對(duì)于model這一層栈雳,當(dāng)model發(fā)生改變時(shí),會(huì)觸發(fā)model-update-event的監(jiān)聽事件

pubSub.on("model-update-event", function(attrName, newVal) {
    var elements = document.querySelectorAll('[d-binding="' + attrName + '"]');
    var tagName;
    for (var i = 0, ilen = elements.length; i < ilen; i++) {
        tagName = elements[i].tagName.toLowerCase();
        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
            elements[i].value = newVal;
        } else {
            elements[i].innerHTML = newVal;
        }
    }
});

從而修改了DOM元素的值缔莲。

屬性劫持

Object.defineProperty()方法直接在對(duì)象上定義一個(gè)新屬性哥纫,或修改對(duì)象上的現(xiàn)有屬性,并返回該對(duì)象痴奏。
關(guān)于Object.defineProperty()的介紹如下:

Object.defineProperty(obj, prop, descriptor)

參數(shù)
    obj:定義屬性的對(duì)象
    prop:要定義或修改的屬性的名稱蛀骇。
    descriptor:定義或修改屬性的描述符。

返回值:傳遞給函數(shù)的對(duì)象读拆。
 
注意:數(shù)據(jù)描述符和訪問器描述符擅憔,不能同時(shí)存在(value,writable 和 get,set)
 get:函數(shù)return將被用作屬性的值。
set:該函數(shù)將僅接收參數(shù)賦值給該屬性的新值檐晕。(在屬性改變時(shí)調(diào)用)

使用Object.defineProperty()實(shí)現(xiàn)雙向數(shù)據(jù)綁定

<!DOCTYPE html>
 <html>
  <head>
    <meta charset="utf-8">
    <title>使用Object.defineProperty實(shí)現(xiàn)簡單的雙向數(shù)據(jù)綁定</title>
  </head>
  <body>
    <input type="text" id="input" />
    <div id="div"></div>
    <script>
        var obj = {};
        var inputVal = document.getElementById("input");
        var div = document.getElementById("div");

        Object.defineProperty(obj, "name", {
          set: function(newVal) {
            inputVal.value = newVal;
            div.innerHTML = newVal;
          }
        });
        inputVal.addEventListener('input', function(e){
          obj.name = e.target.value;
        });
    </script>
  </body>
</html>

當(dāng)在input輸入框輸入值的時(shí)候暑诸,div也會(huì)顯示對(duì)應(yīng)的值,實(shí)現(xiàn)了UI更改model的效果~~~

當(dāng)在控制臺(tái)輸入 obj.name="輸入任意值"并按回車鍵運(yùn)行時(shí)棉姐,input輸入框的值也會(huì)跟著變屠列,這就實(shí)現(xiàn)了model更改UI的效果~~~

可見,Object.defineProperty()實(shí)現(xiàn)雙向綁定比發(fā)布訂閱模式簡單得多~~~

臟值檢查

是通過臟值檢測(cè)的方式比對(duì)數(shù)據(jù)是否有變更伞矩,來決定是否更新視圖笛洛,最簡單的方式就是通過 setInterval()定時(shí)輪詢檢測(cè)數(shù)據(jù)的變動(dòng)。
臟值檢查實(shí)現(xiàn)較為復(fù)雜乃坤,暫時(shí)沒時(shí)間進(jìn)行研究~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苛让,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子湿诊,更是在濱河造成了極大的恐慌狱杰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厅须,死亡現(xiàn)場離奇詭異仿畸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朗和,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門错沽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眶拉,你說我怎么就攤上這事千埃。” “怎么了忆植?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵放可,是天一觀的道長谒臼。 經(jīng)常有香客問我,道長耀里,這世上最難降的妖魔是什么蜈缤? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮备韧,結(jié)果婚禮上劫樟,老公的妹妹穿的比我還像新娘。我一直安慰自己织堂,他們只是感情好叠艳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著易阳,像睡著了一般附较。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潦俺,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天拒课,我揣著相機(jī)與錄音,去河邊找鬼事示。 笑死早像,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肖爵。 我是一名探鬼主播卢鹦,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼劝堪!你這毒婦竟也來了冀自?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤秒啦,失蹤者是張志新(化名)和其女友劉穎熬粗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體余境,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驻呐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芳来。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暴氏。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绣张,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情关带,我是刑警寧澤侥涵,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布沼撕,位于F島的核電站,受9級(jí)特大地震影響芜飘,放射性物質(zhì)發(fā)生泄漏务豺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一嗦明、第九天 我趴在偏房一處隱蔽的房頂上張望笼沥。 院中可真熱鬧,春花似錦娶牌、人聲如沸奔浅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汹桦。三九已至,卻和暖如春鉴裹,著一層夾襖步出監(jiān)牢的瞬間舞骆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工径荔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留督禽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓总处,卻偏偏與公主長得像狈惫,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辨泳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355