結合源碼聊一聊Vue的響應式原理

Vue經過響應式設計實現了數據的雙向綁定,可以作到頁面數據的動態更新。咱們都知道Vue實現響應式的核心是 Object.defineProperty(),可是具體是如何實現的?相信不少人都是不清楚的。這篇文章咱們就結合Vue源碼來了解一下響應式的實現方式。

Vue的響應式實現方式能夠分爲三個主要部分:數據劫持(observe)、依賴收集並註冊觀察者(watcher)、派發更新。

一、數據劫持(Object.defineProperty() )

數據劫持就是對數據的讀寫操做進行監聽,也只有這樣,Vue才能知道咱們的數據修改了,它須要更新DOM;或者是用戶輸入內容了,它須要更新對應的變量。
在說數據劫持的實現以前,咱們先來了解一下 Object.defineProperty() 屬性,由於這個屬性是Vue2 實現響應式的核心原理。

Object.defineProperty()

Object.definProperty(obj, prop, descriptor)
這個方法的做用是直接在目標對象上定義一個新的屬性,或者修改目標對象的現有屬性,而後返回這個對象。

let obj = {}
Object.defineProperty(obj, 'name', {
    value: '猿叨叨'
})
obj // {name: '猿叨叨'}複製代碼

可是這個方法的做用遠不止這些,它的 descriptor 參數接收的是一個對象,給出了不少能夠配置的屬性,具體你們能夠自行查看 MDN文檔 。咱們今天只關心它的存取描述符 get 和 set,get 在讀取目標對象屬性時會被調用,set 在修改目標對象屬性值時會被調用。經過這兩個描述符,咱們能夠實現對象屬性的監聽,並在其中進行一些操做。

let obj = {}
let midValue
Object.defineProperty(obj, 'name', {
    get(){
        console.log('獲取name的值')
        return midValue
    },
    set(val){
        console.log('設置name')
        midValue = val
    }
})
obj.name = '猿叨叨'  //設置name '猿叨叨'
obj.name  //獲取name的值 '猿叨叨'複製代碼

initState()

在上一篇文章 《 結合源碼聊一聊Vue的生命週期》中,咱們提到在created以前會執行 initState (點擊能夠查看該方法源碼)方法,該方法主要是初始化props、methods、data等內容,今天咱們只關心data。

initData()

data的初始化調用了 initData 方法,咱們來看一下這個方法的源碼。

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? 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)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}複製代碼

這個函數首先獲取到組件的data數據,而後對data進行一系列的判斷:data返回值必須是對象,data中變量的命名不能與props、methods重名,不能是保留字段等。這些條件都知足之後執行 proxy() 方法,最後對整個 data 執行 observe() 方法,接下來咱們分別看這兩個方法都幹了些什麼。

proxy()

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}複製代碼

這個函數的邏輯很簡單,經過 Object.defineProperty() 方法將屬性代理到 vm 實例上,這樣咱們 vm.xxx 讀寫到自定義的數據,也就是在讀寫 vm._data.xxx。

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,不然直接返回,而後判斷當前對象是否存在 __ob__ 屬性,若是不存在而且傳入的對象知足一系列條件,則經過 Observe 類實例化一個 __ob__。那麼接下來咱們就要看 Observe 類的邏輯了。

Observe

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)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /** * 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])
    }
  }

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

咱們來看一下 Observe 類中構造函數的邏輯:
  • 首先是實例化 Dep(),Dep主要是用來管理依賴的,咱們下一部分再詳細展開。
  • 而後經過 def() 把當前組件實例添加到 data數據對象 的 __ob__ 屬性上
  • 對傳入的value進行分類處理,若是是數組,則調用 observeArray() 方法;若是是對象,則調用 walk()。
walk() 方法對傳入的對象進行遍歷,而後對每一個屬性調用 defineReactive() 方法;observeArray() 方法遍歷傳入的數組,對每一個數組元素調用 observe() 方法,最終仍是會對每一個元素執行walk()方法。

defineReactive()

/** * Define a reactive property on an Object. */
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()
        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;而後經過 Object.getOwnPropertyDescriptor 方法拿到傳入對象屬性的描述符;通過判斷後對子對象遞歸調用 observe 方法,從而作到無論對象層次有多深均可以保證遍歷到每個屬性並將其變成響應式屬性。

若是調用data的某個屬性,就會觸發 getter,而後經過 dep 進行依賴收集,具體流程咱們後面進行分析;當有屬性發生改變,會調用 setter 方法,而後經過 dep 的 notify 方法通知更新依賴數據的DOM結構。

總結

這個階段主要的做用就是將數據所有轉換爲響應式屬性,能夠簡單地歸納成:
  • 在生命週期 beforeCreate 以後,created以前進行用戶數據初始化時,執行initData初始化data數據,initData調用proxy和observe
  • proxy 將數據代理到組件實例上,vm._data.xxx → vm.xxx
  • observe 調用 Observe 類對屬性進行遍歷,而後調用 defineReactive
  • defineReactive 將屬性定義爲 getter和setter。getter中調用 dep.depend();setter中調用 dep.notify()。

