【Vue源碼】第十六節深入響應式原理之依賴收集

學習Object.defineProperty

Object.defineProperty(obj, prop, descriptor)
  • obj:必需。目標對象;
  • prop: 必需。需定義或修改的屬性的名字;
  • descriptor:必需。目標屬性所擁有的特性

descriptor可以賦值以下內容:

  • value:屬性對應的值,可以使任意類型的值,默認爲undefined;
  • writable:屬性的值是否可以被重寫。設置爲true可以被重寫;設置爲false,不能被重寫。默認爲false;
  • enumerable:此屬性是否可以被枚舉(使用for…in或Object.keys()),默認爲false;
  • configurable:是否可以刪除目標屬性、是否可以再次修改屬性的特性(writable, configurable, enumerable),默認爲false;
  • getter:當使用了getter或setter方法,不允許使用writable和value這兩個屬性
  • setter :使用了getter或setter方法,不允許使用writable和value這兩個屬性。
const obj = {};
Object.defineProperty(obj, 'name', {
    enumerable: true,
    configurable: true,
    getter:function(){},
    setter: function(){}
})

由於Object.defineProperty不兼容IE8及以下瀏覽器,所以Vue不兼容IE8及以下瀏覽器。

將數據變爲響應式數據

new Vue_init過程中,有個initState方法,它主要是對 propsmethodsdatacomputedwathcer 等屬性做了初始化操作。每個初始化的具體代碼就不貼了,主要提一下propsdata的處理方法中。

在處理props時,主要做了以下兩個步驟:

  • defineReactiveprops的每個值添加gettersetter方法;
  • proxyvm._props.xxx 的訪問代理到 vm.xxx 上。

在處理data時,主要是:

  • 通過 proxy 把每一個值 vm._data.xxx 都代理到 vm.xxx 上;
  • 另一個是調用 observe 方法觀測整個 data 的變化。

observe方法是幹什麼的?

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 嘗試爲一個值創建一個觀察者實例,
 * 如果成功觀察到,則返回新的觀察者,
 * 如果值已經有一個,則返回現有的觀察者。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  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
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

observe 方法的作用就是給非 VNode 的對象類型數據添加一個 Observer,如果已經添加過則直接返回,否則在滿足一定條件下去實例化一個 Observer 對象實例。接下來我們來看一下 Observer 的作用。

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 * 附加到每個被觀察對象的觀察者類。
 * 一旦附加,觀察者將目標對象的屬性鍵轉換爲getter/setter,
 * 以收集依賴項並分派更新。
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    // 創建一個Dep實例,Dep下面會介紹
    this.dep = new Dep()
    this.vmCount = 0
    // 把自身實例添加到數據對象 value 的 __ob__ 屬性上
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      // 對數組會調用 observeArray 方法,即遞歸調用observe
      this.observeArray(value)
    } else {
      // 純對象調用 walk 方法,爲對象每個屬性調用defineReactive方法
      this.walk(value)
    }
  }

  /**
   * Walk through each property 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])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

由上面的分析可知,在處理data時,我們會調用observe方法,在這個方法中會對value進行判斷,如果是數組,就遞歸調用observe方法;如果是對象就調用defineReactive方法。

即對propsdata的處理都

  • 調用proxy實現vm._props.xxx 和vm._data.xxxx的讀寫變成了 vm.xxx 的讀寫;
  • 調用了defineReactive方法。

defineReactive

於是我們要來學習defineReactive方法了,這個方法就是爲將對象轉爲響應式對象,給對象新增gettersetter方法,當我們讀取這些對象時,就會調用getter方法,改變他們的值時就會調用setter方法。

/**
 * Define a reactive property on an Object.
 * 在對象上定義響應式屬性
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 創建一個Dep實例,Dep下面會介紹
  const dep = new Dep()

  // 獲得每個屬性的property,因爲要新增getter和setter,如果configurable是false就沒得玩了
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 提供預定的  getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  
  // 對子對象遞歸調用 observe 方法,這樣就保證了無論 obj 的結構多複雜,
  // 也會調用到defineReactive方法
  // 它的所有子屬性也能變成響應式的對象,添加了getter和setter方法
  // 在分析getter和setter之前,我們得先學習一下Dep
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
	 // ...
    },
    set: function reactiveSetter (newVal) {
      // ...
    }
  })
}

Dep

Vue實現響應式中有一個重要的東西:Dep,可以把它理解成vuex中的store。它存儲管理着多個watcher,這些watcher是對響應式數據的監聽。每次數據變爲響應式數據(watcher)時,就會調用getter告訴Dep我被監聽啦,然後它就被`Dep`管理了。當響應式數據發生改變時,會調用`setter`告訴`Dep`我改變啦,然後`Dep`就會告訴`render`:有些數據改變啦,麻煩改變一下視圖render就會更新視圖了~~
在這裏插入圖片描述

我們先來看源碼中Dep的實現, Dep主要有targetsubs存放多個watcher的數組,還有一些添加、刪除、依賴和訂閱的方法。

// src/core/observer/dep.js
import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * dep是一個可觀察的,可以有多個
 */
