Vue2原始碼解讀(4) - 響應式原理及簡單實現

石志凱發表於2021-10-24

Vue2原始碼解讀 - 響應式原理及簡單實現

直接進入主題了,想必大家都知道實現vue響應式核心方法就是 Object.defineProperty,那就從它開始說

Object.defineProperty

缺點:

  • 深度監聽,需要遞迴到底,一次性計算量大
  • 無法監聽新增、刪除屬性(需要vue.set 和 vue.delete)
  • 無法原生監聽陣列,需要特殊處理

實現響應式

function updateView () {
  console.log('檢視更新')
}

// 重新定義陣列原型
const oldArrayProperty = Array.prototype
// 建立新物件原型指向 Array.prototype,在擴充套件新的方法不會影響原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
  arrProto[methodName] = function () {
    updateView()
    oldArrayProperty[methodName].call(this, ...arguments)
  } 
});

// 監聽data傳入的屬性
function defineReactive(target, key, value) {
  // 深度監聽 多層物件巢狀
  observer(value)
  // 核心api
  Object.defineProperty(target, key, {
    get() {
      return value
    },

    set(newVal) {
      // 設定新值也要監聽 比如{age:27}
      observer(newVal)
      if (newVal !== value ) {
        value = newVal
        updateView()
      }
    }
  })
}
// 監聽物件屬性
function observer(target) {
  if (typeof target !== 'object' || target === null) {
    // 不是物件或陣列
    return target
  }
  // 監聽陣列 把原陣列的隱式原型賦值給我們定義好的陣列物件
  if (Array.isArray(target)) {
    target.__proto__ = arrProto
  }
  // 重新定義各個屬性,加getter、setter屬性
  for(let key in target) {
    defineReactive(target, key, target[key])
  }
}

const data = {
  name: 'zk',
  age: 26,
  info: {
    address: 'city'  // 需深度監聽
  },
  nums: [1, 2, 3]
}
observer(data)
// data.info.address = 'beijing' // 需要深度監聽
// data.info = {address:'beijing'} // 需要深度監聽
// data.x = 666                  // 新增屬性,監聽不到  需要vue.set方法 
// delete data.name              // 刪除屬性,監聽不到  需要vue.delete方法 
data.nums.push(21)

vue2簡單的資料雙向繫結實現

<div>內容:<span id="content"></span></div>
<input id="iptName" />
const iptName = document.getElementById('iptName')
const content = document.getElementById('content')
let obj = {
  name: ''
}
let newObj = JSON.parse(JSON.stringify(obj))
Object.defineProperty(obj, 'name', {
  get() {
    return newObj.name
  },

  set(val) {
    if (val === newObj.name) return
    newObj.name = val
    observer()
  }
})
function observer () {
  iptName.innerText = obj.name
  content.innerText = obj.name
}
iptName.oninput = function () {
  obj.name = this.value
}

相關文章