Vue的雙向數(shù)據(jù)綁定

1学赛、原理

Vue的雙向數(shù)據(jù)綁定的原理相信大家也都十分了解了案糙,主要是通過(guò)Object對(duì)象的defineProperty屬性铃在,重寫(xiě)data的set和get函數(shù)來(lái)實(shí)現(xiàn)的,這里對(duì)原理不做過(guò)多描述,主要還是來(lái)實(shí)現(xiàn)一個(gè)實(shí)例庭砍。為了使代碼更加的清晰性置,這里只會(huì)實(shí)現(xiàn)最基本的內(nèi)容拾并,主要實(shí)現(xiàn)v-model,v-bind 和v-click三個(gè)命令鹏浅,其他命令也可以自行補(bǔ)充嗅义。

添加網(wǎng)上的一張圖

2、實(shí)現(xiàn)

頁(yè)面結(jié)構(gòu)很簡(jiǎn)單隐砸,如下

<div id="app">
    <form>
      <input type="text"  v-model="number">
      <button type="button" v-click="increment">增加</button>
    </form>
    <h3 v-bind="number"></h3>
  </div>

包含:

1\. 一個(gè)input芥喇,使用v-model指令
2\. 一個(gè)button,使用v-click指令
3\. 一個(gè)h3凰萨,使用v-bind指令。

我們最后會(huì)通過(guò)類(lèi)似于vue的方式來(lái)使用我們的雙向數(shù)據(jù)綁定械馆,結(jié)合我們的數(shù)據(jù)結(jié)構(gòu)添加注釋

var app = new myVue({
      el:'#app',
      data: {
        number: 0
      },
      methods: {
        increment: function() {
          this.number ++;
        },
      }
    })

首先我們需要定義一個(gè)myVue構(gòu)造函數(shù):

function myVue(options) {

}

為了初始化這個(gè)構(gòu)造函數(shù)胖眷,給它添加一 個(gè)_init屬性

function myVue(options) {
  this._init(options);
}
myVue.prototype._init = function (options) {
    this.$options = options;  // options 為上面使用時(shí)傳入的結(jié)構(gòu)體,包括el,data,methods
    this.$el = document.querySelector(options.el); // el是 #app, this.$el是id為app的Element元素
    this.$data = options.data; // this.$data = {number: 0}
    this.$methods = options.methods;  // this.$methods = {increment: function(){}}
  }

接下來(lái)實(shí)現(xiàn)_obverse函數(shù)霹崎,對(duì)data進(jìn)行處理珊搀,重寫(xiě)data的set和get函數(shù)

并改造_init函數(shù)

 myVue.prototype._obverse = function (obj) { // obj = {number: 0}
    var value;
    for (key in obj) {  //遍歷obj對(duì)象
      if (obj.hasOwnProperty(key)) {
        value = obj[key]; 
        if (typeof value === 'object') {  //如果值還是對(duì)象,則遍歷處理
          this._obverse(value);
        }
        Object.defineProperty(this.$data, key, {  //關(guān)鍵
          enumerable: true,
          configurable: true,
          get: function () {
            console.log(`獲取${value}`);
            return value;
          },
          set: function (newVal) {
            console.log(`更新${newVal}`);
            if (value !== newVal) {
              value = newVal;
            }
          }
        })
      }
    }
  }

 myVue.prototype._init = function (options) {
    this.$options = options;
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    this.$methods = options.methods;

    this._obverse(this.$data);
  }

接下來(lái)我們寫(xiě)一個(gè)指令類(lèi)Watcher尾菇,用來(lái)綁定更新函數(shù)境析,實(shí)現(xiàn)對(duì)DOM元素的更新

function Watcher(name, el, vm, exp, attr) {
    this.name = name;         //指令名稱(chēng)囚枪,例如文本節(jié)點(diǎn),該值設(shè)為"text"
    this.el = el;             //指令對(duì)應(yīng)的DOM元素
    this.vm = vm;             //指令所屬myVue實(shí)例
    this.exp = exp;           //指令對(duì)應(yīng)的值劳淆,本例如"number"
    this.attr = attr;         //綁定的屬性值链沼,本例為"innerHTML"

    this.update();
  }

  Watcher.prototype.update = function () {
    this.el[this.attr] = this.vm.$data[this.exp]; //比如 H3.innerHTML = this.data.number; 當(dāng)number改變時(shí),會(huì)觸發(fā)這個(gè)update函數(shù)沛鸵,保證對(duì)應(yīng)的DOM內(nèi)容進(jìn)行了更新括勺。
  }

