聊聊JavaScript的单线程、非阻塞、任务队列、事件轮询、宏任务、微任务

253 阅读3分钟

JavaScript是服务于浏览器的脚本语言,JavaScript只能是单线程的,即每次只能干一件事!那为什么JavaScript不能是多线程的呢?同时处理更多的事情不是更高效吗?我们假设这样一个场景,一个线程要对dom进行修改,令一个线程要对这个dom进行删除操作,遇到这种情况我们该怎么确定优先级,先后顺序呢?所以从设计之初,JavaScript就被确定为单线程的!

JavaScript也是非阻塞的,何为非阻塞?假如我们在浏览器上进行点击操作,并不会停止页面中视频的播放,gif图片的渲染,页面动画的暂停等等。这就是JavaScript的非阻塞。上面讲到过,JavaScript的是单线程的,每次只能干一件事,那么页面上的各种渲染应该停下等点击操作执行完毕再进行下去,但结果与之相悖,这是为什么呢?

JavaScript之所以是非阻塞的,是因为除了JavaScript的执行线程,也就是主线程外,还提供了渲染线程,浏览器事件线程,http异步线程,任务队列线程等。主线程负责执行同步任务,同步代码,渲染线程负责浏览器的渲染,浏览器事件线程负责对页面点击等其他事件的监听处理,http异步线程负责处理网络请求等

大体的执行流程是JavaScript主线程负责执行同步代码,同步代码中发现dom点击事件监听,ajax请求,promise等异步任务浏览器会将这些异步任务推送到对应的线程中,当用户点击dom元素,ajax请求成功,promise执行完毕,他们的回调都会推送到任务队列中。但主线程的同步代码执行完毕,任务队列中的回调则会推送到主线程中去执行。

谁来检测异步任务是否结束?谁来将已结束的异步任务的回调推送到任务队列中?又是谁在检查主线程中的同步代码是否执行完毕,然后将任务队列中的成功回调推到主线程去执行?谁谁谁,是谁?这个谁就是我们常听到的一个词:事件轮询event loop!

事件轮询可以理解成一个intervel定时器,每个一定时间他会检测异步任务是否执行完毕,执行完毕则将成功回调推送到任务队列中;检测主线程中的代码是否执行完毕,执行完毕将任务队列中的成功回调推到主线程中来执行!

任务队列中存放的定时器回调,网络请求回调,promise.then回调等,每一个回调我们给它起了个名字,叫做任务,然后有给任务分了个类:宏任务跟微任务。

宏任务有:

  • io操作
  • setTimeout
  • setInterval
  • requestAnimationFrame 微任务有:
  • Promise.then
  • process.nextTick

那么宏任务跟微任务到底有什么区别呢?宏任务它是一个需要花费时间的回调,无法做到精细的时间控制。每次执行完一个宏任务时,假设宏任务中同样有一些非宏任务的异步任务,那么JavaScript就会为宏任务添加一个任务队列,所以宏任务中的异步任务被称为微任务。当宏任务中的同步代码执行完毕,微任务队列中的任务将一次执行完毕。

所以最后宏任务与微任务之间的执行顺序是这样的:宏任务->微任务队列执行->宏任务->微任务队列执行!