vue雙向數(shù)據(jù)綁定實現(xiàn)原理學(xué)習(xí)筆記

參考鏈接:https://www.cnblogs.com/kidney/p/6052935.html
黃軼的源碼解讀:https://github.com/DDFE/DDFE-blog/issues/7

一糕珊、雙向數(shù)據(jù)綁定和單向數(shù)據(jù)綁定概念
????????雙向數(shù)據(jù)綁定就是在單向數(shù)據(jù)綁定的基礎(chǔ)上給可輸入元素(input红选、textare等)添加了change(input)事件喇肋,來動態(tài)修改model(js)和 view(視圖)苟蹈,在單向數(shù)據(jù)綁定中右核,input輸入元素中輸入的內(nèi)容可以通過js操作dom動態(tài)獲取,js中改變的數(shù)據(jù)也需要再次操作dom反映到視圖中贺喝。雙向數(shù)據(jù)綁定通過watcher方法自動更新視圖中的數(shù)據(jù),省去了煩瑣的dom操作氮采;
二主到、訪問器屬性
  var obj = {}
  // 為obj對象定義一個名為hello的訪問器屬性
  // 訪問器屬性是對象中的一種特殊屬性登钥,不能直接在對象中定義娶靡,只能通defineProperty方法定義
  // 讀取或設(shè)置訪問器屬性的值塔鳍,實際上是調(diào)用其內(nèi)部函數(shù)get或set方法
  Object.defineProperty(obj, "hello", {
    get: function() {},
    set: function() {}
  })
  obj.hello // 調(diào)用get方法呻此,并返回get方法的返回值
  obj.hello = "123" // 賦值傳參焚鲜,調(diào)用set方法,參數(shù)是123
  // 訪問器屬性會被優(yōu)先訪問郑兴,即訪問器屬性會覆蓋同名屬性
三情连、雙向數(shù)據(jù)綁定的簡化版
var obj = {}
  Object.defineProperty(obj, "hello", {
    get: function() {},
    set: function(newVal) {
      document.getElementById('a').value = newVal
      document.getElementById('b').innerHTML = newVal
    }
  })
  // 模擬watcher
  document.addEventListener('keyup', function(e) {
    obj.hello = e.target.value
  })
四却舀、將vue中的值單向綁定到dom中

1)DocumentFragment文檔片斷
????????可以看做是節(jié)點容器,它可以包含多個子節(jié)點但校,將其插入到dom中時,只有它的子節(jié)點會插入到目標(biāo)節(jié)點倘是;
????????使用DocumentFragment處理節(jié)點搀崭,速度和性能遠遠優(yōu)于直接操作dom瘤睹;
????????vue進行編譯時答倡,就是將掛載目標(biāo)的所有子節(jié)點劫持(通過append方法,dom中的所有節(jié)點會被自動刪除)到DocumentFragment中绸吸,處理后再將DocumentFragment整體返回插入掛載目標(biāo)设江;

// html代碼
<div id="app">
    <input type="text" id="a">
    <span id="b"></span>
  </div>
// js操作
var dom = nodeToFragment(document.getElementById('app'))
  console.log(dom)
  function nodeToFragment(node) {
    var flag = document.createDocumentFragment()
    var child
    while (child = node.firstChild) {
      flag.appendChild(child) // 將子節(jié)點劫持到文檔片斷中
    }
    return flag
  }
  document.getElementById('app').appendChild(dom) // 返回到app中
屏幕快照 2018-07-23 下午4.16.01.png

2)dom編譯和數(shù)據(jù)綁定

// html代碼
  <div id="app">
    <input type="text" v-model="text">
    {{ text }}
  </div>
// js代碼
// 對dom進行編譯,將輸入框以及文本節(jié)點與data中的數(shù)據(jù)綁定
  function compile(node, vm) {
    var reg = /\{\{(.*)\}\}/
    // 節(jié)點類型為元素
    if (node.nodeType === 1) {
      var attr = node.attributes
      // 解析屬性
      for (var i = 0; i < attr.length; i++) {
        if (attr[i].nodeName == 'v-model') {
          var name = attr[i].nodeValue // 獲取v-model綁定的屬性名
          node.value = vm.data[name] // 將data的值賦給該node
          node.removeAttribute('v-model')
        }
      }
    }
    // 節(jié)點類型為text
    if (node.nodeType === 3) {
      if (reg.test(node.nodeValue)) {
        var name = RegExp.$1 // 獲取匹配到的字符串
        name = name.trim()
        node.nodeValue = vm.data[name] // 將data的值賦給該node
      }
    }
  }
// 將節(jié)點轉(zhuǎn)換為文檔片斷
  function nodeToFragment(node, vm) {
    var flag = document.createDocumentFragment()
    var child
    while (child = node.firstChild) {
      compile(child, vm)
      flag.appendChild(child) // 將子節(jié)點劫持到文檔片斷中
    }
    return flag
  }
