简介:学过JavaScript(以下简称JS)的人都知道,它是一种单线程、非阻塞的脚本语言。单线程是指在JS代码执行时,任何时候都只有一个主线程来处理所有的任务,也就是说JS无法进行多线程编程,但是JS中有一个无处不在的异步概念。我们怎么理解?理解异步和非阻塞依赖于事件循环(EventLoop)。本文将围绕JS线程、同步异步、任务队列等讲解EventLoop,JS线程让我们更容易理解事件循环。在此之前,我们先简单了解一下什么叫JS线程。比如浏览器的渲染进程是多线程的,主要包括以下几个线程:JS引擎线程(主线程):负责解析JS脚本和运行代码。GUI渲染线程:负责渲染浏览器界面,解析HTML、CSS,构建DOM树和RenderObject树,布局绘制等都会执行。定时器触发线程(setTimeout):浏览器定时计数器不被JS引擎统计,因为JS引擎是单线程的,如果处于阻塞线程状态,会影响计时的准确性,所以定时和由单独的线程触发,定时器结束后,加入事件队列,等待JS引擎空闲后再执行。http请求线程(ajax):连接XMLHttpRequest后,通过浏览器开启一个新的线程请求。当检测到状态变化时,如果同时设置了回调函数,异步线程会产生一个状态变化事件,回调会被放入事件队列,然后由JS引擎执行。浏览器事件触发线程(onclick):属于浏览器而非JS引擎,用于控制事件循环。可以理解为JS引擎本身太忙了,浏览器需要另开一个线程来辅助。主线程与渲染线程互斥:JS引擎线程与GUI渲染线程互斥。当JS引擎执行时,GUI线程会被挂起(相当于被冻结),GUI更新会保存在一个队列中,直到JS引擎空闲时立即执行。浏览器内核EventLoop轮询处理线程:我们可以将其理解为主线程、异步线程和消息队列之间进行通信和通信的中介。如下图所示:从主线程顺时针方向看,整个过程是循环的。只有主线程的同步代码执行完了,才会去队列中看看还有什么需要执行。主线程分别给三个线程setTimeout、ajax、dom.onclick,它们之间有一些区别。1、对于setTimeout代码,定时器在收到该代码时触发线程开始计时,时间到时将回调函数抛入消息队列。2、对于ajax代码,http异步线程立即发起http请求,请求成功后将回调函数丢入消息队列。3、对于dom.onclick,浏览器事件线程会先监听dom,直到dom被点击后才会将回调函数丢到消息队列中。同步和异步JS分为同步任务和异步任务:同步任务:立即执行的任务队列,比如一个简单的函数;异步任务:请求接口发送ajax,发送promise,或者时间定时器等;任务队列(EventQueue)什么是任务队列?可以理解为静态队列存储结构,遵循先进先出原则:同步任务会立即执行,进入主线程;异步任务会被放入任务队列(EventQueue)中。宏任务队列和微任务队列宏任务(MacroTask):总体代码Script、UI渲染、setTimeout、setInterval、setImmediate(Node.js环境)。微任务(MicroTask):Promise.then(),catch,finally。区别:事件循环中可能有多个MacroTask队列,而MicroTask队列只有一个。MicroTask先于MacroTask执行,所以如果有逻辑需要先执行,MicroTask队列会先于MacroTask执行。下面的代码示例可以让我们充分理解各个任务的执行顺序:执行栈MacroTask和MicroTask被压入栈中执行。JS是单线程的,也就是说只有一个主线程,主线程有一个栈。当每个函数执行时,都会产生一个新的执行上下文。执行上下文会包含当前函数的参数、局部变量等一些信息。它被压入栈中,执行上下文总是在栈顶。当一个函数完成执行时,它的执行上下文从堆栈中弹出。总结同步和异步任务进入不同的执行环境。先执行同步任务,将异步任务放入循环队列,等待同步任务执行完毕,再执行队列中的异步任务。异步任务先执行微任务,再执行宏任务。一直这样循环,反复执行,就是我们所说的EventLoop(事件循环)。事件循环是JS语言中一个非常重要和基础的概念。让我们清楚的了解事件循环的执行顺序和各个阶段的特点,可以让我们清楚的了解一段异步代码的执行顺序,从而减少代码运行的不确定性。合理使用各种延迟事件的方法将有助于代码根据其优先级更好地执行。如果您在阅读过程中发现文章中存在问题,请留言,感谢您阅读本文。作者简介倪萌,网易云信web前端开发工程师,目前从事云信金融线业务的开发工作。
