Vue中$nextTick的理解
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',data: {
msg: 'Vue'
},template:`
<div>
<div ref="msgElement">{{msg}}</div>
<button @click="updateMsg">updateMsg</button>
</div>
`,methods:{
updateMsg: function(){
this.msg = "Update";
console.log("DOM未更新:",this.$refs.msgElement.innerHTML)
this.$nextTick(() => {
console.log("DOM已更新:",this.$refs.msgElement.innerHTML)
})
}
},})
</script>
</html>
- 执行栈就是在主线程执行同步任务的数据结构,函数调用形成了一个由若干帧组成的栈。
- 后台线程就是浏览器实现对于
setTimeout
、setInterval
、XMLHttpRequest
等等的执行线程。
- 宏队列,一些异步任务的回调会依次进入宏队列,等待后续被调用,包括
setTimeout
、setInterval
、setImmediate(Node)
、requestAnimationFrame
、UI rendering
、I/O
等操作
- 微队列,另一些异步任务的回调会依次进入微队列,等待后续调用,包括
Promise
、process.nextTick(Node)
、Object.observe
、MutationObserver
等操作
- 首先将执行栈中代码同步执行,将这些代码中异步任务加入后台线程中
- 执行栈中的同步代码执行完毕后,执行栈清空,并开始扫描微队列
- 取出微队列队首任务,放入执行栈中执行,此时微队列是进行了出队操作
- 当执行栈执行完成后,继续出队微队列任务并执行,直到微队列任务全部执行完毕
- 最后一个微队列任务出队并进入执行栈后微队列中任务为空,当执行栈任务完成后,开始扫面微队列为空,继续扫描宏队列任务,宏队列出队,放入执行栈中执行,执行完毕后继续扫描微队列为空则扫描宏队列,出队执行
- 不断往复...
实例
// Step 1
console.log(1);
// Step 2
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
},0);
// Step 3
new Promise((resolve,reject) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
})
// Step 4
setTimeout(() => {
console.log(6);
},0);
// Step 5
console.log(7);
// Step N
// ...
// Result
/*
1
4
7
5
2
3
6
*/
Step 1
// 执行栈 console
// 微队列 []
// 宏队列 []
console.log(1); // 1
Step 2
// 执行栈 setTimeout
// 微队列 []
// 宏队列 [setTimeout1]
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
},0);
Step 3
// 执行栈 Promise
// 微队列 [then1]
// 宏队列 [setTimeout1]
new Promise((resolve,reject) => {
console.log(4); // 4 // Promise是个函数对象,此处是同步执行的 // 执行栈 Promise console
resolve();
}).then(() => {
console.log(5);
})
Step 4
// 执行栈 setTimeout
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
setTimeout(() => {
console.log(6);
},0);
Step 5
// 执行栈 console
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
console.log(7); // 7
Step 6
// 执行栈 then1
// 微队列 []
// 宏队列 [setTimeout1 setTimeout2]
console.log(5); // 5
Step 7
// 执行栈 setTimeout1
// 微队列 [then2]
// 宏队列 [setTimeout2]
console.log(2); // 2
Promise.resolve().then(() => {
console.log(3);
});
Step 8
// 执行栈 then2
// 微队列 []
// 宏队列 [setTimeout2]
console.log(3); // 3
Step 9
// 执行栈 setTimeout2
// 微队列 []
// 宏队列 []
console.log(6); // 6
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',template:`
<div>
<div ref="msgElement">{{msg}}</div>
<button @click="updateMsg">updateMsg</button>
<button @click="updateMsgTest">updateMsgTest</button>
</div>
`,methods:{
updateMsg: function(){
this.msg = "Update";
setTimeout(() => console.log(1))
Promise.resolve().then(() => console.log(2))
this.$nextTick(() => {
console.log(3)
})
},updateMsgTest: function(){
setTimeout(() => console.log(1))
Promise.resolve().then(() => console.log(2))
this.$nextTick(() => {
console.log(3)
})
}
},})
</script>
</html>
/**
* Defer a task to execute it asynchronously.
*/
var nextTick = (function () {
// 闭包 内部变量
var callbacks = []; // 执行队列
var pending = false; // 标识,用以判断在某个事件循环中是否为第一次加入,第一次加入的时候才触发异步执行的队列挂载
var timerFunc; // 以何种方法执行挂载异步执行队列,这里假设Promise是完全支持的
function nextTickHandler () { // 异步挂载的执行任务,触发时就已经正式准备开始执行异步任务了
pending = false; // 标识置false
var copies = callbacks.slice(0); // 创建副本
callbacks.length = 0; // 执行队列置空
for (var i = 0; i < copies.length; i++) {
copies[i](); // 执行
}
}
// the nextTick behavior leverages the microtask queue,which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support,however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so,if native
// Promise is available,we will use it:
/* istanbul ignore if */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
var logError = function (err) { console.error(err); };
timerFunc = function () {
p.then(nextTickHandler).catch(logError); // 挂载异步任务队列
// in problematic UIWebViews,Promise.then doesn't completely break,but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed,until the browser
// needs to do some other work,e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) { setTimeout(noop); }
};
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,// e.g. PhantomJS IE11,iOS7,Android 4.4
var counter = 1;
var observer = new MutationObserver(nextTickHandler);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode,{
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else {
// fallback to setTimeout
/* istanbul ignore next */
timerFunc = function () {
setTimeout(nextTickHandler,0);
};
}
return function queueNextTick (cb,ctx) { // nextTick方法真正导出的方法
var _resolve;
callbacks.push(function () { // 添加到执行队列中 并加入异常处理
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e,ctx,'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
//判断在当前事件循环中是否为第一次加入,若是第一次加入则置标识为true并执行timerFunc函数用以挂载执行队列到Promise
// 这个标识在执行队列中的任务将要执行时便置为false并创建执行队列的副本去运行执行队列中的任务,参见nextTickHandler函数的实现
// 在当前事件循环中置标识true并挂载,然后再次调用nextTick方法时只是将任务加入到执行队列中,直到挂载的异步任务触发,便置标识为false然后执行任务,再次调用nextTick方法时就是同样的执行方式然后不断如此往复
if (!pending) {
pending = true;
timerFunc();
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve,reject) {
_resolve = resolve;
})
}
}
})();
var nextTick = (function(){
var pending = false;
const callback = [];
var p = Promise.resolve();
var handler = function(){
pending = true;
callback.forEach(fn => fn());
}
var timerFunc = function(){
p.then(handler);
}
return function queueNextTick(fn){
callback.push(() => fn());
if(!pending){
pending = true;
timerFunc();
}
}
})();
(function(){
nextTick(() => console.log("触发DOM渲染队列的方法")); // 注释 / 取消注释 来查看效果
setTimeout(() => console.log(1))
Promise.resolve().then(() => console.log(2))
nextTick(() => {
console.log(3)
})
})();
https://github.com/WindrunnerMax/EveryDay
https://www.jianshu.com/p/e7ce7613f630
https://cn.vuejs.org/v2/api/#vm-nextTick
https://segmentfault.com/q/1010000021240464
https://juejin.im/post/5d391ad8f265da1b8d166175
https://juejin.im/post/5ab94ee251882577b45f05c7
https://juejin.im/post/5a45fdeb6fb9a044ff31c9a8