// vue綁定的完整操作
  function Vue(options) {
    this.data = options.data
    var id = options.el
    var dom = nodeToFragment(document.getElementById(id), this)
    // 編譯完成后,將dom返回到app中
    document.getElementById(id).appendChild(dom)
  }

  var vm = new Vue({
    el: 'app',
    data: {
      text: 'hello world'
    }
  })

最終結(jié)果:


屏幕快照 2018-07-23 下午5.03.33.png
五、實現(xiàn)數(shù)據(jù)與dom雙向綁定

??????? 在輸入框中輸入數(shù)據(jù)的時候练俐,首先會觸發(fā)input或者keyup事件冕臭,在相應(yīng)的事件處理程序中腺晾,我們獲取輸入框的value并賦值給vm實例的text屬性,利用defineProperty將data中的text設(shè)置為vm的訪問器屬性辜贵,會觸發(fā)set方法更新屬性的值悯蝉;

// html代碼
<div id="app">
    <input type="text" v-model="text">
    {{ text }}
  </div>
// js代碼
var obj = {}

  function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
      get: function() {
        return val
      },
      set: function(newVal) {
        if (newVal === val) return
        val = newVal
        console.log(val)
      }
    })
  }

  // watcher
  function observe(obj, vm) {
    Object.keys(obj).forEach(function(key) {
      defineReactive(vm, key, obj[key])
    })
  }

  function Vue(options) {
    this.data = options.data
    var data = this.data
    observe(data, this)
    var id = options.el
    var dom = nodeToFragment(document.getElementById(id), this)
    // 編譯完成后,將dom返回到app中
    document.getElementById(id).appendChild(dom)
  }

  // 對dom進行編譯托慨,將輸入框以及文本節(jié)點與data中的數(shù)據(jù)綁定
  function compile(node, vm) {
    var reg = /\{\{(.*)\}\}/
    // 節(jié)點類型為元素
    if (node.nodeType === 1) {
      var attr = node.attributes
      // 解析屬性
      for (var i = 0; i < attr.length; i++) {
        if (attr[i].nodeName == 'v-model') {
          var name = attr[i].nodeValue // 獲取v-model綁定的屬性名
          node.addEventListener('input', function(e) {
            // 給相應(yīng)的data屬性賦值鼻由,進而觸發(fā)該屬性的set方法
            vm[name] = e.target.value
          })
          node.value = vm[name] // 將data的值賦給該node
          node.removeAttribute('v-model')
        }
      }
    }
    // 節(jié)點類型為text
    if (node.nodeType === 3) {
      if (reg.test(node.nodeValue)) {
        var name = RegExp.$1 // 獲取匹配到的字符串
        name = name.trim()
        node.nodeValue = vm[name] // 將data的值賦給該node
      }
    }
  }

  function nodeToFragment(node, vm) {
    var flag = document.createDocumentFragment()
    var child
    while (child = node.firstChild) {
      compile(child, vm)
      flag.appendChild(child) // 將子節(jié)點劫持到文檔片斷中
    }
    return flag
  }

  var vm = new Vue({
    el: 'app',
    data: {
      text: 'hello world'
    }
  })

結(jié)果如下:


屏幕快照 2018-07-23 下午5.44.28.png
六、實現(xiàn)數(shù)據(jù)與dom雙向綁定

????????text 文本變化了,set方法觸發(fā)了嗡靡,使用訂閱發(fā)布模式將綁定到text的文本節(jié)點同步變化跺撼,訂閱發(fā)布模式是一種一對多的關(guān)系,即多個觀察者同時監(jiān)聽一個主題對象,這個主題對象的狀態(tài)發(fā)生變化時會通知所有觀察者對象菩貌;
????????流程:發(fā)布者發(fā)出通知=》主題對象收到通知并推送給觀察者=》訂閱者執(zhí)行相應(yīng)操作

 //  一個發(fā)布者publisher
  var pub = {
    publish: function() {
      dep.notify()
    }
  }
  // 三個訂閱者subscribers
  var sub1 = {
    update: function() {
      console.log(1)
    }
  }
  var sub2 = {
    update: function() {
      console.log(2)
    }
  }
  var sub3 = {
    update: function() {
      console.log(3)
    }
  }

  // 一個主題對象
  function Dep() {
    this.subs = [sub1, sub2, sub3]
  }
  Dep.prototype.notify = function() {
    this.subs.forEach(function(sub) {
      sub.update()
    })
  }
  // 發(fā)布者發(fā)布消息仇参,主題對象執(zhí)行notif方法婆芦,進而觸發(fā)訂閱者執(zhí)行update方法
  var dep = new Dep()
  pub.publish() // 1,2,3
七或粮、雙向數(shù)據(jù)綁定完整代碼

