单线程的执行阶段

286 阅读3分钟

事件循环

JS所有任务分为两种

  • 同步任务
  • 异步任务

同步任务

调用立即得到结果的任务,同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

异步任务

调用无法立即得到结果,需要通过一系列的额外操作才能得到结果的任务

异步任务不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

异步执行的运行机制:
  1. 所有同步任务都在主线程上执行,形成一个【执行栈】
  2. 主线程之外,还存在一个【任务队列】(task queue)。只要异步任务有了运行结果,就在"任务队列"中回调一个事件(不是全部异步任务都要回调函数)
  3. 一旦【执行栈】中的所有同步任务执行完毕,系统就会读【任务队列】。对应第一个的异步任务结束等待状态,进入【执行栈】,开始执行
  4. 主线程不断重复上面的第三步

image

JavaScript引擎是基于事件驱动的

宏任务(task)

  • JS引擎会在一个 task 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)

  • eg:鼠标点击会触发一个事件回调、setTimeout、setInterval

image

微任务(microtasks)

  • 需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或者需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销
  • 在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行
  • eg:mutation observe的回调、promise

pormise

  • pormise 新建后就会立即执行
  • pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务
const observer = new MutationObserver((mutations, observer) => {
    console.log(mutations);//[{type: 'childList',.....}]
})
observer.observe(root, {
    childList: true,
    subtree: true
})
const p = Promise.resolve();
root.appendChild(document.createElement('div'));
// DOM操作执行完成,触发观察回调
p.then(() => {
    console.log('reslove')
})
// 运行结果 👇
reslove
[{type:  .......}]

异步任务定时器

console.log('start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);
console.log('end');
👇
console.log
	start
    end
    setTimeout

注意:即便主线程为空,0ms实际上也是达不到的,根据HTML标准,最低是4ms

setTimeout 与setInterval

setTimeout(function(){
	console.log('a')
	setTimeout(arguments.callee,1000)
},1000)

setInterval(function(){
	console.log('a')
},1000)

//setTimeout 的运行过程👇   
0s 开始执行  1s		     2s		    3s
|___/_______|___/_______|__________|__________|
0.3s👆 运行完 相隔1s 1.3s开始第二次

//setInterval 的运行过程👇   每隔1s就添加事件到队列 
0s 开始执行  1s          2s		   3s
|___/_______|___/_______|__________|__________|
0.3s👆 运行完  1s开始第二次   
//  可以看出,setInterval从执行完成到执行下一次定时器的时间间隔小于1s


// 如果没有执行完的,会定时器跳过,定时器会保证执行时前面已经没有定时器事件
//	setInterval 有可能多个定时器扎堆

延时执行时间有最大值

Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,这导致定时器会被立即执行

let startTime = Date.now()
function foo(){
  const endTime = Date.now()
  console.log('cost time',endTime - startTime)
  console.log("test")
}
var timerID = setTimeout(foo,2147483648);//会被立即调用执行



【多多登场】

略略略

【本来参考】
事件循环
宏任务与微任务