JavaScript单线程和异步机制

随着对JavaScript学习的深入和实践经验的积累,一些原理和底层的东西也开始逐渐了解。早先也看过一些关于js单线程和事件循环的文章,不过当时看的似懂非懂,只留了一个大概的印象:浏览器中的js程序时是单线程的。嗯,就这么点印象。当时也有些疑问:既然是单线程的,那异步调用是怎么实现的?计时器是靠谁来计时的,这单线程总不能一边执行程序一边计时吧?那些耗时的I/O操作为啥没把线程阻塞,不是说好的单线程么?相信很多不了解JavaScript单线程的同学也有过类似的疑问。

今天看了不少相关的资料,就详细地总结一下,希望能帮到大家,同时自己也梳理一下知识点,理解是一回事,能写下来是另一回事。好了,开始正文。

一、JavaScript单线程

在浏览器的一个页面中,该页面的JS程序只有一个线程,故曰单线程。因为是单线程,所以程序的执行顺序就是从上到下依次执行,同一时间内只能有一段代码被执行。那为什么不用多线程,这样不是更能充分利用CPU,提高效率么?

早期的网页内容非常简单,单线程足以应付,所以在设计之初,我估计设计者就没考虑使用多线程。另外,JavaScript主要用来处理用户与页面产生的交互,以及操作DOM;如果以多线程的方式来操作DOM,一个线程要求删除该DOM,另一个要求修改DOM样式,那么浏览器该听谁的?这大大增加了程序设计的复杂度,本来前端都不好学,不是么~~~简简单单多好。

虽然JavaScript是单线程的,可是浏览器内部不是单线程的。你的一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。

如果想利用多线程处理一些耗时较长的任务,可以使用HTML5提出的Web Worker。

二、任务队列和事件循环

提到异步机制,不得不说到任务队列和事件循环,这里理解JS异步机制的要点。

首先理解浏览器的并发模型,来看下面这个图:

左边的栈存储的是同步任务,所谓同步的任务就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。右边的堆用来存储声明的变量、对象。下面的队列就是任务队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和后面提到的setTimeout插入的事件。每个异步任务都和一个回调函数相关联。

一个js程序的单线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取任务队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务,执行完毕。

单线程从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫任务循环。因为每个任务都由一个事件所触发,所以也叫事件循环。

三、异步机制

有了上面两节做铺垫,理解异步机制就容易多了。拿ajax来说,当页面的单线程执行xhr.send()之后,对于页面来说发送任务已经完成了。怎么发送,那是浏览器的事,和单线程无关;什么时候响应,这事说不准。为了及时地得到响应的内容,在单线程中注册相应的事件就好xhr.onreadystatechange = fn() {...}。注册之后,浏览器会在内部的其他线程中自动地帮我们监听该事件。直到该事件被触发,浏览器会在任务队列中添加一个任务等待该单线程执行。

四、定时器

setTimeout的作用是在间隔一定的时间后,将回调函数插入任务队列中,等栈中的同步任务都执行完毕后,再执行。因为栈中的同步任务也会耗时,所以间隔的时间一般会大于等于指定的时间。

setTimeout(fn, 0)的意思是,将回调函数fn立刻插入任务队列,等待执行,而不是立即执行。看一个例子:

setTimeout(function() {
    console.log("a")
}, 0)

for(let i=0; i<10000; i++) {}
console.log("b")

// 结果:b  a

打印结果表明回调函数并没有立刻执行,而是等待栈中的任务执行完毕后才执行的。栈中的任务执行多久,它就得等多久。

五、总结

JavaScript单线程和其异步机制就如上所述。所谓的单线程并不孤单,它的背后有浏览器的其他线程为其服务,其异步也得靠其他线程来监听事件的响应,并将回调函数推入到任务队列等待执行。单线程所做的就是执行栈中的同步任务,执行完毕后,再从任务队列中取出一个事件(没有事件的话,就等待事件),然后开始执行栈中相关的同步任务,不断的这样循环。

