1、JS运行机制
JS执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
2) 主线程之外,还存在一个“任务队列”(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
3)一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4)主线程不断重复上面的第三步。
2、nextTick的作用
nextTick就是设置一个回调,用于异步执行。
就是把你设置的回调放在setTimeout中执行,这样就算异步了,等待当时同步代码执行完毕再执行。
this.$nextTick( _ => {
this.$refs.saveTagInput.$refs.input.focus();
})
当页面上的元素被重新渲染之后,才会执行指定回调函数中的代码。
3、如何将多个设置的回调在一起执行异步调用
每设置一个nextTick就新建一个setTimeout,毕竟一个setTimeout是异步,两个setTimeout也是异步,两个都要等再同步代码执行完毕之后才执行,那我直接只设置一个setTimeout不就好了。
存在回调函数里,每次调用nextTick,便往数组里面push设置的回调
只注册一个setTimeout,时间为0,用于遍历回调数组,然后逐个执行子项
同步代码执行完毕,setTimeout自然会执行
4、Vue实现nextTick的原理
Vue不止使用setTimeout实现nextTick;
会判断promise是否存在,选择任务类型。如果promise存在,就使用微任务。
5、Vue在一个tick中多次更新数据页面只会更新一次
即使在Vue中多么频繁地修改数据,最后Vue页面只会更新一次。
例如:
数据name被页面引用,name会收集到页面的watcher;
name 被修改时,会通知所有收集到的watcher进行更新(watcher.update);
如果name一时间被修改三次时,按道理应该会通知三次watcher更新,那么页面会更新三次,但是最后只会更新一次。
这是因为:
当数据变化后,把watcher.update函数存放进nextTick的回调数组中,并且会做过滤。
通过watcher.id来判断回调数组,便会执行了更新。
所以,当三次修改数据的时候,会push回调数组,三个watcher.update,但是只有第一次是push成功的,其他的会被过滤掉,因为已经存在了。
所以,不管你修改多少次数据,nextTick的回调数组中只存在唯一一个watcher.update,从而页面只会更新一次。
nextTick的实现原理
nextTick的作用:在下次DOM更新循环结束之后执行的延迟回调
在浏览器环境中,常见的
宏任务(macro task)有setTimeout、MessageChannel、postMessage、setImmediate;
微任务(micro task)有MutationObserver和Promise.then
宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。
如果浏览器不支持微任务:
首先,nextTick并不是浏览器本身提供的一个异步API,而是Vue中,用过由浏览器本身提供的原生异步API封装而成的一个异步封装方法。
它对于浏览器异步API的选用规则如下,Promise存在取Promise.then, 不存在Promise则取MutationObserver,
MutationObserver不存在setImmediate,setImmediate不存在最后取setTimeout来实现。
nextTick即有可能是微任务,也有可能是宏任务,从优先去Promise和MutationObserver可以看出nextTick优先微任务,
其次是setImmediate和setTimeout宏任务。
同步代码执行完毕之后,优先执行微任务,其次才会执行宏任务
setTimeout(()=>{
console.log("settimeout 1")
}, 0);
console.log("同步 1");
Promise.resolve().then(()=> {
console.log("promise 1");
});
this.$nextTick(() => {
console.log("nextTick")
})
Promise.resolve().then(()=>{
console.log("promise 1")
})
console.log("同步 1");
setTimeout(()=>{
console.log("settimeout 1");
}, 0)
实现一个nextTick
myNextTick(callback: Function){
return Promise.resolve().then(()=>{
callback();
});
}