實現 vue 的數據響應式原理

實現 vue 的數據響應式原理

這篇文章主要是給不瞭解或者沒接觸過 vue 響應式源碼的小夥伴們看的,其主要目的在於能對 vue 的響應式原理有個基本的認識和了解,若是在面試中被問到此類問題,可以知道面試官想讓你回答的是什麼?「PS:文中若有不對的地方,歡迎小夥伴們指正」vue

響應式的理解

響應式顧名思義就是數據變化,會引發視圖的更新。這篇文章主要分析 vue2.0 中對象和數組響應式原理的實現,依賴收集和視圖更新咱們留在下一篇文章分析。面試

在 vue 中,咱們所說的響應式數據,通常指的是數組類型和對象類型的數據。vue 內部經過 Object.defineProperty 方法對對象的屬性進行劫持,數組則是經過重寫數組的方法實現的。下面咱們就簡單實現一下。編程

  • 首先咱們定義一個須要被攔截的數據
const vm = new Vue({
  data () {return {      count: 0,      person: { name: 'xxx' },      arr: [1, 2, 3]
    }
  }
})let arrayMethodsfunction Vue (options) { // 這裏只考慮對 data 數據的操做
  let data = options.data  if (data) {
    data = this._data = typeof data === 'function' ? data.call(this) : data
  }
  observer (data)
}function observer(data) { 
  if (typeof data !== 'object' || data === null) {return data
  }  if (data.__ob__) { // 存在 __ob__ 屬性,說明已經被攔截過了return data
  }  new Observer(data)
}複製代碼

這裏的 arrayMethods、Observer 、 __ob__的實現和做用請繼續往下看數組

實現 Observer 類

class Observer {  constructor (data) {Object.defineProperty(data, '__ob__', { // 在 data 上定義 __ob__ 屬性,在數組劫持裏須要用到  enumerable: false, // 不可枚舉  configurable: false, // 不可配置  value: this // 值是 Observer 實例})if (Array.isArray(data)) { // 對數組進行攔截  data.__proto__ = arrayMethods // 原型繼承  this.observerArray(data)
    } else { // 對象進行攔截  this.walk(data)
    }
  }
  walk (data) {const keys = Object.keys(data)for(let i = 0; i < keys.length; i++) {      const key = keys[i]
      defineReactive(data, key, data[key])
    }
  }
  observerArray (data) { // 攔截數組中的每一項data.forEach(value => observer(value))
  }
}複製代碼

對象的攔截

對象的劫持須要注意的幾點:ide

  • 遍歷對象,若是值仍是對象類型,須要從新調用 observer 觀測方法
  • 若是設置的新值是對象類型,也須要被攔截
// 處理對象的攔截function defineReactive(data, key, value) {
  observer(value) // 若是 value 值還是對象類型,須要遞歸劫持
  Object.defineProperty(data, key, {get() {      return value
    },set(newValue){      if (newValue === value) return  value = newValue
      observer(newValue) // 若是設置 newValue 值也是對象類型,須要被劫持}
  })
}複製代碼

數組的劫持

數組的劫持須要注意的幾點:函數

  • 數組是使用函數劫持(切片編程)的思想,對數據進行攔截的
  • 數組裏新增長的值,若是是對象類型,也須要被從新攔截
const oldArrayPrototype = Array.prototype
arrayMethods = Object.create(oldArrayPrototype)const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 可以改變原數組的方法methods.forEach(method => {
  arrayMethods[methods] = function (...args) {const result = oldArrayPrototype[methods].call(this, ...args)const ob = this.__ob__ // this 就是調用改方法的數組let inserted; // 數組新增的項的集合,須要再對其進行攔截switch(methods) {      case 'push': 
      case 'unshift':
        inserted = args      case 'splice':
        inserted = args.slice(2) // 由於 splice 第二個參數後面的纔是新增的}if (inserted) {
      ob.observerArray(inserted)
    }return result
  }
})複製代碼

原理總結

在面試中,若是咱們須要手寫 vue 的響應式原理,上面的代碼足矣。可是咱們經過學習 vue 的源碼,若是在面試中可以給出如下加以總結性的回答更能獲得面試官的青睞。 vue 2.0 源碼的響應式原理:性能

  • 由於使用了遞歸的方式對對象進行攔截,因此數據層級越深,性能越差
  • 數組不使用 Object.defineProperty 的方式進行攔截,是由於若是數組項太多,性能會不好
  • 只有定義在 data 裏的數據纔會被攔截,後期咱們經過 vm.newObj = 'xxx' 這種在實例上新增的方式新增的屬性是不會被攔截的
  • 改變數組的索引和長度,不會被攔截,所以不會引發視圖的更新
  • 若是在 data 上新增的屬性和更改數組的索引、長度,須要被攔截到,可使用 $set 方法
  • 可使用 Object.freeze 方法來優化數據,提升性能,使用了此方法的數據不會被重寫 set 和 get 方法

vue 3.0 源碼響應式原理:學習

  • 3.0 版本中使用了 proxy 代替了 Object.defineProperty ,其有13中攔截方式,不須要對對象和數組分別進行處理,也無需遞歸進行攔截,這也是其提高性能最大的地方
  • vue 3.0 版本響應式原理的簡單實現
const handler = {
  get (target, key) {if (typeof target[key] === 'object' && target[key] !== null) {      return new Proxy(target[key], handler)
    }return Reflect.get(target, key)
  },
  set (target, key, value) {if(key === 'length') return trueconsole.log('update')return Reflect.set(target, key, value)
  }
}const obj = {  arr: [1, 2, 3],  count: { num: 1 }
}// obj 是代理的目標對象, handler 是配置對象const proxy =  new Proxy(obj, handler)複製代碼
相關文章
相關標籤/搜索