JavaScript单线程异步的背后——事件循环机制

JavaScript单线程异步的背后——事件循环机制

感觉这篇文章拖了很久,好尴尬的拖延症

正文从这里开始~~~

对JavaScript有个很模糊的印象,它是单线程异步的。本文主要来说说JavaScript到底是怎么运行的。但在这之前,让我们先理一下这些概念(现学现卖)。

基本概念

线程与进程

进程(Process)是系统资源分配和调度的单元。一个运行着的程序就对应了一个进程。一个进程包括了运行中的程序和程序所使用到的内存和系统资源。如果是单核CPU的话,在同一时间内,有且只有一个进程在运行。但是,单核CPU也能实现多任务同时运行,比如你边听网易云音乐的每日推荐歌曲,边在网易有道云笔记上写博文。这算开了两个进程(多进程),那运行的机制就是一会儿播放一下歌,一会儿响应一下你的打字,但由于CPU切换的速度很快,你根本感觉不到,以至于你认为这两个进程是在同时运行的。进程之间是资源隔离的。

那线程(Thread)是什么?线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程。比如网易云音乐一边播放音频,一边显示歌词。多进程的运行其实也就是通过进程中的线程来执行的。一个进程下的线程是共享资源的。当多个线程同时操作同一个资源的时候,就出现资源争抢的问题。这又是另外一个问题了。

并行与并发

并行(Parallelism)是指程序的运行状态,在同一个时间内有几件事情并行在处理。由于一个线程在同一时间只能处理一件事情,所以并行需要多个线程在同一时间执行多件事情。

而并发(Concurrency)是指程序的设计结构,在同一时间内多件事情能被交替地处理。重点是,在某个时间内只有一件事情在执行。比如单核CPU能实现多任务运行的过程就是并发的。

同步与异步

同步异步是指程序的行为。同步(Synchronous)是程序发出调用的时候,一直等待直到返回结果,没有结果之前不会返回。也就是说,同步是调用者主动等待调用过程。

异步(Asynchronous)是发出调用之后,马上返回,但是不会马上返回结果。调用者不必主动等待,当被调用者得到结果之后会主动通知调用者。

举个例子,去奶茶店买饮料。同步就是,一个顾客说出需求(请求),然后一直等着服务员做好饮料,顾客拿到自己点的饮料之后才离开;然后下一个顾客继续重复上述过程。异步就是,顾客先排队点单,点完之后拿着单子在一边,等服务员做好之后会叫号,叫到你了你去拿就好。

所以线程跟同步异步没有直接的关系,单线程也是可以实现异步的。至于实现的方式,下面会具体说到。

阻塞与非阻塞

阻塞与非阻塞是指等待状态。阻塞(Blocking)是指调用在等待的过程中线程被“挂起”(CPU资源被分配到其他地方去了)。

非阻塞(Non-blocking)是指等待的过程CPU资源还在该线程中,线程还能做其他的事情。

以刚才排队买饮料的例子,阻塞就是你在等待的时候什么事情也做不了,而非阻塞是你在等待的时候可以管自己先做其他的事情。

所以,同步可以阻塞也可以非阻塞,异步可以阻塞也可以非阻塞。

单线程的JS

大概理清楚上述概念之后呢,就知道单线程和异步是没有矛盾的。那JS是如何执行的呢?JS其实就是一门语言,说是单线程还是多线程得结合具体运行环境。JS的运行通常是在浏览器中进行的,具体由JS引擎去解析和运行。下面我们来具体了解一下浏览器。

浏览器

目前最为流行的浏览器为:Chrome,IE,Safari,FireFox,Opera。浏览器的内核是多线程的。一个浏览器通常由以下几个常驻的线程:

  • 渲染引擎线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时触发器线程:处理定时事件,比如setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

需要注意的是,渲染线程和JS引擎线程是不能同时进行的。渲染线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了。

JS引擎

通常讲到浏览器的时候,我们会说到两个引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染页面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎对同一个样式的实现不一致,就导致了经常被人诟病的浏览器样式兼容性问题。这里我们不做具体讨论。

