一直以来都知道JavaScript是一门单线程语言,在笔试过程中不断的遇到一些输出结果的问题,考量的是对异步编程掌握情况。一般被问到异步的时候脑子里第一反应就是Ajax,setTimseout...这些东西。在平时做项目过程中,基本大多数操作都是异步的。JavaScript异步都是通过回调形式完成的,开发过程中一直在处理回调,可能不知不觉中自己就已经处在回调地狱中。
浏览器线程
在开始之前简单的说一下浏览器的线程,对浏览器的作业有个基础的认识。之前说过JavaScript是单线程作业,但是并不代表浏览器就是单线程的。
在JavaScript引擎中负责解析和执行JavaScript代码的线程只有一个。但是除了这个主进程以外,还有其他很多辅助线程。那么诸如onclick回调,setTimeout,Ajax这些都是怎么实现的呢?即浏览器搞了几个其他线程去辅助JavaScript线程的运行。
浏览器有很多线程,例如:
1.GUI渲染线程 - GUI渲染线程处于挂起状态的,也就是冻结状态
2.JavaScript引擎线程 - 用于解析JavaScript代码
3.定时器触发线程 - 浏览器定时计数器并不是 js引擎计数
4.浏览器事件线程 - 用于解析BOM渲染等工作
5.http线程 - 主要负责数据请求
6.EventLoop轮询处理线程 - 事件被触发时该线程会把事件添加到待处理队列的队尾
7.等等等
从上面来看可以得出,浏览器其实也做了很多事情,远远的没有想象中的那么简单,上面这些线程中GUI渲染线程,JavaScript引擎线程,浏览器事件线程是浏览器的常驻线程。
当浏览器开始解析代码的时候,会根据代码去分配给不同的辅助线程去作业。
进程
进程是指在操作系统中正在运行的一个应用程序
线程
线程是指进程内独立执行某个任务的一个单元。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。
进程中包含线程,一个进程中可以有N个进程。我们可以在电脑的任务管理器中查看到正在运行的进程,可以认为一个进程就是在运行一个程序,比如用浏览器打开一个网页,这就是开启了一个进程。但是比如打开3个浏览器,那么就开启了3个进程。
同步&异步
既然要了解同步异步当然要简单的说一下同步和异步。说到同步和异步最有发言权的真的就属Ajax了,为了让例子更加明显没有使用Ajax举例。(●ˇ∀ˇ●)
同步
同步会逐行执行代码,会对后续代码造成阻塞,直至代码接收到预期的结果之后,才会继续向下执行。
console.log(1); alert("同步"); console.log(2); // 结果: // 1 // 同步 // 2
异步
如果在函数返回的时候,调用者还不能够得到预期结果,而是将来通过一定的手段得到结果(例如回调函数),这就是异步。
console.log(1); setTimeout(() => { alert("异步"); },0); console.log(2); // 结果: // 1 // 2 // 异步
为什么JavaScript要采用异步编程
一开始就说过,JavaScript是一种单线程执行的脚本语言(这可能是由于历史原因或为了简单而采取的设计)。它的单线程表现在任何一个函数都要从头到尾执行完毕之后,才会执行另一个函数,界面的更新、鼠标事件的处理、计时器(setTimeout、setInterval等)的执行也需要先排队,后串行执行。假如有一段JavaScript从头到尾执行时间比较长,那么在执行期间任何UI更新都会被阻塞,界面事件处理也会停止响应。这种情况下就需要异步编程模式,目的就是把代码的运行打散或者让IO调用(例如AJAX)在后台运行,让界面更新和事件处理能够及时地运行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
异步运行机制:
1.所有同步任务都在主线程上执行,形成一个执行栈。
2.主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
3.一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4.主线程不断重复上面的第三步。
举个例子:
<button>同步</button> <button>异步</button> <div></div> <script> function updateSync() { for (var i = 0; i < 1000000; i++) { document.getElementById('output').innerHTML = i; } } function updateAsync() { var i = 0; function updateLater() { document.getElementById('output').innerHTML = (i++); if (i < 1000000) { setTimeout(updateLater, 0); } } updateLater(); } </script>