二、依賴收集

Dep

前面遇到了 Dep 類的實例化,而且調用了裏邊的 depend 和 notify 等方法,接下來咱們就看一下 Dep 類裏面作了什麼。

/** * A dep is an observable that can have multiple * directives subscribing to it. */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array;

  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()
    }
  }
}複製代碼

Dep類主要初始化了 id 和 subs,其中 subs 是用來存儲 Watcher 的數組。另外,Dep 還有一個靜態屬性 target,它也是 Watcher 類型,而且全局惟一,表示的是當前正在被計算的 Watcher。除此以外,它還有一些方法:addSub用來往 subs 數組裏插入 Watcher;removeSub用來從 subs 中移除;depend調用的是 Watcher 的addDep方法;notify會對subs中的元素進行排序(在條件知足的狀況下),而後進行遍歷,調用每一個 Watcher 的update方法。

從上面咱們能夠看出來,Dep類其實就是對Watcher進行管理的,因此接下來咱們看看Watcher裏都作了哪些事情。

Watcher

let uid = 0

/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */
export default class Watcher {
  //...

  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
      this.user = !!options.user
      this.lazy = !!options.lazy
      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 = []
    this.newDeps = []
    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(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } 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
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
}複製代碼

watch實例有兩種類型,一直是咱們經常使用的自定義watch,還有一種是Render類型。在上一篇文章瞭解生命週期時,mounted 階段有一段這樣的代碼:

// 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,對組件進行監聽。咱們來看一下watcher實例化的過程:

進入Watcher的構造函數後,通過初始化參數和一些屬性之後,執行 get() 方法,get主要有如下幾步:
這個方法就是把當前正在渲染的 Watcher 賦值給Dep的target屬性,並將其壓入 targetStack 棧。

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}複製代碼

  • 接下來在 try catch 中執行了下面的代碼:

value = this.getter.call(vm, vm)複製代碼

this.getter,調用的是建立 Watcher 時傳入的回調函數,也就是在實例化 Watcher 時傳入的 updateComponent ,而這個方法執行的是 vm._update(vm._render(), hydrating) , vm._render 會將代碼渲染爲 VNode,這個過程勢必是要訪問組件實例上的數據的,在訪問數據的過程也就觸發了數據的 getter。從 defineReactive 方法中咱們知道 getter 會調用 dep.depend() 方法,繼而執行Dep.target.addDep(this) ,由於 Dep.target 拿到的是要渲染的 watcher 實例,因此也就是調用當前要渲染 watcher 實例的 addDep() 方法。
  • addDep

/** * 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)
      }
    }
  }複製代碼

addDep() 首先進行判斷,保證當前數據所持有 dep 實例的 subs 數組中不存在當前 watcher 實例,而後執行 dep.addSub(),將當前 watcher 實例加入當前數據所持有的的 dep 實例的 subs 數組中,爲後續數據變化是更新DOM作準備。


至此已經完成了一輪的依賴收集,可是後面在 finally 還有一些邏輯:

// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
    traverse(value)
}
popTarget()
this.cleanupDeps()複製代碼

  • 首先對若是deep參數爲true,就對 value 進行遞歸訪問,以保證可以觸發每一個子項的 getter 做爲依賴進行記錄。
  • 執行 popTarget() 
這一步是在依賴收集完成後,將完成的 watcher 實例彈出 targetStack 棧,同時將 Dep.target 恢復成上一個狀態。

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}複製代碼

  • 執行 this.cleanupDeps()
整理並移除不須要的依賴

/** * 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
  }複製代碼

總結

這個階段主要是對依賴某個數據的組件信息進行收集,也能夠理解爲創建數據和組件之間的對應關係,以便在數據變化時知道哪些組件須要更新。該過程能夠簡單地總結爲如下步驟:
  • 在create階段完成data數據初始化之後進入mount階段,此階段會建立 渲染類型的watcher實例監聽 vm 變化,回調函數爲 updateComponent;watcher實例化的過程當中完成了 dep.target 的賦值,同時觸發傳入的回調函數。
  • updateComponent 被觸發,回調函數內部執行 vm._update(vm._render(), hydrating),vm._render() 會將代碼渲染爲 VNode。
  • 代碼渲染爲 VNode 的過程,會涉及到數據的訪問,勢必會觸發上一節定義的數據的 getter,getter中調用 dep.depend(),進而調用 watcher.addDep(),最終調用到 dep.addSub 將watcher實例放入 subs 數組。
至此依賴收集的過程已經完成,當數據發生改變時,Vue能夠經過subs知道通知哪些訂閱進行更新。這也就到了下一階段:派發更新。

三、派發更新

通過第二部分,頁面渲染完成後,也完成了依賴的收集。後面若是數據發生變化,會觸發第一步對數據定義的setter,咱們再來看一下 setter 的邏輯:

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()
}複製代碼

在 setter 最後,調用了 dep.notify(),上一節在看 Dep 類的源碼時咱們知道,notify 方法會調用subs數組中每一個 watcher 實例的 update 方法。

/** * Subscriber interface. * Will be called when a dependency changes. */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }複製代碼

