【源碼分析】Vue的響應數據

前言

上篇文章【源碼解析】建立Vue實例時幹了什麼?提到響應數據這個詞,到底什麼樣的數據是響應數據呢?node

小提示:配合源碼食用更佳美味。react

怎麼就是響應數據?

先講這樣一個過程。數組

在$mont()的時候,會建立Watcher實例的過程,把Dep.target設置爲當前Watcher,而後會開始render,render的時候就會讀取到響應數據,從而觸發get,只有被觀察的數據才配置了get,get執行過程當中會建立一個Dep實例,此時有了Watcher和Dep,他們會創建關係。他們創建關係以後,當一旦被觀察的數據發生改變,就會觸發set,set調用dep.notify(),dep則會讓跟他有關係的Watcher進行更新。緩存

被觀察的數據更改會致使組件進行更新從而影響到dom的改變。這個被觀察的數據就是響應數據,而這個get的過程咱們叫作依賴收集(後續分析)。性能優化

可觀察對象

在init過程當中 咱們調用了initState -> initData閉包

觀察data

src/core/instance/state.jsapp

// 觀察data
observe(data, true /* asRootData */)
複製代碼

看一下observe函數的實現。進行各類判斷,最終返回Observer實例。dom

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // vnode和不是對象的不須要被觀察
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  
  // 性能優化:帶有__ob__的是已經被觀察的數據 直接返回value.__ob__
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 須要被觀察的 直接建立Observer實例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
複製代碼

Observer類

因爲數組和普通對象的劫持方式不一樣,所以 新建一個Observer類進行來統一。函數

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // __ob__賦值this,性能優化並表示數據已經被觀察
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 數組的觀察邏輯
      // 重寫數組方法進行響應 見下文
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 觀察數組
      this.observeArray(value)
    } else {
       // 對象的觀察邏輯
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      // 定義響應數據 obj
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      // 觀察每一項
      observe(items[i]) 
    }
  }
}
複製代碼

響應數組

對數組方法進行重寫, 而且響應數據源碼分析

src/core/observer/array.js

import { def } from '../util/index'

// 數組原型
const arrayProto = Array.prototype
// 建立新對象 不會對原數組產生影響
export const arrayMethods = Object.create(arrayProto)

// 常會對着7個方法進行劫持,僅有這7個方法會對數組改變
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]


methodsToPatch.forEach(function (method) {
  
  // 緩存原來的方法
  const original = arrayProto[method]
  
  // 重寫方法
  def(arrayMethods, method, function mutator (...args) {
  
    // 調用原來的方法
    const result = original.apply(this, args)
    
    // 緩存
    const ob = this.__ob__
    
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    
    // 再次數據進行響應 確保修改後和新增的數據是響應的
    if (inserted) ob.observeArray(inserted)
    // 觸發更新 重中之重就是這個地方 依賴收集的時候分析
    ob.dep.notify()
    return result
  })
})

複製代碼

響應普通對象

調用了defineReactive(obj, keys[i])

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


  // 這個對象可能設置過getter和setter,須要多調用一次用戶配置的
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 孩子是對象的遞歸觀察
  let childOb = !shallow && observe(val)
  
  // 劫持get和set 響應數據核心
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 先掉一下用戶的getter
      const value = getter ? getter.call(obj) : val
      
      // 收集依賴相關
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      
      // 同樣的值則不須要被賦值
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }

      if (getter && !setter) return
      if (setter) {
        // 用戶的setter
        setter.call(obj, newVal)
      } else {
        // 賦值 閉包
        val = newVal
      }
      // 被設置的須要進行觀察
      childOb = !shallow && observe(newVal)
      // 觸發更新
      dep.notify()
    }
  })
}
複製代碼

系列

結尾

感謝各位的閱讀,錯誤是在所不免的,如有錯誤,或者有更好的理解,請在評論區留言,再次感謝。但願你們相互學習,共同進步。

相關文章
相關標籤/搜索