vue實現雙向綁定和依賴收集遇到的坑

這篇文章主要介紹了vue的雙向綁定和依賴收集,主要是通過Object.defineProperty() 實現雙向綁定,具體思路代碼大家跟隨小編一起看看吧

在掘金上買了一個關於解讀vue源碼的小冊,因爲是付費的,所以還比較放心

在小冊裏看到了關於vue雙向綁定和依賴收集的部分,總感覺有些怪怪的,然後就自己跟着敲了一遍。 敲完後,發現完全無法運行,  坑啊,  寫書人完全沒有測試過。

然後自己完善代碼, 越寫越發現坑, 問題有些大。。。。。。

最後自己重新實現了一遍,代碼較多。 用到觀察訂閱者模式實現依賴收集, Object.defineProperty() 實現雙向綁定

/*
  自己寫的代碼, 實現vue的雙向綁定和依賴收集
  場景: 多個子組件用到父組件data中的數據, 當父組件data中的此數據發生改變時, 
  所有依賴它的 子組件全部更新
  通常子組件的從父組件中拿取的數據不允許發生改變
*/
  //訂閱者 Dep
  //一個訂閱者只管理一個數據
  class Dep {
    constructor () {
      this.subs = []  //存放vue組件
    }
    addSubs (sub) {
      this.subs.push(sub)
      console.log('add watcher: ', sub._name)
    }
    notify () {
      this.subs.forEach( sub => {  //通知vue組件更新
        sub.update()
      })
    }
  }
  //監聽者
  //一個vue實例包含一個Watcher實例
  class Watcher {
    // 在實例化Watcher時, 將Dep的target指向此實例, 在依賴收集中使用
    // 因爲依賴收集是在組件初始化時觸發的, 而數據變更後視圖相應變更是在初始化後
    // 所以讓Dep.target指向此實例, 當此vue實例初始化完成後, 再指向下一個正在初始化的vue實例完成依賴收集
    constructor (name) {
      Dep.target = this
      this._name = name
    }
    update () {
      // 這裏模擬視圖更新
      // 其實還應該讓子組件的props相應值與父組件更新的數據同步
      console.log("子組件視圖更新了..." + this._name)
    }
  }
  //對data中的數據設置讀寫監聽, 並且創建訂閱者, 用於收集子組件的依賴和發佈
  function defineReactive (obj, key, value) {
    // 對vue實例中data對象的每一個屬性都 設置一個訂閱者Dep
    let dep = new Dep()
    // 第二個vue實例的監聽 覆蓋了第一個vue實例的監聽, 因爲引用的obj是同一個
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      get () {  
      // 在讀此屬性時, 將當前 watcher 對象收集到此屬性的 dep 對象中
      // 在實例化vue時將Dep.target指向當前Watcher
      // get()依賴收集的時候是vue組件初始化的時候, set()是在初始化後
        if (dep.subs.indexOf(Dep.target) === -1) {
          dep.addSubs(Dep.target)
        }
        //return obj[key]   此寫法報錯 提示棧溢出 原因是無限調用get()
        return value
      },
      set (newVal) {  // 此屬性改變時, 通知所有視圖更新
        if (newVal !== value) {
          value = newVal
          dep.notify()  
        }
      }
    })
  }
  //接收一個對象作爲參數, 將該對象的所有屬性調用defineReactive設置讀寫監聽
  function observer (obj) {
    if (!obj || (typeof obj !== 'object')) {
      return 
    }
    Object.keys(obj).forEach( key => {
      defineReactive(obj, key, obj[key])
    }) 
  }
  // 構造函數, 監聽 配置options中的data()方法返回的對象的所有屬性 的讀寫
  class Vue {
    constructor (options) {
      this._name = options.name
      this._data = options.data
      // 每個vue組件都是一個vue實例, 在一個頁面中有多個vue實例
      // 在初始化該vue實例時, new一個Watcher對象, 使Dep.target指向此實例
      new Watcher(options.name)
      // 給data中的數據掛載讀寫監聽
      observer(this._data)
      //模擬vue解析template過程, 獲取從父組件傳遞過來的props
      //在這裏進行依賴收集
      this._props = options.props ? getProps() : {}
      // 實例化該組件的子組件
      this._children = options.render ? (options.render() || {}) : {}
    }
  }
  // 父組件數據
  let data = {
    first: "hello",
    second: 'world',
    third: ['啦啦啦']
  }
  let times = 0
  // 第一次調用返回的是第一個子組件的從父組件繼承的數據(vue中props屬性的值)
  // 第二次調用返回的是第二個子組件的從父組件繼承的數據(vue中props屬性的值)
  function getProps () {
    times++
    if (times == 1) {
      let obj = {first: "", second: ""}
      Object.keys(obj).forEach( key => {
        // 如果是對象, 則進行深拷貝
        // 這裏使用到了父組件的數據, 觸發依賴收集
        if (data[key] instanceof Object) {
          obj[key] = JSON.parse(JSON.stringify(data[key]))
        } else {
          obj[key] = data[key]
        } 
      })
      return obj
    } else if (times == 2) {
      let obj = {first: "", third: ""}
      Object.keys(obj).forEach( key => {
        if (data[key] instanceof Object) {
          obj[key] = JSON.parse(JSON.stringify(data[key]))
        } else {
          obj[key] = data[key]
        } 
      })
      return obj
    }  
  }
   let vue_root = new Vue({
     name: 'vue_root',
     data,
     //模擬編譯template和實例化vue的過程 
     //在編譯父組件 並且傳遞參數給子組件時, 將子組件的 watcher 添加進父組件的 dep
     render () {
       let vue_1 = new Vue({
         name: 'vue_1',
         data: {},
         props: true,
         render () {}
       }) 
       let vue_2 = new Vue({
         name: 'vue_2',
         data: {},
         props: true,
         render () {}
       }) 
       return {
         vue_1,
         vue_2
       }
     }
   })
  console.log(vue_root)
   vue_root._data.first = 'hello hello'  // vue_1 和 Vue_2 都依賴此數據, 都更新
   vue_root._data.third = "aaa"      // 只有 vue_2 依賴到了此數據, 更新

總結

以上所述是小編給大家介紹的vue的雙向綁定和依賴收集遇到的坑,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章