深入理解vue响应式原理

b546d83e314836cd24798e6d4894d4f1.jpeg

非侵入性的响应式系统是vue最独特的特性之一。作为一个基于MVVM的前端框架,vue的数据模型只是一个普通的 js 对象。当我们修改数据对象时,视图会进行更新。习惯了响应式系统带给我们的便利之外,我们有必要理解其工作原理。接下来,我们来研究一下vue响应式系统的底层细节。

defineProperty

vue内部使用Object.defineProperty把我们通过data定义的数据对象上的所有属性转换成gettersetter方法。当访问这些属性的时候,watcher实例将属性收集为依赖。当修改这些属性的时候,这些依赖又会通知watcher实例对页面进行更新。

vue内部实际上使用了两次Object.defineProperty 来转换数据对象的属性。

第一次是对vue实例使用Object.defineProperty。数据对象在vue内部有一个副本_data。在vue底层对数据对象的访问和修改都是对 _data的操作。通过 Object.defineProperty 将 _data 所有的属性全部代理到了vue实例上。当我们操作vue实例的属性时,就实现了对_data的操作。此外,vue底层会检查data、props、methods定义的属性名和方法名,如果命名有冲突在开发环境中会有提示。

const sharedPropertyDefinition = {
 enumerable: true,
 configurable: true,
 get: noop,
 set: noop
}

/**
* 使用在target上创建key代理this[sourceKey]对象上对应的key
* @param {Object} target 代理对象
* @param {string} sourceKey 被代理对象的key
* @param {string} key 代理对象的key
*/
export function proxy (target: Object, sourceKey: string, key: string) {
 // 创建getter
 sharedPropertyDefinition.get = function proxyGetter () {
   return this[sourceKey][key]
 }
 // 创建setter
 sharedPropertyDefinition.set = function proxySetter (val) {
   this[sourceKey][key] = val
 }
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 初始化data状态
function initData (vm: Component) {
 let data = vm.$options.data
 // 获取data的值
 data = vm._data = typeof data === 'function'
   ? getData(data, vm)
   : data || {}

 // 使用vue实例代理data对象的所有属性
 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
       )
     }
   }
   // data中的属性名不能与props中重复
   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)) {
     // 如果data中的属性名不是保留字段,则在vue实例上创建对应的属性代理_data中的属性。
     proxy(vm, `_data`, key)
   }
 }
 
 // 为data对象创建观察者,观察data对象中的变化,以自动更新页面
 observe(data, true /* asRootData */)
}

第二次是对数据对象使用Object.defineProperty。vue底层对数据对象的每个属性调用defineReactive,将数据对象的属性全部转换成了gettersetter属性。在getter中,vue会为每个属性创建一个dep(依赖),如果有watcher实例访问了它,则被收集为依赖,在setter中,dep 会通知所有订阅了该依赖的watcher实例去更新页面。