更新_init函數(shù)以及_obverse函數(shù)

myVue.prototype._init = function (options) {
    //...
    this._binding = {};   //_binding保存著model與view的映射關(guān)系,也就是我們前面定義的Watcher的實(shí)例曲掰。當(dāng)model改變時(shí)疾捍,我們會(huì)觸發(fā)其中的指令類(lèi)更新,保證view也能實(shí)時(shí)更新
    //...
  }

  myVue.prototype._obverse = function (obj) {
    //...
      if (obj.hasOwnProperty(key)) {
        this._binding[key] = {    // 按照前面的數(shù)據(jù)栏妖,_binding = {number: _directives: []}                                                                                                                                                  
          _directives: []
        };
        //...
        var binding = this._binding[key];
        Object.defineProperty(this.$data, key, {
          //...
          set: function (newVal) {
            console.log(`更新${newVal}`);
            if (value !== newVal) {
              value = newVal;
              binding._directives.forEach(function (item) {  // 當(dāng)number改變時(shí)乱豆,觸發(fā)_binding[number]._directives 中的綁定的Watcher類(lèi)的更新
                item.update();
              })
            }
          }
        })
      }
    }
  }

那么如何將view與model進(jìn)行綁定呢?接下來(lái)我們定義一個(gè)_compile函數(shù)吊趾,用來(lái)解析我們的指令(v-bind,v-model,v-clickde)等宛裕,并在這個(gè)過(guò)程中對(duì)view與model進(jìn)行綁定。

 myVue.prototype._init = function (options) {
   //...
    this._complie(this.$el);
  }

myVue.prototype._complie = function (root) { root 為 id為app的Element元素趾徽,也就是我們的根元素
    var _this = this;
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {  // 對(duì)所有元素進(jìn)行遍歷续滋,并進(jìn)行處理
        this._complie(node);
      }

      if (node.hasAttribute('v-click')) {  // 如果有v-click屬性,我們監(jiān)聽(tīng)它的onclick事件孵奶,觸發(fā)increment事件疲酌,即number++
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('v-click');
          return _this.$methods[attrVal].bind(_this.$data);  //bind是使data的作用域與method函數(shù)的作用域保持一致
        })();
      }

      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有v-model屬性,并且元素是INPUT或者TEXTAREA了袁,我們監(jiān)聽(tīng)它的input事件
        node.addEventListener('input', (function(key) {  
          var attrVal = node.getAttribute('v-model');
           //_this._binding['number']._directives = [一個(gè)Watcher實(shí)例]
           // 其中Watcher.prototype.update = function () {
           //   node['vaule'] = _this.$data['number'];  這就將node的值保持與number一致
           // }
          _this._binding[attrVal]._directives.push(new Watcher(  
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            _this.$data[attrVal] =  nodes[key].value; // 使number 的值與 node的value保持一致朗恳,已經(jīng)實(shí)現(xiàn)了雙向綁定
          }
        })(i));
      } 

      if (node.hasAttribute('v-bind')) { // 如果有v-bind屬性,我們只要使node的值及時(shí)更新為data中number的值即可
        var attrVal = node.getAttribute('v-bind');
        _this._binding[attrVal]._directives.push(new Watcher(
          'text',
          node,
          _this,
          attrVal,
          'innerHTML'
        ))
      }
    }
  }

至此载绿,我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單vue的雙向綁定功能粥诫,包括v-bind, v-model, v-click三個(gè)指令。效果如下圖

[圖片上傳失敗...(image-e82522-1536566993539)]

附上全部代碼崭庸,不到150行

<!DOCTYPE html>
<head>
  <title>myVue</title>
</head>
<style>
  #app {
    text-align: center;
  }
</style>
<body>
  <div id="app">
    <form>
      <input type="text"  v-model="number">
      <button type="button" v-click="increment">增加</button>
    </form>
    <h3 v-bind="number"></h3>
    <form>
      <input type="text"  v-model="count">
      <button type="button" v-click="incre">增加</button>
    </form>
    <h3 v-bind="count"></h3>
  </div>
