JavaScript是多线程的!从JS的事件循环、队列和执行栈理解JS的异步运行机制

一、JavaScript是多线程还是单线程?JavaScript不是多线程的,也不是单线程!为什么?这个说法本身有点问题,线程指的是在一个栈空间执行的流程,应该说JavaScript不提供创建多线程创建和管理的机制,更准确的来说,JavaScript是解释性语言,是JS的引擎在负责运行JavaScript的代码,而且一般来说JS引擎是和浏览器成为一个整体的进程,JS引擎线程只是浏览器的其中一个线程,在这方面来说,我们是在做面向线程的JavaScript开发,而不是面向进程上的JavaScript开发。所以说为什么JavaScript要有多线程,它自己本身就只是一个线程,至于说多线程操作同一个DOM元素的解析倒不是有多么合理,JAVA和C++都支持多线程创建,但是面向GUI开发不是仍旧要处理多线程的同步问题,终究还是因为JavaScript最初是被设计运行在浏览器的语言,否则在V8引擎里面使用C++添加创建多线程的API也不是不行。

JavaScript是多线程的!从JS的事件循环、队列和执行栈理解JS的异步运行机制

文章图片
【JavaScript是多线程的!从JS的事件循环、队列和执行栈理解JS的异步运行机制】所以在浏览器运行的JavaScript是运行在单一线程中的,也就是在一个栈空间中,JavaScript的所有操作都是在主线程(JS本身的线程)完成,但是JavaScript的运行环境是多线程的,如果JS有异步执行的需要,它需要依靠其它线程完成异步操作,这是浏览器给JS分配线程执行任务,所以关于事件循环(event loop)、事件队列(event queue)、执行栈(execution context stack)这些概念其实都是关于JavaScript多线程(JS线程和其它线程)的处理问题。
二、JavaScript多线程处理和事件循环
JavaScript是多线程的!从JS的事件循环、队列和执行栈理解JS的异步运行机制

文章图片
在这里主要谈到的是浏览器的JavaScript,浏览器内核是多线程的,一般有这些线程:UI渲染线程、JavaScript引擎线程、AJAX线程、DOM事件线程和定时触发线程,其中UI线程和JS引擎线程是互斥的,也就是说JS执行的时候UI线程就会暂停。
首先解释上图:
我们写的JS代码被加载到栈空间中执行,栈是先进后出的,例如将一个函数入栈执行,参数先入栈,也是最后出栈,出栈就是栈清空了。
1、假设JS程序按调用顺序有三部分:init函数,ajax异步请求函数,for循环,那么init函数先入栈执行,异步执行的第一个特点:主线程先把当前的同步任务先执行完再进行多线程的操作,同步任务的意思就是只在当前线程执行的代码。也就是说,主线程会把for循环都执行完才会去处理多线程的问题。
2、主线程执行到ajax部分的时候,一般这里会传入一个回调函数onReceive用于接收数据,主线程仅仅在这里向其它线程递交任务,随即就向下执行了。
该ajax请求会被ajax线程处理,处理完毕,则在任务队列中添加onReceive事件任务,该队列类似于Array的push和shift操作,即先进先出,先加入队列的任务会先被加入到主线程执行。
3、事件循环:指的是事件循环线程循环查看任务队列中是否有任务,如果有任务则将该任务出列,添加到主线程执行,注意并不是任务队列有任务就会马上执行,而是要等到主线程处理完毕才行。
综上,试着简单地描述JavaScript的多线程处理:JavaScript将异步任务传给其它线程处理,其它线程将处理的结果返回到任务队列中,JS主线程执行完毕则会去处理任务队列中的任务。
三、关于JavaScript事件循环机制的例子首先我们先验证JavaScript对异步任务的处理顺序,看如下代码:
setTimeout(function(){ console.log("task-01"); }, 10); setTimeout(function(){ console.log("task-02"); }, 0); console.log("task-end"); // 输出结果顺序:task-end,task-02,task-01

为什么输出结果是这样的呢?上面setTimeout时间设置为0,意思为立即执行,但是这个任务是一个异步任务,它并不是立即执行的,它是等到主线程执行完后才是立即执行。所以你可以看到只要是异步任务,它就得等待主线程空闲才会被执行。
继续看下面的代码:
for(var i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); }, 200); }// 输出结果为10个10

为什么是10个10呢?首先for循环最后一次i==9的时候还会去执行i++,所以i==10,这里要注意i是在for循环中创建的,一般在其它语言中执行完则被回收了的,但是这里的i在for循环后还存在,你可以通过window.i查看i的值。
主线程执行完之后,将队列中的任务依次入栈执行,i的值都是取自于window.i,如果没有需要i的操作这个代码是没有问题的,但是如果需要分别输出0-9,则可以使用下面即时函数的方法:
for(var i = 0; i < 10; i++){ (function(value){setTimeout(function(){ console.log(value); }, 200)})(i); }

    推荐阅读