/**
 * 将数据对象的属性转成getter和setter属性
 * @param {Object} obj 目标对象
 * @param {string} key 属性
 * @param {*} val 属性值
 * @param {Function} customSetter 自定义setter函数
 * @param {boolean} shallow 是否浅复制
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建依赖对象
  const dep = new Dep()
  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
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
  
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 数据变化后,通知watcher更新页面
      dep.notify()
    }
  })
}

观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

这一模式中的两个关键角色是观察者和被观察者,一个被观察者可以有任意数目的之相依赖的观察者,一旦被观察者的状态发生改变,所有的观察者都将得到通知。得到状态改变的通知后,每个观察者都会及时更新自己的状态,以与目标状态同步,这种交互方式也称为发布-订阅模式。被观察者是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它和接收通知。

在社区里,开发者一直对观察者模式和发布订阅模式的定义一直有争论。两者都是实现了一种对象间一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知,并自动更新。两者只是在结构上略有不同,分辨模式的关键是意图而不在结构。在实践中对此不必过分较真。

vue响应式系统的实现也是应用了观察者模式。在vue中,dep对象扮演着被观察者,watcher对象扮演着观察者。。当数据对象的属性被访问时,会触发getter方法。在getter方法中,与当前属性所对应的dep会调用depend方法把当前watcher对象添加到订阅者队列中。

当数据对象的属性发生改变时,会触发setter方法。在setter方法中,与当前属性所对应的dep对象调用notify方法通知订阅列表中的所有watcher进行更新。下文贴出depwatcher对象的主要代码实现,大家在脑子里有个大致印象。

f0579ae4e9d54a83bcc92e455f2c096b.png

被观察者Dep

export default class Dep {
  // 当前watcher
  static target: ?Watcher;
  // 依赖的id
  id: number;
  // 订阅该依赖的watcher队列
  subs: Array<Watcher>;

  constructor () {
    // 每个dep实例分配唯一的id
    this.id = uid++
    // subs保存所有的订阅者
    this.subs = []
  }

  /**
   * 添加订阅者,一个watcher是一个订阅者
   * @param {Watcher} sub 
   */
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /**
   * 移除订阅者,一个watcher是一个订阅者
   * @param {Watcher} sub 
   */
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  /**
   * 将dep实例添加为当前watcher的依赖
   */
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /**
   * 通知所有的订阅者更新
   */
  notify () {
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

观察者Watcher

let uid = 0
export default class Watcher {
  // 被观察的Vue 实例
  vm: Component;
  // 观察 Vue 实例上的一个表达式或者一个函数计算结果的变化
  expression: string;
  // 被观察的属性值发生变化后触发的回调函数,回调函数得到的参数为新值和旧值。一般用在watch属性上
  cb: Function;
  id: number;
  // 为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
  deep: boolean;
  user: boolean;
  lazy: boolean;
  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
  ) {
    // 保留对vue实例的引用
    this.vm = vm
    // 如果是render watcher
    if (isRenderWatcher) {
      vm._watcher = this
    }
    // 将当前watcher实例添加到vue实例的_watchers数组中
    vm._watchers.push(this)
    
    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
    // watcher实例收集的依赖实例数组
    this.deps = []
    // watcher实例重新收集的依赖实例数组
    this.newDeps = []
    // watcher实例收集的依赖实例的id组成的set
    this.depIds = new Set()
    // watcher实例重新收集的依赖实例的id组成的set
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 将expression转换为一个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()
  }

  /**
   * 计算getter,重新收集依赖
   */
  get () {
    // 将watcher添加到全局的targetStack中,并作为当前watcher
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 根据getter计算value的值
      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
  }

  /**
   * 1. 如果dep已经在watcher中,则不作任何处理
   * 2. 如果是新增的依赖,那么将dep添加到watcher的依赖数组里
   * 3. 将watcher加到dep的订阅者数组里
   */
  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)
      }
    }
  }

  /**
   * 清除依赖收集
   */
  cleanupDeps () {
    let i = this.deps.length
    // 将watcher从dep的订阅者队列中删除
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    // 更新depIds
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()

    // 更新deps
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * 订阅接口,当依赖发生变化时调用
   */
  update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * 任务调度接口,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)
        }
      }
    }
  }

  /**
   * 计算watcher的值,只有lazy watcher(computed属性对应的watcher)会调用
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * 一次性订阅此watcher收集的依赖。
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * 将watcher从所有依赖的订阅者列表中删除
   */
  teardown () {
    if (this.active) {
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

watcher根据使用场景的不同可以分成三类:

render watcher

每个vue实例在创建的时候,都会创建一个render watcherrender watcher会将页面渲染时所访问的数据对象属性作为依赖收集。当数据对象属性发生改变时,通知render watcher 重新渲染页面。

user watcher

有时我们可能希望在数据变化时执行异步或开销较大的操作。我们可以在watch选项中,创建一个自定义的监听器,来响应数据的变化。vue底层会给在watch中创建的每个监听器,创建一个watcher实例。watcher实例同样会将自己所监听的数据属性作为依赖收集。当数据对象属性发生改变时,watcher会调用在watch中定义的回调函数。回调函数默认在下个事件循环执行。如果用户指定了immediate属性,则会立即调用。除了在watch选项中自定义监听器外,还可以通过$watch定义watcher实例。watch选项在内部也是调用的$watch创建的watcher实例。因为这里的watcher是用户自己创建的,因此也称之为user watcher

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

  // 为Vue实例定义$watch方法
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 如果immediate为true,立即调用回调函数
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
computed watcher

有时我们在模板中使用的数据需要经过复杂的计算后再使用。这种情况,我们可以在computed选项中定义一个计算属性,对原始数据的计算加工都放在计算属性的处理函数中。针对计算属性,vue内部会为每个计算属性创建一个watcher实例,我们称之为computed watcher。在计算属性的处理函数中所访问的数据,会被computed watcher 收集会依赖。计算属性的结果会被缓存,当数据发生变化时,才会通知computed watcher重新计算。

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  // 对所有的计算属性
  for (const key in computed) {
    const userDef = computed[key]
    // 将计算属性对应的函数作为计算属性的getter方法
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // 为每个计算属性创建一个watcher实例,watcher实例可以收集响应式依赖,并在响应式依赖发生变化时,更新计算属性的值
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      // 计算属性不能与data及prop中的属性重名
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}
/**
 * 将计算属性以响应式依赖的形式混入vue实例,使之与data中定义的属性一样具备响应式的特性
 * @param {*} target 
 * @param {*} key 
 * @param {*} userDef 
 */
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  // 计算属性既可以是一个函数也可以是一个包含getter和setter方法的对象,因此需要分别处理
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
/**
 * 创建计算属性的getter方法
 * @param {*} key 
 */
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 重新计算计算属性的值
      if (watcher.dirty) {
        watcher.evaluate()
      }

      // 将计算属性添加为watcher的依赖
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

异步更新队列

vue 在更新 DOM 时是异步执行的。当侦听到数据变化,数据所对应的dep对象将通知所有的订阅者watcher进行更新。 watcher得到需要更新的通知后,并不是马上就执行。而是开启一个队列,并将同一事件循环中所有需要执行更新任务的watcher 推入队列。如果同一个watcher在同一事件循环中被通知了多次,只会被推入到队列中一次。通过异步缓冲的方式,去除重复更新任务可以有效地避免不必要的计算和 DOM 操作。然后,在下一个的事件循环中,vue 会调用队列中的watcher执行更新(已去重) 工作。

例如,当我们设置 vm.someData = 'new value',该组件不会立即重新渲染。此时如果有个DOM元素的textContent使用了someData,那么在vm.someData = 'new value'代码后面直接获取该节点的textContent,你并不能得到new value的结果,因为DOM更新是异步的。如果希望得到的是更新之后的结果,可以使用nextTick(callback),在callback中获取。

调度器

vue 内部设计了一个异步任务调度器来管理异步任务。vue会调用queueWatcher将同一个事件循环内待更新的watcher实例添加到queue中。然后通过nextTick开启一个异步任务,在下一个事件循环中,调用flushSchedulerQueue执行queque中的更新任务。更新完成,重置调度器的状态,并且调用组件的updatedactivated钩子(keep-alive包裹的组件)。

export const MAX_UPDATE_COUNT = 100
// 存储watcher的队列
const queue: Array<Watcher> = []
// 激活状态下的子组件
const activatedChildren: Array<Component> = []
// 存储watcher的id,用以标识队列中是否已经存在该watcher
let has: { [key: number]: ?true } = {}
// 循环引用对象
let circular: { [key: number]: number } = {}

let waiting = false
// 是否在处理异步队列
let flushing = false
let index = 0

/**
 * 重置调度器的状态
 */
function resetSchedulerState () {
  index = queue.length = activatedChildren.length = 0
  has = {}
  if (process.env.NODE_ENV !== 'production') {
    circular = {}
  }
  waiting = flushing = false
}

export let currentFlushTimestamp = 0

let getNow: () => number = Date.now

if (inBrowser && !isIE) {
  const performance = window.performance
  if (
    performance &&
    typeof performance.now === 'function' &&
    getNow() > document.createEvent('Event').timeStamp
  ) {
    getNow = () => performance.now()
  }
}

/**
 *清空所有的队列并执行watcher的更新逻辑
 */
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // 队列按照watcher的id升序排序,目的是确保:
  // 1. 组件总是从父向子进行更新
  // 2. 用户创建的watcher先于渲染watcher更新
  // 3. 如果组件在父组件的watcher运行时被销毁,该组件的watcher可以跳过处理
  queue.sort((a, b) => a.id - b.id)

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    // 调用vue实例的beforeUpdate钩子
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // vue实例更新
    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
      }
    }
  }

  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // 调用 updated 和 activated 钩子
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
// 调用vue实例的updated钩子
function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'updated')
    }
  }
}