</body>

<script>
  function myVue(options) {
    this._init(options);
  }

  myVue.prototype._init = function (options) {
    this.$options = options;
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    this.$methods = options.methods;

    this._binding = {};
    this._obverse(this.$data);
    this._complie(this.$el);
  }

  myVue.prototype._obverse = function (obj) {
    var _this = this;
    Object.keys(obj).forEach(function (key) {
      if (obj.hasOwnProperty(key)) {
        _this._binding[key] = {                                                                                                                                                          
          _directives: []
        };
        console.log(_this._binding[key])
        var value = obj[key];
        if (typeof value === 'object') {
          _this._obverse(value);
        }
        var binding = _this._binding[key];
        Object.defineProperty(_this.$data, key, {
          enumerable: true,
          configurable: true,
          get: function () {
            console.log(`${key}獲取${value}`);
            return value;
          },
          set: function (newVal) {
            console.log(`${key}更新${newVal}`);
            if (value !== newVal) {
              value = newVal;
              binding._directives.forEach(function (item) {
                item.update();
              })
            }
          }
        })
      }
    })
  }

  myVue.prototype._complie = function (root) {
    var _this = this;
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {
        this._complie(node);
      }

      if (node.hasAttribute('v-click')) {
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('v-click');
          return _this.$methods[attrVal].bind(_this.$data);
        })();
      }

      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
        node.addEventListener('input', (function(key) {
          var attrVal = node.getAttribute('v-model');
          _this._binding[attrVal]._directives.push(new Watcher(
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            _this.$data[attrVal] =  nodes[key].value;
          }
        })(i));
      } 

      if (node.hasAttribute('v-bind')) {
        var attrVal = node.getAttribute('v-bind');
        _this._binding[attrVal]._directives.push(new Watcher(
          'text',
          node,
          _this,
          attrVal,
          'innerHTML'
        ))
      }
    }
  }

  function Watcher(name, el, vm, exp, attr) {
    this.name = name;         //指令名稱(chēng)怀浆,例如文本節(jié)點(diǎn),該值設(shè)為"text"
    this.el = el;             //指令對(duì)應(yīng)的DOM元素
    this.vm = vm;             //指令所屬myVue實(shí)例
    this.exp = exp;           //指令對(duì)應(yīng)的值怕享,本例如"number"
    this.attr = attr;         //綁定的屬性值执赡,本例為"innerHTML"

    this.update();
  }

  Watcher.prototype.update = function () {
    this.el[this.attr] = this.vm.$data[this.exp];
  }

  window.onload = function() {
    var app = new myVue({
      el:'#app',
      data: {
        number: 0,
        count: 0,
      },
      methods: {
        increment: function() {
          this.number ++;
        },
        incre: function() {
          this.count ++;
        }
      }
    })
  }
</script>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市函筋,隨后出現(xiàn)的幾起案子沙合,更是在濱河造成了極大的恐慌,老刑警劉巖跌帐,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件首懈,死亡現(xiàn)場(chǎng)離奇詭異绊率,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)究履,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)滤否,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挎袜,你說(shuō)我怎么就攤上這事顽聂。” “怎么了盯仪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵紊搪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我全景,道長(zhǎng)耀石,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任爸黄,我火速辦了婚禮滞伟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炕贵。我一直安慰自己梆奈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布称开。 她就那樣靜靜地躺著亩钟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳖轰。 梳的紋絲不亂的頭發(fā)上清酥,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蕴侣,去河邊找鬼焰轻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昆雀,可吹牛的內(nèi)容都是我干的辱志。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼狞膘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揩懒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起客冈,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稳强,沒(méi)想到半個(gè)月后场仲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體和悦,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渠缕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸽素。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亦鳞,死狀恐怖馍忽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燕差,我是刑警寧澤遭笋,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站徒探,受9級(jí)特大地震影響瓦呼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜测暗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一央串、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碗啄,春花似錦质和、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至尉共,卻和暖如春褒傅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袄友。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工殿托, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剧蚣。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓支竹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鸠按。 傳聞我的和親對(duì)象是個(gè)殘疾皇子礼搁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容