3

$nextTick原理解析

 3 years ago
source link: http://www.fly63.com/article/detial/10532
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

nextTick 是什么

nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise(如果支持的话)

// 修改数据
vm.msg = "Hello";

// DOM 还没有更新
vue.nextTick(function() {
  // DOM 更新了
});

这块理解 EventLoop 的应该一看就懂,其实就是在下一次事件循环开始时开始更新 DOM,避免中间频繁的操作引起页面的重绘和回流。

这块引用官方文档:

可能你还没有注意到,vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

列如当设置vm.text = 'new value'时,该组件不会立即重新渲染,当刷新队列时,组件会在下一个事件循环‘tick’中更新,

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

一般在设置了this.xx='xx'数据后,立即得到最新的 DOM 数据时,才会用到$nextTick,因为 DOM 的更新是异步进行的,所以获取需要用到这个方法。

更新流程(源码解析)

  1. 当数据被修改时,watcher 会侦听到变化,然后会将变化进行入队:
/*
 * Subscriber interface.
 * Will be called when a dependency changes.
 */
Watcher.prototype.update = function update() {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};
  1. 并使用 nextTick 方法添加一个 flushScheduleQueue 回调
/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
function queueWatcher(watcher) {
  var 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.
      var 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 (!config.async) {
        flushSchedulerQueue();
        return;
      }
      nextTick(flushSchedulerQueue);
    }
  }
}
  1. flushScheduleQueue 加入到 callback 数组中,并且异步执行
function nextTick(cb, ctx) {
  var _resolve;
  callbacks.push(function() {
    if (cb) {
      try {
        cb.call(ctx); // !! cb 就是加入的回调
      } catch (e) {
        handleError(e, ctx, "nextTick");
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    // 异步执行 操作 见timerFunc
    pending = true;
    timerFunc();
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== "undefined") {
    return new Promise(function(resolve) {
      _resolve = resolve;
    });
  }
}
  1. timerFunc 操作就是异步执行了依次判断使用:Promise.then=>MutationObserver=>setImmediate=>setTimeout
var timerFunc;

if (typeof Promise !== "undefined" && isNative(Promise)) {
  var p = Promise.resolve();
  timerFunc = function() {
    p.then(flushCallbacks);
    // 1. Promise.then
    if (isIOS) {
      setTimeout(noop);
    }
  };
  isUsingMicroTask = true;
} else if (
  !isIE &&
  typeof MutationObserver !== "undefined" &&
  (isNative(MutationObserver) ||
    MutationObserver.toString() === "[object MutationObserverconstructor]")
) {
  // 2.  MutationObserver
  var counter = 1;
  var observer = new MutationObserver(flushCallbacks);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = function() {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) {
  // 3. setImmediate
  timerFunc = function() {
    setImmediate(flushCallbacks);
  };
} else {
  //4. setTimeout
  timerFunc = function() {
    setTimeout(flushCallbacks, 0);
  };
}
  1. flushCallbacks 遍历所有的 callbacks 并执行
function flushCallbacks() {
  pending = false;
  var copies = callbacks.slice(0);
  callbacks.length = 0;
  for (var i = 0; i < copies.length; i++) {
    copies[i]();
  }
}
  1. 其中就有前面加入的 flushScheduleQueue,利用 queue 中的 watcher 的 run 方法,更新组件
for (index = 0; index < queue.length; index++) {
  watcher = queue[index];
  watcher.run();
}

以上就是 vue 的 nextTick 方法的实现原理了,总结一下就是:

  1. Vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
  2. microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 因为兼容性问题,vue 不得不做了 microtask 向 macrotask 的降级方案

原文来自:https://segmentfault.com/a/1190000040357545

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: http://www.fly63.com/article/detial/10532


Recommend

  • 61

    先来温习一下event loop中的几个phase可参见我的上篇文章libuv概览 timers: 这个phase是来检查定时器是否到期的,并执行的 poll(I/O): 这个是用来监听fd的事件的,比如socket的可读,可写,文件的可读可等 check: 当事件循环被I/O阻塞结束之后立刻调用check handle...

  • 111

    开端 在Vue中有一个nextTick方法,偶然一天,我发现不管代码的顺序如何,nextTick总是要比setTimeout先要执行。同样是排队,凭什么你nextTick就要比我快? 开局一道题,内容全靠编。(在node下运行,答案在文末给出。) new P

  • 51
    • 掘金 juejin.im 7 years ago
    • Cache

    Event Loop、计时器、nextTick

    原文:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ 以下是译文: 什么是事件循环(Event Loop,注意空格) JavaScript 是单线程的,有了 event loo

  • 63
    • 掘金 juejin.im 7 years ago
    • Cache

    Vue nextTick 机制

    背景 我们先来看一段Vue的执行代码: export default { data () { return { msg: 0 } }, mounted () { this.msg = 1 this.msg

  • 85

    熟悉 Vue 的同学们都知道,Vue 有个 nextTick 方法,用来异步更新数据。 来看看这个栗子: &lt;body&gt; &lt;div id="main"&gt; &lt;ul class="list"&gt;

  • 48
    • 掘金 juejin.im 6 years ago
    • Cache

    Vue.js异步更新及nextTick

    写在前面 前段时间在写项目时对nextTick的使用有一些疑惑。在查阅各种资料之后,在这里总结一下Vue.js异步更新的策略以及nextTick的用途和原理。如有总结错误的地方,欢迎指出! 本文将从以下3点进行总结: 为什么Vue.js要异步更新视图?

  • 11

    上篇文章vue生命周期中我们说过一个句话,那就是mounted中并不会保证所有子组件都被挂载完成后再触发,因此当你希望视图完全渲染完成后再做某些事情时,请在mounted中使用$nextTick。那么$nextTick到底是干嘛用的,为什么能解决我们以上的问题。...

  • 4
    • www.fly63.com 2 years ago
    • Cache

    Vue之nextTick 原理

    知其然且知其所以然,vue 作为目前最为主流的前端 MVVM 框架之...

  • 2

    #yyds干货盘点#Vue源码之nextTick 原理 原创 尼羲 2022-07-07 10:27:15

  • 9

    vue 官网中是这样描述 nextTick 的 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,可以获取更新后的 DOM。 在...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK