一、JavaScript为什么设计为单线程?

JavaScript语言的一大特点就是单线程,换言之就是同一个时间只能做一件事。其他任务都必须在后面排队等待。

for(var i = 0; i < 5; i++) {console.log(i);
}
console.log('end');

上面的代码,只有for循环执行完毕,才会执行end;

JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。JavaScript 从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。如果 JavaScript 同时有两个线程,一个线程在网页 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?是不是还要有锁机制?所以,为了避免复杂性,JavaScript 一开始就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 JavaScript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。JavaScript 语言本身并不慢,慢的是读写外部数据,比如等待 Ajax 请求返回结果。这个时候,如果对方服务器迟迟没有响应,或者网络不通畅,就会导致脚本的长时间停滞。

如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 操作(输入输出)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的“事件循环”机制(Event Loop)。
单线程模型虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果用得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node 可以用很少的资源,应付大流量访问的原因。

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。二、同步任务和异步任务
程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。
同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。
举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。

三、任务队列和事件循环
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)
首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。
JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。

范例1

 let timer1 = setTimeout(() => {console.log(1)}, 0)console.log(2)

以上代码先输出1,立即输出2 延时0ms和延时1ms效果其实是一样的,定时器有最小时间粒度。[1]定时器最小时间间隔:在苹果机上的最小时间间隔是10ms,在Windows系统上的最小时间间隔大约是15ms。Firefox中定义的最小时间间隔是10ms,而HTML5规范中定义的最小时间间隔是4ms

范例2

let t = ture
let timer = setTimeout(() {t = false
}, 1000)
while (t) {}
console.log('end');

以上代码是一个死循环,结果是浏览器卡死,永远不会输出'end'

范例3

const useTime = t => {let start = Date.now()while (Date.now() - start < t) {}
}
let timer1 = setTimeout(() => {console.log(3)
}, 500)
let timer2 = setTimeout(() => {console.log(4)
}, 1000)
console.log(1)
useTime(2000)
console.log(2)

以上代码立即输出1,等待2秒后同时依次输出2、3、4,因为setTimeout是异步任务,而timer1比time2先注册,且timer1比timer2定时时间短。又因为useTime函数执行时间是2s,所以执行完之后,将立即执行任务队列里的timer1、timer2,所以等待2秒后将依次输出2、3、4。
执行流程
1、进入全局执行上下文
创建由第三方计时模块管理的timer1,timer1会在500ms后把任务fn1放入任务队列
创建由第三方计时模块管理的timer2,time2会在1000ms后把任务fn2放入任务队列
输出1
2、进入useTime执行上下文
执行代码耗时2000ms,退出useTime执行上下文
在500ms和1000ms时timer1和timer2各自完成投放,此操作不属于JS主线程
3、返回全局执行上下文
输出2
同步任务执行完毕 开始扫描任务队列
取出队列的fn1
4、进入fn1执行上下文
输出3,退出fn1执行上下文
取出队列的fn2
5、进入fn2执行上下文
输出4,退出fn2执行上下文
循环扫描任务队列
线程、事件循环和任务队列
Javascript是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue)事件循环:JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为Tick。每次Tick的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次Tick会查看任务队列中是否有需要执行的任务。任务队列:异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如onclick, setTimeout,ajax 处理的方式都不同,这些异步操作是由浏览器内核的webcore来执行的,webcore包含下图中的3种 webAPI,分别是DOM Bindingnetworktimer模块。

  • DOM Binding 模块处理一些DOM绑定事件,如onclick事件触发时,回调函数会立即被webcore添加到任务队列中。
  • network 模块处理Ajax请求,在网络请求返回时,才会将对应的回调函数添加到任务队列中。
  • timer 模块会对setTimeout等计时器进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。

主线程:JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。
参考:

小爽4230:单线程、任务队列、同步与异步​zhuanlan.zhihu.com

饥人谷课件:

https://static.xiedaimala.com/xdml/file/f40ceb64-df08-4420-9226-7f76dbff15d5/2019-12-20-11-14-11.pdf​static.xiedaimala.comjavascript中单线程和任务队列​www.jianshu.com

