打印: 1 2 promise 3
打印: 1 promise 2 3
vue中的$nextTick不是也用Promise().resolve().then()做的吗?都是微任务,为什么在第一张图中, '2'比'Promise'先被打印出来?
打印: 1 2 promise 3
打印: 1 promise 2 3
vue中的$nextTick不是也用Promise().resolve().then()做的吗?都是微任务,为什么在第一张图中, '2'比'Promise'先被打印出来?
有意思哈,还特意去查了下文档:
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有"then" 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
$nextTick:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新
微任务?两者的使用场景不一样,执行的先后无法排列,鄙人如此理解。
promise是js中的异步控制, nextTick是Vue的回调.当改变dom后触发
也就是this.msg = 'end'
当msg被渲染为end后触发nextTick
其实我比较好奇 在第二张图里面 mounted
方法没有操作与dom相关的数据,为什么$nextTick还能执行.
你能提出这个问题说明你应该阅读过源码,知道nextTick在native Promise的时候是基于Promise实现的。那你应该也知道通过Promise加入微任务的实际上是一个遍历调用回调函数数组的回调函数,这个函数只会同时在微任务队列中出现一次。而我们传入的函数被存放在回调组中。这样这个问题就很简单了。
你所遇到的这个问题只会出现在生命周期函数中,而不会出现在一般的methods中定义的函数中,除非你在生命周期函数中调用它。
因为当你上述例子中的mounted钩子执行到nextTick时,仍然执行的是同步代码,组件元素即将完成挂载到父元素。此时你不能保证nextTick的回调函数没有已经被之前的一些其他代码加入到微任务队列。
你遇到的这个问题,正好是因为nextTick的回调函数已经被加入到微任务队列了,而你传入的回调仅仅被push到回调数组中,因此它会先于promise.then的回调被调用。
我猜测之后你可能删除了一行代码,然后保存了代码编辑器。这时你并没有刷新页面,先于这个mounted将nextTick回调函数加入微任务队列的一些代码可能没有再次执行,也就打印出了我们一般期望的结果。因此你可以尝试每次都刷新页面,这样你得到的结果应该每次都是一样的。
首先我们要知道nextTick方法做了什么?直接上源码:
function nextTick (cb, ctx) {
var _resolve;
// 包装cb回调函数,如果没有回调则返回一个promise实例
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
主要做了两件事:
执行timerFunc函数
timerFunc函数是什么?看源码:
var callbacks = [];
var pending = false;
function flushCallbacks () {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}
var timerFunc;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks);
if (isIOS) { setTimeout(noop); }
};
}
// 省略优雅降级使用setTimeout的部分代码
timerFunc
就是把callbacks
回调队列的执行放到了p.then()
中。
好了知道了nextTick
和timerFunc
是干什么的之后,我们再回到题目。
当执行到this.msg = 'end'
时,触发了msg
属性的setter,setter中执行dep.notify()
派发更新,触发Watcher的update方法,最终会执行nextTick(flushSchedulerQueue)
(这里可以在第一张图的nextTick回调中加个断点就执行函数调用栈了)。
也就是说,执行完this.msg='end'
的时候,会触发一次nextTick
,继而执行timerFunc
方法,然后会把p.then(flushCallbacks)
推到微任务中,然后再接着执行Promise.resolve().then(...)
,再继续走,后面的this.$nextTick(...)
只是把回调函数收集到了callbacks
。
因此第一张图中2是比promise!先执行。
10 回答11.7k 阅读
2 回答3.2k 阅读✓ 已解决
4 回答2.2k 阅读✓ 已解决
3 回答1.2k 阅读✓ 已解决
3 回答832 阅读✓ 已解决
3 回答1k 阅读✓ 已解决
2 回答1.2k 阅读✓ 已解决
nextTick支持两种形式,一种是回调,一种是promise,具体哪种取决于你有没有传入回调函数,看下面vue源码

当你传入回调函数时vue实际上是将回调推入一个队列等待下次更新时机时一起调用,然后因为你第一个例子中修改了data的msg,所以会触发vue 的update逻辑,也就触发了nextTick,你可以看下vue的调用栈

可以看到update后就紧接着执行了nextTick来执行推入的回调函数,所以才会出现第一个例子中的那样,如果你改成this.$nextTick().then(...)这才是promise的形式,这样输出才会符合你所预期的,也就是第二个例子那样