宏任务和微任务
1.宏任务macrotask/Tasks:
(1)种类:
script主代码块、setTimeout、setInterval、nodejs的setImmediate、MessageChannel(react的fiber用到)、postMessage、网络I/O、文件I/O、用户交互的回调等事件、UI渲染事件(DOM解析、布局计算、绘制)等等
(2)宏任务的问题,也即为什么要有微任务?
时间粒度比较大,执行的时间间隔是不能精确控制的,消息队列中就有可能被插入很多系统级的任务,对一些高实时性的需求就不太符合了;所有设计了微任务,通俗的讲微任务可以插队,本轮宏任务最后,插在下一轮宏任务之前,微任务队列的任务一次性全部执行完;
2.微任务包括microtask/jobs:
(1)种类:
浏览器端有3个1)newPromise().then回调2)MutationObserver,监控dom节点变化;MutationObserver使用“异步”+“微任务”的方式,替代旧版mutationevent这个同步事件,异步解决同步操作的性能问题;微任务解决了实时性的问题;3)Object.observe,已经废弃了,用Proxy对象替代;
nodejs有2个,process.nextTick、newPromise().then回调
(2)优先级如下:
nodejs中process.nextTicknewPromise().then(回调)MutationObserver
(3)宏任务和微任务的关系
每个宏任务可以创建自己的一个微任务队列;
3.一个宏任务微任务的例子,理解用户点击事件和JS模拟点击事件的区别
当我们使用手动点击按钮时,浏览器的输出是listener1-promiseresolved1-listener2-promiseresolved2
当我们使用JS触发点击行为时,浏览器的输出是listener1-listener2-promiseresolved1-promiseresolved2原因:
(1)人工点击从上图中我们可以看到,一次点击事件之后,浏览器会调用FunctionCall进入JS引擎,执行listener1,输出listener1。
弹栈时发现JS调用栈为空,这时候就会执行Microtasks队列中的所有Microtask,输出promiseresolved1。
接着浏览器调用FunctionCall进入JS引擎,执行listener2,输出listener2。
弹栈时发现JS调用栈为空,这时候就会执行Microtasks队列中的所有Microtask,输出promiseresolved2。
(2)JS触发点击事件从上图中我们可以看到,浏览器运行JS代码时,调用了button.click这个函数
进入事件处理,执行listener1,输出listener1。
弹栈时发现JS调用栈非空(button.click函数还在运行)
执行listener2,输出listener2。
弹栈时发现JS调用栈为空,这时候就会执行Microtasks队列中的所有Microtask,输出promiseresolved1、promiseresolved2。
4.调试V8源码的微任务
使用工具,chrome://tracing/是一个structuralprofiler或叫CPUprofiler,与ChromeDevtoolperformance的sampleprofiler不同,他是由代码中主动的去埋点打印出来的,所以每一次函数调用都会被记录下来,不会像sampleprofiler一样漏掉采样时刻之间的状态。具体使用步骤可自行谷歌;
5.任务队列和回调函数的区别
(1)Javascript单线程任务被分为同步任务和异步任务同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。(2)异步任务都能注册回调函数比如nodejs的setImmediate、MessageChannel、setTimeout、setInterval、process.nextTick、newPromise().the(回调)、MutationObserver等等(3)任务队列分为:宏任务队列,微任务队列;
6.settimeout、setinterval的几个注意点:
(1)当前任务执行时间过久,会影响定时器任务的执行下面这个例子会有两种结果:settimeout(a,);…同步代码执行很久;再一个settimeout(b,)同步代码执行时间=,顺序执行,先b再a;可以把同步看做很快执行完;同步代码执行时间执行完后,先a再b;(2)chrome中最低延时是1ms;即设置0最低为1ms;因为设置0的话,可能会导致JS单进程eventloop过度循环,阻塞;(3)setTimeout存在嵌套调用chrome中超过5层,系统会设置最短时间间隔为4毫秒,setinterval是10毫秒;最开始是1ms,太低会导致计算机没法进入睡眠模式,耗电非常快,所以改成4ms;(4)未激活的页面,setTimeout执行最小间隔是毫秒,减少加载损耗,节约电量;(5)延时执行时间有最大值32个bit来存储延时值的,最大只能存放的数字是毫秒,超过这个值会溢出,相当于延时值被设置为0了,这导致定时器会被立即执行。(6)第一个参数除了可以传函数,还可以直接传递code字符串,下面一个例子setTimeout(1)和setTimeout(2)的区别首先语法如下:
1)返回的timeoutID不一样2)code值不一样,一个传入1一个传入2;注意code是delay毫秒之后编译和执行的字符串,不推荐,跟eval一样有安全风险;3)执行时间不一样,虽然都没有设置delay,默认0ms,但是都要放到任务队列排队等待被执行;有先后顺序;
浏览器中的事件循环
1.机制如下:
浏览器的事件循环,是在渲染进程中的;执行一个宏任务,栈中没有就从事件队列中获取;执行过程中如果遇到微任务,就添加到微任务的队列中;当前这个宏任务执行完毕后,立即执行当前微任务队列的所有微任务;当前宏任务执行完毕,GUI线程接管渲染;渲染完毕后,JS线程继续接管,开始下一个宏任务;
简化就两步:执行一个宏任务,执行完它对于的所有微任务;
2.浏览器渲染进程有三个异步队列
宏任务macrotask/Tasks
微任务Microtasks/Jobs
Animationcallbacks:requestAnimationFrame也属于异步执行的方法,但该方法既不属于宏任务,也不属于微任务。
(1)宏任务队列,每次循环只执行一个。设置2个相同时间的timeout,两个并不会一起执行,依然是分批的。(2)微任务队列,每次循环会把队列全部执行完才继续。因此如果任务本身又新增Microtasks,也会一直执行下去。所以下面的例子才会产生阻塞。
(3)Animationcallbacks队列,每次循环执行队列里的全部任务,连续调用两句requestAnimationFrame,会在同一次事件循环执行;但是任务本身又新增Animationcallback是放到下一个循环执行,不会阻塞;(4)例题:
1)test1会导致执行栈溢出,因为所有的代码都在同步执行栈中,最大栈深度,溢出;2)test2,可以正常运行,因为把任务移到了宏任务队列中,没有阻塞主渲染线程;3)test3可以运行,但是会造成页面卡顿,因为微任务队列自身创建微任务,会不停创建执行、创建执行,虽然不会爆栈,但是阻塞了主线程,页面卡顿严重;
3.promise相关的经典面试题
(1)promise.then要等到resolve之后加入当前宏任务后面的微任务中;
第一轮:宏任务1,4;微任务:空
第二轮:宏任务2;微任务:3
第三轮:宏任务5,微任务:resolve了,把对应微任务加入~~7、8、9
第四轮:6结果依次输出:
(2)promise.then中returnPromise.resolve,会产生类似两轮的微任务
4的地方产生两轮微任务,所以3、7这一轮完了要再等两轮,再8、9之后输出输出结果:1、5、2、6、3、7、8、9、4、10
具体V8的底层原理,可以看知乎这一个问题