JS引擎可以说是JS虚拟机,负责JS代码的解析和执行。通常包括以下几个步骤:

  • 词法分析:将源代码分解为有意义的分词
  • 语法分析:用语法分析器将分词解析成语法树
  • 代码生成:生成机器能运行的代码
  • 代码执行

不同浏览器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。

回到JS是单线程这句话,本质上来说,是浏览器在运行时只开启了一个JS引擎线程来解析和执行JS。那为什么只有一个引擎呢?如果同时有两个线程去操作DOM,浏览器是不是又要不知所措了?!

JS运行机制

说了这么多之后,终于我们要说到JS的整个运行过程了。

同步执行

我们先看下JS同步执行过程是怎么做到的?这就涉及到了一个很重要的概念——执行上下文。我的一篇译文深入学习JavaScript闭包很详细地说明了这个概念。

执行上下文记录了代码运行时的环境,当前运行状态下有且有一个执行上下文在起作用。那执行上下文到底记录了什么呢?大概有词法环境,变量环境等。举个简单的例子:

var x = 10;

function foo(){
    var y=20;
    function bar(){
        var z=15; 
    }
    bar();
}

foo();

代码运行,首先进入全局上下文。然后执行foo()的时候,就进入了foo上下文,当然此时全局上下文还在。当执行 bar()的时候,又进入了bar上下文。执行完毕bar(),回到foo上下文。执行完foo(),又回到全局上下文。所以,执行过程执行上下文会形成一个调用栈(Call stack),先进后出。

// 进栈
                                                          3 bar Context
    =>                      =>   2 foo Context     =>     2 foo Context
        1 global Context         1 global Context         1 global Context

// 出栈
3 bar Context
2 foo Context     =>   2 foo Context     =>                        =>
1 global Context       1 global Context         1 global Context

在JS执行过程中,有且仅有一个执行上下文在起作用。因为JS是单线程的,一次只能做一件事。

以上的过程都是同步执行的。

异步执行——事件循环

我们回顾一下JS中自带了哪些原生的异步事件:

  • setTimeout
  • setInterval
  • 事件监听
  • Ajax请求
  • etc..

JS异步的效果得益于浏览器的执行环境。实际上,浏览器又开了线程来处理这些BOM事件。举例:

function foo(){
    console.log(1);
}

function bar(){
    console.log(2);
}

foo();

setTimeout(function cb(){
    console.log(3);
});

bar();

按照上一节的分析,首先进入全局上下文,运行至foo(),进入了foo上下文环境;执行console.log(1),控制台输出1;foo上下文环境出栈,运行至setTimeout,交给浏览器的定时处理线程;运行至bar(),进入了bar上下文环境;执行console.log(2),控制台输出2;foo上下文环境出栈;等到浏览器线程执行完setTimeout,返回cb()回调函数至当前任务队列;当发现执行栈为空时,浏览器的JS引擎会执行一次循环,将事件队列的队首出队至JS执行栈中;执行cb(),进入cb上下文环境;执行console.log(3),控制台输出3;事件队列为空,全局上下文出栈。

以上就是JS引擎的事件循环机制,是实现异步的一种机制。主要涉及到浏览器线程,任务队列以及JS引擎。所以,我们可以看出,JS的异步请求,借助了而它所在的运行环境浏览器来处理并且返回结果。而且,这也解释了为什么那些回调函数的this指向window,因为这些异步的代码都是在全局上下文环境下执行的。

后话:不知道自己理解对了没有,也不知道说得清楚不清楚。不当之处,欢迎指出。

参考资料:

JavaScript:彻底理解同步、异步和事件循环(Event Loop)还在疑惑并发和并行?IMWeb社区 浏览器进程?线程?傻傻分不清楚!Javascript是单线程的深入分析JavaScript单线程和浏览器事件循环简述AlloyTeam 【转向Javascript系列】从setTimeout说事件循环模型小胡子哥 JavaScript异步编程原理阮一峰 JavaScript 运行机制详解:再谈Event Loop【朴灵评注】JavaScript 运行机制详解:再谈Event LoopPhilip Roberts: Help, I’m stuck in an event-loop.Philip Roberts: What the heck is the event loop anyway?jquery中Ajax的异步和同步
编辑于 2017-05-22 11:30