JavaScript作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM
JavaScript语言特点:
单线程, 同一个时间只能做一件事。所有任务需要排队,形成"任务队列"。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。
所有任务可以分成:
-
同步任务(synchronous)
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
-
异步任务(asynchronous)
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
-
宏任务(macrotask)
宿主环境(浏览器/node)发起的任务,每次执行栈执行的代码
包含生成dom对象、解析HTML、执行主线程js代码、更改当前URL还有其他的一些事件如页面加载、输入、网络事件和定时器事件
常见API:(setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering)
-
微任务(microtask)
由 javaScript 语言标准提供的,由js引擎发起的任务,当前task执行结束后立即执行的任务
完成一些更新应用程序状态的较小任务,如处理promise的回调和DOM的修改,这些任务在浏览器重渲染前执行。Microtask应该以异步的方式尽快执行,其开销比执行一个新的macrotask要小。Microtasks使得我们可以在UI重渲染之前执行某些任务,从而避免了不必要的UI渲染
常见API:(Process.nextTick, Promise.then catch finally, Object.observe, MutationObserve)
-
js的运行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
- 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
(4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这个过程会不断重复,这就是JavaScript的运行机制。
Event Loop(事件循环),主线程从"任务队列"中读取事件,这个过程是循环不断的。
主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数
回调函数(callback),会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
由于存在"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
setTimeout[1]
setTimeout(function(){
console.log('定时器开始啦')
},3000);
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
遇到 new Promise直接执行,打印"马上执行for循环啦"
遇到then方法,是微任务,将其放到微任务的【队列里】
打印 "代码执行结束"
本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"
到此,本轮的event loop 全部完成。下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"定时器开始啦"
JavaScript 运行机制详解:再谈Event Loop
-
3秒后,setTimeout里的函数被会推入event queue,而event queue(事件队列)里的任务,,只有在主线程空闲时才会执行。主线程执行内容很多,执行时间超过3秒,比如执行了10秒,那么这个函数只能10秒后执行了 ↩
网友评论