JavaScript单线程机制
总所周知,JavaScript是一门单线程的脚本语言,所有事情必须按部就班的按照一个顺序依次执行下去,那么为什么JavaScript是一门单线程语言呢?这和他的运用场景有关和设计初衷有关,由于JavaScript的设计初衷是一门运行在客户端的脚本语言,方便浏览器与用户进行交互,通过JavaScript去操作DOM结构,那么DOM操作就必须按照一定的顺序来,如果有多线程同时操作一个DOM,那么情况会变得相当混乱。
但是在处理器愈来愈强大的今天,单线程是有点大材小用,于是HTML5提出了web work标准,允许创建多个线程,但是次线程是无法操作DOM且必须完全由主线程指挥,所以其实本质上JavaScript依旧是单线程。
事件循环(Event Loop)
当执行栈空闲的时候会去读取任务队列,把任务队列的事情搬到执行栈,在处理过程中如果有遇到异步代码再次挂起,执行栈空的时候再去任务队列拿,形成了一个回环,这就是事件循环 event loop,这里提到了执行栈和任务队列的概念,下文会讲到。
执行栈和任务队列(stack and heap)
在JavaScript中,是一个单线程的执行过程,前一个事情没做完,后面的事情就会一直等着,但是有一些事情需要的执行时间很长(比如ajax请求),就会导致整个队列在那干等着,于是会,JavaScript创始人开放了执行栈和任务队列。
- 一段JavaScript代码放进执行栈逐行解析,碰到异步代码,会将异步代码挂起,然后继续往下逐行解析。
- 异步代码被挂起执行,当执行结束拿到结果的时候,会在任务队列放置一个事件。
- 当执行栈空的时候,系统会去读取任务队列,将已经存在任务队列里面的代码全部拿进执行栈,当作同步代码继续执行。(这时候异步代码可以理解为变成同步代码,因为只有已经有结果的异步代码才会被放置到任务队列)
- 执行栈执行这些“同步代码”(之前异步转同步的)的时候,如果又遇到了异步代码(比如定时器里面创建定时器),那么异步代码会继续挂起。
这里解释一下,定时器被挂起的时候做的事情
-
定时器
- 当一个定时器被挂起的一瞬间,就开始计时了,当计时完毕的时候回调函数立马扔进任务队列
- 执行栈执行完了,去任务队列拿进执行栈
- 例子一:
const timer = setTimeout(function(){ console.log('我是计时器,5s的那个') },5000) const timer = setTimeout(function(){ console.log('我是计时器,2s的那个') },2000) ... //这一段是计算量非常大的同步代码,计算完成需要费时10s //程序在运行 10s 的时候输出: '我是计时器,2s的那个' '我是计时器,5s的那个'
-
例子一我们得知:
-
定时器是一旦被执行栈侦测到,就被挂起,并且开始计时,一旦计时完成立马把回调函数扔进任务队列。
-
因为程序执行非常快,5s的定时器和2s的定时器几乎是同时被执行到并挂起开始计时的,2s的定时器因为时间比较短,会先计时完成扔进任务队列。
-
而当执行栈执行完毕(例子里面是用了10s),会到任务队列拿代码块进执行栈,队列先入先出的原理,所以先执行的是2s定时器的回调函数,再执行的是5s的回调函数
-
-
例子二:
const timer = setTimeout(function(){ console.log('我是计时器,5s的那个') setTimeout(function(){ console.log('我是在5s定时器里面创建的定时器') },5000) },5000) ... //这是一段计算量非常大的同步代码,计算完成需要费时10s //程序在运行 10s 的时候输出: '我是计时器,5s的那个' //时间又过了 5s '我是在5s定时器里面创建的定时器'
- 例子二我们得知:
- 当父计时器计时完成的时候被放进任务队列,此时子计时器还未创建。
- 当执行栈执行完成的时候,到任务队列拿代码,父定时器的回调被执行,这时候子计时器才被创建
- 可以理解为,异步的定时器计时完成回调扔进任务队列再被拿到执行栈的时候,这时候的回调函数的代码块已经是同步代码了,而回调里面的异步代码依旧是异步代码,该挂起的时候依旧要被挂起
那ajax呢?ajax代码也是一个异步代码,异步的时候发生了什么
- 执行栈执行的时候碰到ajax代码,ok,被挂起,这时候ajax请求已经发送了出去,只不过还没有回应
- 一旦有了回应,拿到响应体的时候,ajax执行的回调立马被扔进任务队列
- 当执行栈执行完毕的时候,到任务队列拿代码的时候才会执行ajax响应回来的回调函数
- 如果你有多个ajax请求呢?
- 他们都会被挂起
- 都是在各自挂起的一瞬间开始发送请求
- 谁先收到响应,谁的回调先扔进任务队列
总结
JavaScript执行栈执行的时候遇到异步代码的时候统统给你挂起,而这些异步代码不管被挂起多少个,也不管谁先挂起谁后挂起,只不过谁先搞定,谁的回调就先扔进任务队列罢了。