export default class Dep {
  // target這是一個全局唯一 Watcher, 在同一時間只能有一個全局的 Watcher 被計算
  static target: ?Watcher;
  id: number;
  // subs 也是 Watcher 的數組
  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()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}

Dep離不開Watcher

Watcher

_initState時,我們的響應式數據已經有了gettersetter方法,什麼時候會調用getter方法呢?

_render的過程中我們會去實例化一個Watcher對象。

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
  if (vm._isMounted && !vm._isDestroyed) {
    callHook(vm, 'beforeUpdate')
  }
}
}, true /* isRenderWatcher */)

進入到Watcher中,賦值各種值,然後調用了get方法

let uid = 0

/**
 * A watcher parses an expression,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 * watcher 監視程序解析一個表達式,收集依賴項
 * 並在表達式值改變時觸發回調
 * 這在$watch() api和指令中都使用
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  // watcher分多種
  // 還記得vue中watch的deep嗎,就是這個
  // [1] deep watcher:深度觀測
  // 當deep爲true時,會調用traverse函數,對一個對象做深層遞歸遍歷,歷過程中就是對一個子對象的訪問,會觸發它們的 getter 過程,這樣就可以收集到依賴
  deep: boolean;  
  // [2] user watcher
  // 在對 watcher 求值以及在執行回調函數的時候,會處理一下錯誤
  user: boolean;
  // [3] computed watcher
  // 爲計算屬性量身定製的
  lazy: boolean;
  // [4] sync watcher
  // 由於數據更新是異步的,但是比如:value.sync=""時,value更新,會同步更新
  sync: boolean;
  dirty: boolean; 
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep  // 是否是 deep Watcher
      this.user = !!options.user  // 是否是 user Watcher
      this.lazy = !!options.lazy  // 是否是 computed watcher
      this.sync = !!options.sync  // 是否是同步更新
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []   // deps 表示上一次添加的 Dep 實例數組
    this.newDeps = [] // newDeps 表示新添加的 Dep 實例數組
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // pushTarget 的定義在 src/core/observer/dep.js 中
    // export function pushTarget (_target: Watcher) {
    //  if (Dep.target) targetStack.push(Dep.target)
    // 	Dep.target = _target
    // }
    // 實際上就是把 Dep.target 賦值爲當前的渲染 watcher 並壓棧(爲了恢復用)
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
      // 在上面有這麼一行代碼: this.getter = expOrFn
      // 即調用updateComponent
      // vm._update(vm._render(), hydrating)
      // 在渲染VNode的過程中就會對數據進行訪問,所以就訪問到了數據的getter方法
      // 開始來分析一下getter方法:
        
      // get: function reactiveGetter () {
      //    const value = getter ? getter.call(obj) : val
      //    【註釋】Dep.target是當前的watcher
      //    if (Dep.target) {
      //    【註釋】dep.depend即是調用了Dep.target.addDep(this);
      //    【註釋】又由於Dep.target是當前的watcher所以就是調用了addDep,所以要去分析addDep了
      //    【註釋】當前的 watcher 訂閱到這個數據持有的 dep 的 subs 中
      //    dep.depend()
      //    if (childOb) {
      //      childOb.dep.depend()
      //      if (Array.isArray(value)) {
      //       dependArray(value)
      //     }
      //    }
      // }
      //  return value
      //  },
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      // 遞歸去訪問 value,觸發它所有子項的 getter
      if (this.deep) {
        traverse(value)
      }
      // Dep.target = targetStack.pop()
      // 將當前watcher推出
      popTarget()
      // 依賴清空
      // 在執行 cleanupDeps 函數的時候,
      // 首先遍歷 deps,移除對 dep.subs 數組中 Wathcer 的訂閱,
      // 然後把 newDepIds 和 depIds 交換,newDeps 和 deps 交換,
      // 並把 newDepIds 和 newDeps 清空
      // 保證在切換視圖時,不會對舊視圖的數據還進行監控
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

}

關於依賴收集就寫到這裏,畫了一張圖消化一下,唔知有沒有錯誤:
在這裏插入圖片描述
init時,我們會調用initState方式去初始化propsdatacomputed等內容,其中以propsdata爲例,發現主要是使用defineReactive方法給這些數據加上了gettersetter方法。之後我們在$mount方法中,創建了Watcher實例,這個實例調用了_render方法,會讀取到數據,於是將會觸發數據上的getter方法,在getter方法上,將當前的 watcher 訂閱到這個數據持有的 dep 的 subs 中,然後清空依賴完成了依賴收集

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