nextTick异步更新源码解析

// 给一个对象定一个响应式属性
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // dep和key 1:1
  // 如果key的值变化,通知更新
  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() // dep和watcher互相添加映射关系
        // 子Ob实例也要添加映射关系
        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.notify();开始看异步更新

  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)
    }
    // 遍历关联所有watcher
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
  
    update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      // watcher入队
      queueWatcher(this)
    }
  }
  
  export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 去重:单个watcher只入队一次
  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
      }
      // 异步方式将flushSchedulerQueue放入队列
      nextTick(flushSchedulerQueue)
    }
  }
}
问题:nextTick的作用是什么

异步方式将flushSchedulerQueue放入微任务队列

问题:nextTick怎么把flushSchedulerQueue放进去的

// 此方法就是我们平时使用的nextTick方法
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 异步执行callbacks中的任务
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

问题:flushSchedulerQueue是怎么执行的

let timerFunc
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]'
)) {
  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)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

这里做了判断Promise>MutationObserver>setImmediate>setTimeout

可以看出如果真的不支持几种微任务,那就只能用setTimeout去做更新

这里执行的是p.then(flushCallbacks)并不是flushSchedulerQueue,

// 将callback中回调全部执行一遍
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

这里callbacks里边放的函数就是我们的flushSchedulerQueue;

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  queue.sort((a, b) => a.id - b.id)

  // 按id顺序执行watcher更新
  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')
  }
}

看一下run

  // watcher真正执行更新的函数
  run () {
    if (this.active) {
      // 调用watcher的get方法
      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的get方法:其实就是updateComponent

  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
  }
  

get方法执行了watcher的getter

 constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) 

if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    }
    
    expOrFn就是new Watcher的时候传进来的第二个参数
    
    
    new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  
  
  updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }

queueWatcher先对watcher去重:

           mounted() {
               

               this.foo = Math.random()
               console.log('1:' + this.foo); 
               this.foo = Math.random()
               console.log('2:' + this.foo);
               this.foo = Math.random()
               console.log('3:' + this.foo);
               // 异步行为,此时内容没变
               console.log('p1.innerHTML:' + p1.innerHTML) // ?


               // [flushCallbacks, cb1]
               //    callbacks: [flushScheduleQueue, cb2]
               Promise.resolve().then(() => {
                   console.log('promise p1.innerHTML:' + p1.innerHTML)
               })
               
               // [cb, flushSchexxxx]
               this.$nextTick(() => {
                   // 这里才是最新的值
                   console.log('p1.innerHTML:' + p1.innerHTML) // ?
               })
           }

所以只有第一次入队列

Notify()的时候回调用watcher的update,把watcher放入queueWatcher,

queueWatcher会根据ID对watcher去重,把watcher放入queue中并执行nextTick(flushSchedulerQueue)(// 异步方式将flushSchedulerQueue放入微任务队列)

这里的nextTick方法就是我们平时使用的nextTick方法,会执行timerFunc()(异步执行callbacks中的任务)

timerFunc()会调动flushCallbacks(),.对flushSchedulerQueue进行清空

flushSchedulerQueue会对queue里的watcher排序,然后挨个调用watcher.run();

run去调用watcher的get方法,const value = this.get(),get去调用传给watcher的第二个参数updateCompnent

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容