/**
 * 添加到激活组件队列
 * @param {*} vm 
 */
export function queueActivatedComponent (vm: Component) {
  vm._inactive = false
  activatedChildren.push(vm)
}
// 调用vue组件实例的activated钩子
function callActivatedHooks (queue) {
  for (let i = 0; i < queue.length; i++) {
    queue[i]._inactive = true
    activateChildComponent(queue[i], true /* true */)
  }
}

/**
 * 将watcher添加到异步队列
 * @param {*} watcher 
 */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 如果watcher不在队列中,将它添加到队列中
  if (has[id] == null) {
    has[id] = true

    // 如果异步队列未开始执行,添加watcher
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 如果已经在处理中了,如果还没执行到该异步任务,插入到队列
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }

    if (!waiting) {
      waiting = true
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

nextTick

vue 在内部的异步队列是通过nextTick方法实现的。该方法也是vue实例方法$nextTick的底层实现。 $nextTick(cb)会把回调函数cb添加到队列callbacks中,然后调用timerFunc方法开启异步任务。在下一个事件循环中调用flushCallbacks执行callbacks中的任务。

由于Promise.thenMutationObserver会创建微任务,而setImmediatesetTimeout会创建宏任务。相比于宏任务,微任务能更快地得到执行,因此timerFunc方法优先使用原生的 Promise.thenMutationObserver 来来执行异步任务,其次采用 setImmediate。如果以上特性执行环境均不支持,才降级为 setTimeout(fn, 0) 。

// 是否使用了微任务
export let isUsingMicroTask = false

// 异步任务队列
const callbacks = []

// 控制上一次事件循环中的回调函数是否已经执行完毕。true:表示正在处理中;false:表示已经处理完成
let pending = false

/**
 * 执行并清空异步任务队列
 */
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc

/**
 * nextTick方法的实现巧妙地借用了微任务队列,nextTick底层实现中主要依赖MutationObserver和promise来访问微任务队列
 * MutationObserver在ios>=9.3.3中有严重bug,所以更多情况下,nextTick是借助promise来构造微任务
 */
// 1. 优先考虑使用promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 2. 其次选择使用MutationObserver。(PhantomJS、iOS7、Android4.4中不支持promise)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 3. 第三使用 setImmediate ,浏览器环境下不支持。setImmediate虽然同样也是使用宏队列,但是相比setTimeout仍是一个更好的选择。
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 如果promise、mutationObserver、setImmediate均不支持,使用setTimeout。
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
/**
 * 下一个事件循环中执行回调函数
 * @param {*} cb 
 * @param {*} ctx 
 */
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 1. 首先在callbacks数组中添加一项回调函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })

  // 2. 如果当前没有需要处理的任务,调用 timerFunc 异步执行所有的回调函数
  if (!pending) {
    pending = true
    // 通过异步方法,将回调函数的调用放到微任务队列
    timerFunc()
  }
  // 如果未指定回调函数,则返回一个promise对象
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

总结

vue通过Object.definePropert将数据对象中的所有属性转成gettersetter。基于观察者模式,使用watcher对象作为观察者在getter中订阅数据对应的dep对象。在setter中,当数据发生改变时,使用dep对象作为被观察者,通知watcher进行更新。以上就是vue响应式系统的底层实现原理。

推荐阅读

8c33bac144387a10d62698dc8e1b0414.jpeg

好文我在看👇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值