上述代碼中,lazy 和 sync 兩種狀況本文不展開詳說,大部分數據更新時走的是最後else分支,執行 queueWatcher() 方法,接下來咱們看一下這個方法的代碼:

queueWatcher() 

/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}複製代碼

這個函數引入了隊列的概念,用來存儲等待更新的 watcher 實例,由於Vue並不會每次數據改變都觸發 watcher 回調,存入隊列後,在 nextTick 後執行。
在函數最開始,經過has判斷保證每一個 watcher 在隊列裏只會被添加一次;而後判斷 flushing ,這個值在調用 flushSchedulerQueue 方法時,會置爲 true,所以這個值能夠用來判斷當前是否處於正在執行 watcher 隊列的狀態中,根據這個值,分爲兩種狀況:
  1. 當 flushing 爲false,直接將watcher塞入隊列中。
  2. flushing 爲true,證實當前隊列正在執行,此時從隊列尾部往前找,找到一個待插入隊列watcher的id比當前隊列中watcher的id大的位置,而後將它插入到找到的watcher後面。
執行完上述判斷邏輯後,經過waiting判斷會否能夠繼續往下執行,waiting在最外層被初始化爲false,進入if內部,被置爲true,而且在隊列執行完畢進行reset以前不會改變。這樣能夠保證每一個隊列只會執行一次更新邏輯。更新調用的是 flushSchedulerQueue 方法:

flushSchedulerQueue()

/** * Flush both queues and run the watchers. */
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  // created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  // user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  // its watchers can be skipped.
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}複製代碼

更新方法首先會將flushing 置爲true,而後對 watcher 隊列進行排序,根據註釋能夠知道要保證如下三個條件:
  • 更新從父到子,由於建立是從父到子,所以父組件的 watcher 要比子組件先建立
  • 用戶自定義的watcher要先於渲染watcher執行
  • 當某個組件在其父組件 watcher 執行期間被銷燬了,那麼這個組件的watcher能夠跳過不執行
排序完成後,對隊列進行遍歷,這裏須要注意的是,遍歷時不能緩存隊列的長度,由於在遍歷過程當中可能會有新的watcher插入,此時flushing爲true,會執行上一個方法中咱們分析的第二種狀況。而後若是watcher存在before參數,先執行before對應的方法;以後執行watcher的run方法。再日後就是對死循環的判斷;最後就是隊列遍歷完後對一些狀態變量初始化,由resetSchedulerState方法完成。
咱們看一下 watcher 的 run 方法:

watcher.run()

/** * Scheduler job interface. * Will be called by the scheduler. */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }複製代碼

run方法首先經過 this.get() 拿到新值,而後進行判斷,若是知足新值不等於舊值、新值是一個對象或者deep模式任何一個條件,則執行 watcher 的回調函數,並將新值和舊值分別做爲第一個和第二個參數,這也是爲何咱們在使用watcher時總能拿到新值和舊值的緣由。

若是是渲染類型的watcher,在run方法中執行 this.get() 求值是,會觸發 getter 方法,拿到新值後,會調用watcher的回調函數 updateComponent,從而觸發組件的從新渲染。

總結

這個階段主要是當數據發生變化時,如何通知到組件進行更新,能夠簡單總結爲如下幾個步驟:
  • 當數據發生變化,會觸發數據的 setter,setter 中調用了 dep.notify。
  • dep.notify 中遍歷依賴收集階段獲得的 subs 數組,並調用每一個watcher元素的 update 方法。
  • update 中調用 queueWatcher 方法,該方法整理傳入的watcher實例,並生成 watcher 隊列,而後調用 flushSchedulerQueue 方法。
  • flushSchedulerQueue 完成對 watcher 隊列中元素的排序(先父後子,先自定義後render,子組件銷燬可調過次watcher),排序完成後遍歷隊列,調用每一個watcher的 run 方法。
  • run 方法中調用 this.get() 獲得數據新值,而後調用watcher的回調函數,渲染類watcher爲 updateComponent,從而觸發組件的從新渲染。


再看一遍這個圖,是否是感受好像看明白了~javascript

四、挖個坑

到這裏Vue的響應式原理基本已經結束了,可是相信大多數人也都據說過,Vue3 響應式實現的方式再也不使用 Object.definePeoperty。緣由咱們也知道,Vue是在初始化階段經過轉換setter/getter完成的響應式,因此沒有辦法檢測到初始化之後新增的對象屬性;同時不能檢測經過下標對數組元素的修改。

Vue3 實現響應式的方式改成使用 Proxy,具體的實現這篇文章留個坑,下一篇來填。若是還不瞭解 Proxy 的同窗能夠先去看一看介紹: MDN-Proxy
相關文章
相關標籤/搜索