????????監(jiān)聽數(shù)據(jù)的過程中,會為data中的每一個屬性生成一個主題對象dep;
????????在編譯html過程中贱除,會為每個與數(shù)據(jù)綁定相關(guān)的節(jié)點生成一個訂閱者watcher悬蔽,watcher會將自己添加到相應(yīng)屬性的dep中禾乘;
????????發(fā)出通知dep.notify()=>觸發(fā)訂閱者的update方法=>更新視圖;

  function defineReactive(obj, key, val) {
    var dep = new Dep()
    Object.defineProperty(obj, key, {
      get: function() {
        // 添加訂閱者watcher到主題對象Dep
        if (Dep.target) dep.addSub(Dep.target)
        return val
      },
      set: function(newVal) {
        if (newVal === val) return
        val = newVal
        // 作為發(fā)布者發(fā)出通知
        dep.notify()
      }
    })
  }

  // watcher
  function observe(obj, vm) {
    Object.keys(obj).forEach(function(key) {
      defineReactive(vm, key, obj[key])
    })
  }

  // 一個主題對象
  function Dep() {
    this.subs = []
  }
  Dep.prototype = {
    addSub: function(sub) {
      this.subs.push(sub)
    },
    notify: function() {
      this.subs.forEach(function(sub) {
        sub.update()
      })
    }
  }

  function Vue(options) {
    this.data = options.data
    var data = this.data
    observe(data, this)
    var id = options.el
    var dom = nodeToFragment(document.getElementById(id), this)
    // 編譯完成后决记,將dom返回到app中
    document.getElementById(id).appendChild(dom)
  }

  // 對dom進行編譯建车,將輸入框以及文本節(jié)點與data中的數(shù)據(jù)綁定
  function compile(node, vm) {
    var reg = /\{\{(.*)\}\}/
    // 節(jié)點類型為元素
    if (node.nodeType === 1) {
      var attr = node.attributes
      // 解析屬性
      for (var i = 0; i < attr.length; i++) {
        if (attr[i].nodeName == 'v-model') {
          var name = attr[i].nodeValue // 獲取v-model綁定的屬性名
          node.addEventListener('input', function(e) {
            // 給相應(yīng)的data屬性賦值,進而觸發(fā)該屬性的set方法
            vm[name] = e.target.value
          })
          node.value = vm[name] // 將data的值賦給該node
          node.removeAttribute('v-model')
        }
      }
    }
    // 節(jié)點類型為text
    if (node.nodeType === 3) {
      if (reg.test(node.nodeValue)) {
        var name = RegExp.$1 // 獲取匹配到的字符串
        name = name.trim()
        // node.nodeValue = vm[name] // 將data的值賦給該node
        new Watcher(vm, node, name)
      }
    }
  }

  function Watcher(vm, node, name) {
    Dep.target = this
    this.name = name
    this.node = node
    this.vm = vm
    this.update()
    Dep.target = null
  }

  Watcher.prototype = {
    update: function() {
      this.get()
      this.node.nodeValue = this.value
    },
    // 獲取data中的屬性值
    get: function() {
      this.value = this.vm[this.name] // 觸發(fā)相應(yīng)屬性的get
    }
  }

  function nodeToFragment(node, vm) {
    var flag = document.createDocumentFragment()
    var child
    while (child = node.firstChild) {
      compile(child, vm)
      flag.appendChild(child) // 將子節(jié)點劫持到文檔片斷中
    }
    return flag
  }

  var vm = new Vue({
    el: 'app',
    data: {
      text: 'hello world'
    }
  })

結(jié)果如下:


屏幕快照 2018-07-23 下午6.42.45.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唬涧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轴合,更是在濱河造成了極大的恐慌总滩,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒂阱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門响委,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肪虎,你說我怎么就攤上這事⌒咳福” “怎么了铐维?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵解幼,是天一觀的道長特铝。 經(jīng)常有香客問我鲫剿,道長,這世上最難降的妖魔是什么粱挡? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮帚稠,結(jié)果婚禮上篮撑,老公的妹妹穿的比我還像新娘。我一直安慰自己匆瓜,他們只是感情好赢笨,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驮吱,像睡著了一般茧妒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上左冬,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天桐筏,我揣著相機與錄音,去河邊找鬼拇砰。 笑死梅忌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的除破。 我是一名探鬼主播牧氮,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瑰枫!你這毒婦竟也來了踱葛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤光坝,失蹤者是張志新(化名)和其女友劉穎尸诽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盯另,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡性含,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸳惯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片商蕴。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悲敷,靈堂內(nèi)的尸體忽然破棺而出究恤,到底是詐尸還是另有隱情,我是刑警寧澤后德,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站抄腔,受9級特大地震影響瓢湃,放射性物質(zhì)發(fā)生泄漏理张。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一绵患、第九天 我趴在偏房一處隱蔽的房頂上張望雾叭。 院中可真熱鬧,春花似錦落蝙、人聲如沸织狐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽移迫。三九已至,卻和暖如春管行,著一層夾襖步出監(jiān)牢的瞬間厨埋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工捐顷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荡陷,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓迅涮,卻偏偏與公主長得像废赞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叮姑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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