Vue源碼解讀之響應式數據原理

前言

通過珠峯課程的學習來理解 Vue 源碼的響應式原理 。

響應式原理

1.核心點: Object.defineProperty
2.默認 Vue 在初始化數據時,會給 data 中的屬性使用 Object.defineProperty 重新定義所有屬性,當頁面獲取到對應屬性時。會進行依賴收集(收集當前組件的watcher) 如果屬性發生變化會通知相關依賴進行更新操作。

什麼叫依賴收集 ?
通過Object.defineProperty在重新定義data屬性的時候,進行攔截,再進行實際渲染 ; 那實際渲染之前的一系列處理邏輯就是依賴收集上邊有說,會在依賴收集的時候爲每一個屬性創建一個watcher,如果屬性發生變化,則通知對應的 watcher 更新視圖

來看看源碼
1,首先從構造函數初始化看起 ,src/core/instance/index.js 由於我們主要分享響應式數據原理,也就是初始化Vue數據是如何渲染並建立監聽的,主要看 stateMixin 模塊
在這裏插入圖片描述
2,進入stateMixin 模塊 , 我們直接看向 initData 函數

function initData (vm: Component) { // 初始化data
  let data = vm.$options.data // 獲取到用戶傳入的data數據
  data = vm._data = typeof data === 'function' // 模板語法與標準語法區分獲取data數據
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) { 
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key) // es6 proxy 代理
    }
  }
  /*
  中間我就跳過了,看意思是非生產環境下,對 props , methods 的一些定義,聲明做的判斷,不允許重複聲明
  另外就是添加了 proxy , es6 新增代理屬性 , 包含所有 Object.defineProperty 的功能, 重要的一點是解決 	 		了不能對,數組,對象監聽的問題等。
  */
  // observe data
  observe(data, true /* asRootData */) // 重點在這兒
}

重點 : observe(data, true /* asRootData */) 接下來我們看向 observer 方法

src/core/observer/index.js 113 行 ,其實 obsrver 方法基本沒有做多少事兒 , 就是對 data 類型做了判斷,然後判斷data屬性是否已經被監聽,如果監聽了直接賦值,沒有監聽則創建監聽 new Observer()

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    /* 
    不是對象不進行觀測,如:不管是模板語法還是標準語法data均是一個對象
    
    data () {   模板語法返回一個對象
      return  {}
    } 
    new Vue ({  標準語法 data 也是一個對象
      data:{}
    })
    */
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 已經被監聽的,不會重複監聽
    ob = value.__b__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && // 是否可擴展
    !value._isVue
  ) {
    ob = new Observer(value) //重點,重點,重點, 回調回去,觀測對象類型
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

寫了三個重點,那麼我們來看一下 Observer 類到底做了那些事兒 看代碼
src/core/observer/index.js 37 行

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) { // 構造函數
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
 /*
 觀測呢分爲兩種,一種是數組,一種是對象
 數組:是通過改寫數組原型方法,包含 push,shift,unshift,pop ,splice, sort 等,也就是說 vue 監聽數組變化是通過改寫原型方法 + 遞歸遍歷實現的數據觀測 。 後續我們詳解
 */
    if (Array.isArray(value)) { // 是數組
      if (hasProto) {
        protoAugment(value, arrayMethods) // 改寫數組原型方法
      } else {
        copyAugment(value, arrayMethods, arrayKeys) // 複製數組已有方法
      }
      this.observeArray(value) // 深度觀察數組中的每一項 , observeArray 往下看
    } else { 
      this.walk(value) // 重新定義對象類型數據   walk 往下看
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) { // 是對象則走進這個方法,上邊有判斷
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { // 遍歷對象
      defineReactive(obj, keys[i]);  // 定義響應式數據,這裏可以看到 defineReactive 方法
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) { // 是數組則遍歷數組,走進observer方法查看是否被監聽
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])  // 觀測數組中的每一項 
    }
  }
}

通過上邊的代碼,我們看到了Observer 類主要做了一件事,就是區分數組和對象,並對數組和對象遍歷,創建觀察者 watcher => defineReactive 方法

總之都會走到 defineReactive 方法 接下來我們看代碼
src/core/observer/index.js 148 行 defineReactive 響應式數據綁定關鍵方法 也就是我們常常說到的 Object.defineProperty() 應用的地方 ;所有的初始化數據都會走到這裏 。

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val) // 是數組則遞歸觀測
  Object.defineProperty(obj, key, { // 重點
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () { // 數據的取值
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()  // 收集依賴 watcher , 也就是創建觀察者
        if (childOb) {
          childOb.dep.depend()  // 收集依賴
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) { // 數據的設置值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // 觸發數據對應的依賴進行更新 , 重點,重點,往下看
    }
  })
}

Dep 所有觀察者的集合,也就是 wathcer 的集合,在創建觀測的時候爲每一個data屬性創建了watcher 觀察者(Object.defineProperty方法的 get 裏邊),那麼觸發數據更新的 set 方法會調用 dep.notify(),看代碼

src/core/observer/dep.js 13行,Dep 類可以看見有構造函數,添加watcher ,刪除watcher 等方法,那麼我看來看更新方法notify()

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () { // 通知存儲的依賴更新
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() // 依賴中對應修改屬性的update方法
    }
  }
}

update () 方法在 src/core/observer/wachter.js 166行 ,其實這裏是定義 wachter 觀察者類,裏邊有各種操作 wachter 觀察者的方法,如:增加,修改,清除等 。

總結

好了,分析到這裏,其實就已經很明瞭,針對數據響應式原理,總體的過程就是:
在這裏插入圖片描述

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