参考

  1. ^定时器最小时间间隔:在苹果机上的最小时间间隔是10ms,在Windows系统上的最小时间间隔大约是15ms。Firefox中定义的最小时间间隔是10ms,而HTML5规范中定义的最小时间间隔是4ms。

javascript 等待指定时间_javascript的单线程和任务队列相关推荐

  1. 等待指定时间后自动跳转或关闭当前页面

    //指定时间之后跳转 <script language="javascript"> function go( ) {//定义函数 window.location=&qu ...

  2. javascript等待异步线程完成_前端:什么是单线程,同步,异步?彻底弄懂 JavaScript 执行机制...

    javascript是按照语句出现的顺序执行的. js是一行一行执行的: let a = '1';console.log(a);let b = '2';console.log(b); 然而实际上js是 ...

  3. javascript等待异步线程完成_JavaScript 中的异步原理

    来源:极链科技 作者:周哲 所谓"异步" ,简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段.比如,有一个任务是读取文件进行处理,异 ...

  4. JavaScript实现距离指定时间还有多少天

    /*** 获取距离指定时间还有多少天* @param {String | Number | Date} dateTime 日期时间* @example* ```javascript* getDista ...

  5. JavaScript 整分或者指定时间执行操作

    整分 let timer = null function timeFunc() {const date = new Date()// 取当前分钟个位数,方便计算const mins = date.ge ...

  6. javascript获取系统时间时区_javascript怎么获取当前时间?

    javascript怎么获取当前时间?下面本篇文章就来给大家介绍一下使用javascript获取当前时间的方法,希望对大家有所帮助. 在JavaScript中可以使用Date对象中的Date()方法来 ...

  7. javascript获取系统时间时区_javascript怎么获取显示系统时间?

    JavaScript可以使用date()方法获取系统时间,使用getYear().getMonth().getDate()等方法将系统时间转换为年月日格式,然后使用innerHTML方法将转换后的日期 ...

  8. javascript等待异步线程完成_作为前端你了解JavaScript运行机制吗?

    作为前端工程师,大家都知道js是前端一开始就要学会的知识点,js的代码你会写了,那js的运行机制你了解吗?只有了解了js的运行机制,才能在工作中如鱼得水,今天就跟随珠峰的老师一起来了解下js的运行机制 ...

  9. 运行指定代码_JavaScript 运行机制(Event Loop)详解

    一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

最新文章

  1. CF332C Students' Revenge
  2. 一个合格的程序猿编程
  3. 重叠面积_谁出去?谁不出去?重叠部分面积的探讨
  4. mint ui tabbar选中后怎么改变icon图标_UI全书(下)读后梳理:iPhone设计规范和Material Design规范...
  5. erlang精要(1)-四则算术运算
  6. 1035 插入与归并 (25 分)(c++)
  7. UI组件库从1到N开发心得-组件篇
  8. LeetCode 1230. 抛掷硬币(DP)
  9. 给plt.axvline设置图例(label)
  10. 在阿里云服务器centOs7系统中部署.NET Core项目
  11. mysql定义和调用存储过程
  12. error) DENIED Redis is running in protected mode because protected mode is enabled报错
  13. ArcGIS Engine中如何获取Map中已经选择的要素呢(转)
  14. 字符编码(一):序言
  15. 图像分割学习笔记_1(opencv自带meanshift分割例子)
  16. word 中巧妙添加分隔线
  17. 华为薪资等级结构表_华为公司等级薪酬制度
  18. SD卡 TF卡 引脚定义
  19. php txt投票功能,php查询操作实现投票功能
  20. 云计算的特点,主要有哪些?

热门文章

  1. 使用c++查看linux服务器某个进程正在使用的内存_Linux 系统管理
  2. python输出50-150之间不能被5整除的整数代码解读
  3. python构建二叉树_python--使用递归的方式建立二叉树
  4. 谈谈神经网络的大规模训练优化
  5. ELECTRA模型精讲
  6. 美团开源 Logan Web:前端日志在 Web 端的实现
  7. 百度高级Java三面题目!涵盖JVM +Java锁+分布式等
  8. Android官方开发文档Training系列课程中文版:添加ActionBar之设置ActionBar
  9. Failed to execute goal org.apache.maven.plugins:maven-resources-plugin
  10. 集成学习(西瓜书学习)