nextTick源码浅析

115 阅读1分钟

1 事件循环 每次event loop的最后,会有一个UI render步骤,也就是更新DOM。标准为什么这样设计呢?考虑下面的代码:

for(let i=0; i<100; i++)
{    
dom.style.left = i + 'px';
}

浏览器会进行100次DOM更新吗?显然不是的,这样太耗性能了。事实上,这100次for循环同属一个task,浏览器只在该task执行完后进行一次DOM更新
那我们的思路就来了:只要让nextTick里的代码放在UI render步骤后面执行,岂不就能访问到更新后的DOM了?

vue的数据响应过程包含:数据更改->通知Watcher->更新DOM。而数据的更改不由我们控制,可能在任何时候发生。如果恰巧发生在repaint之前,就会发生多次渲染。这意味着性能浪费,是vue不愿意看到的。

vue进行DOM更新内部也是调用nextTick来做异步队列控制。而当我们自己调用nextTick的时候,它就在更新DOM的那个microtask后追加了我们自己的回调函数,从而确保我们的代码在DOM更新后执行.下面是VUE源码watcher对象更新操作源码,调用了nextTick,所以我们调用nextTick之前给data赋值的对应DOM操作会创建对应的microTask,

Vue 数据拦截的Watcher对象,update方法

    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      **queueWatcher**(this)
    }
  }

而该方法也是使用nextTick来执行

  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)
    }
  }
}