为什么JavaScript是单线程
JavaScript的单线程,与它的用途有关,作为浏览器脚本语言,JavaScript的主要用途是为了与用户互动以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如:假定JavaScript同时又两个线程,一个线程在某个DOM节点上添加内容,一个线程要删除这个节点,这时,浏览器就不知道该以哪个线程为准。
同步任务和异步任务
同步任务:在主线程上排队执行额任务,只有前一个任务执行完毕之后才能执行下一个任务。
异步任务:不进入主线程,而是进入任务队列的任务。
注:除了放置异步任务的事件,任务队列还可以放置定时器任务。
EventLoop
任务队列是一个事件的队列,主线程读取任务队列,就是读取里面的事件。
主线程从“任务队列”中读取事件,这种过程是循环不断的,所以整个过程的运行机制又称为EventLoop
不同的任务源会被分配到不同的Task队列中去,任务源又可以分为微任务(miscrotask)和宏任务(mascrotask)
EventLoop循环过程如下:
- 执行完同步代码,当所有同步代码均被执行完毕之后,执行栈清空,查询是否有异步代码
- 在macrotask队列中选择最早的那一个,如果没有可选的任务,就跳到下面的microtask队列中
- 将上面选中的macrotask设置为正在运行的task
- 运行被选中的task
- 运行完毕之后,将eventloop中的current running task设置为null
- 从task队列中移除前面运行的macrotask
- 执行所有在microtask队列里的任务。
- 更新渲染
- 返回到开始
另一理解
- 一段代码执行时先执行宏任务中的同步代码
- 如果遇到像setTimeOut这种宏任务就会把代码放置在宏任务队列中去
- 如果遇到promise.then这类任务会放置在微任务队列中。
- 在本轮宏任务执行完之后会依此执行本轮微任务队列中的所有代码,然后开始下一轮的宏任务代码。
注:await后面无论接的是同步代码还是异步代码都必须等他们执行完毕之后才能执行await结果后面的代码。
注释:在遇见定时器后,会将定时器内的函数进行注册,也就是放在Event Table。然后在定时器设定的事件后将Event Table内注册的函数放入Event queue。如果主线程中的call stack为空就将Event queue按顺序放入call stack中进行执行,如果call stack并不为空,Event queue内的事件并不会进入call stack,也就不会执行。
在call stack中的内容执行完毕后清空,会在Event queue检查一下哪些任务时宏任务,哪些是微任务,然后执行所有的微任务,然后执行下一个宏任务,之后再次执行所有的微任务。也就是说在主线程任务执行完毕后会把任务队列里的微任务全部执行,然后再执行下一个宏任务,这个宏任务执行完再次检查队列内部的微任务,有就全部执行,没有就再执行一个宏任务。