参考资料

时间: 2024-12-24 13:23:22

JavaScript单线程和异步机制的相关文章

[转]JavaScript异步机制详解

原文: https://www.jianshu.com/p/4ea4ee713ead --------------------------------------------------------------------------- 学习JavaScript的时候了解到JavaScript是单线程的,刚开始很疑惑,单线程怎么处理网络请求.文件读写等耗时操作呢?效率岂不是会很低?随着对这方面内容的了解和深入,知道了其中的奥秘.本篇文章就主要讲解一下JavaScript怎么处理异步问题. 一.同

javascript的单线程异步机制探究

JavaScript的同步异步模式 JavaScript的语言执行环境都是单线程.单线程就是想排队那样任务需要按顺序一个一个的完成.这种模式的好处是实现起来简单,但是坏处就是如果队列中有一个任务耗费时间很长,那后边的任务就必须等前一个执行完,会拖延整个程序的执行. 为了优化这个问题,JavaScript语言将任务的执行模式分成两种,同步(Synchronous)和异步(Asynchronous). 同步模式就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的.同步

JavaScript异步机制

单线程异步执行的JavaScript JavaScript是单线程异步执行的,单线程意味着代码在任务队列中会按照顺序一个接一个的执行.异步代表JavaScript代码在任务队列中的顺序并不完全等同于代码的书写顺序,比如事件绑定.Ajax.setTimeout()等任务的发生时间是“不可被预期”的. 既然JavaScript是单线程机制,那Ajax为什么是异步的?setTimeout()是怎样执行的? 在浏览器中,JavaScript引擎是单线程执行的.也就是说,在同一时间内,只能有一段代码被Ja

Javascript的单线程和异步编程

运行时概念 下面的内容解释了一个理论上的模型.现代 JavaScript 引擎着重实现和优化了描述的几个语义. 可视化描述 栈 函数调用形成了一个栈帧. function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); 当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量.当bar调用foo时,第二个帧就被

深入理解Javascript单线程谈Event Loop

假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件."但你能说出背后的原因吗? 1.线程与进程 进程:是系统资源分配和调度的单元.一个运行着的程序就对应了一个进程.一个进程包括了运行中的程序和程序所使用到的内存和系统资源. 线程:线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程. 2.同步和异

细说JavaScript单线程的一些事

最近被同学问道 JavaScript 单线程的一些事,我竟回答不上.好吧,感觉自己的 JavaScript 白学了.下面是我这几天整理的一些关于 JavaScript 单线程的一些事. 首先,说下为什么 JavaScript 是单线程? 总所周知,JavaScript是以单线程的方式运行的.说到线程就自然联想到进程.那它们有什么联系呢? 进程和线程都是操作系统的概念.进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间.代码.数据和其它系统资源所组成:进程在运行过程中能够申请创建和使用系

【javascript 进阶】异步调用

前言 javascript的中的异步是很重要的概念,特别是ajax的提出,给整个web带来了很大的影响,今天就介绍下javascript的异步编程. 同步与异步 何为同步?何为异步呢? 同步:说白了就是程序一步一步从下向下执行,没有什么别的代码的跳动,就是按序执行,和在景区里女生上厕所是排队是一样的(每次女厕都是有好多人在排队).可以看成是一个单线程问题. 异步:异步就是程序可以跳着执行,开始执行一段程序之后不用等返回结果就执行其他的代码,等结果返回之后在对结果进行处理,也就是可以在有限的时间内

从Javascript单线程谈Event Loop

假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件."但你能说出背后的原因吗? 先理解相关概念 线程与进程 进程:是系统资源分配和调度的单元.一个运行着的程序就对应了一个进程.一个进程包括了运行中的程序和程序所使用到的内存和系统资源. 线程:线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程.

js的单线程和异步

js的单线程和异步 前言 说到js的单线程(single threaded)和异步(asynchronous),很多同学不禁会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性.js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性.往下看,你会发现js的机制是多么的简单高效! 说说浏览器 js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),