JS运行机制之 Event Loop 的思考
先举个栗子,如下:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log('i: ',i); //一秒之后输出几乎没有时间间隔依次输出5个5
}, 1000);
}
console.log(i); //立即输出5
想必很多人看到立马能看出答案吧,但是为什么定时器不能依次打印出0, 1,2,3,4呢?答案稍后分晓。
那到底怎么才能依次输出我们想要的结果呢?大家可能都想到是利用闭包,或者是利ES6中的let声明,再或者可以用Promise, 如果还不过瘾就用ES7 的async或者await; 如下,但是今天我们主要不讲这个。
//利用闭包
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i); //0,1,2,3,4
}, 1000);
})(i);
} console.log( i); //5 //let
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log (i);
}, 1000); //0 ,1,2,3,4
} console.log( i); //这里会报错,因为let声明的块级作用域,外面是拿不到这个i的,使用没有声明的变量当然会报错啦
一、为什么js是单线程?
大家都知道js不同于其他语言,它是单线程的。那么问题来了,为什么不是多线程呢?按道理来说多线程不是能够同时解决问题提高效率么?除了多线程产生冲突、抢占资源等答案,还可以是什么呢?
其实,这跟它作为浏览器脚本语言的用途有关,浏览器的脚本语言主要的用途是用来与用户互动,会产生DOM的操作,这就是问题的关键,假设js是多线程,有一个线程是删除DOM操作,有个在当前DOM添加内容,这时候浏览器应该怎么办呢?这就决定了js应该被设计成单线程。那js有没有多线程的可能呢?答案是肯定的,HTML5提出的web Worker标准,但是,
“为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质” ----阮一峰的一篇博客。
二 、 Event Loop 事件循环
因为js是单线程,所以执行时候需要排队,前一个任务结束之后,后一个任务才会执行,那么如果前一个任务执行很久,后面一个任务就要等很久。如果一些等待不是因为CPU计算慢产生的,比如IO设备的使用,那么js会把等待的任务挂起,执行后面的任务,等IO返回结果,再回头执行直线挂起的任务。
这样任务就有了同步任务和异步任务,同步任务是主线程上执行的任务,形成一个执行栈(execution context stack),是排队进行的。异步任务不是在主线程执行的,是在“任务队列”(Queue)里面,只有当主线程所有同步的任务执行完成,任务队列中的异步任务才会进入主线程,然后被执行。
异步执行机制如下:---阮一峰
可视化描述---MDN
主线程上:
一个函数1被调用了,创建一个堆栈帧,包含了函数1的参数和局部变量,当函数1中又调用了函数2,又创建了一个堆栈帧,此时这个堆栈帧在第一个堆栈帧之前,包含了函数2的参数和局部变量。主线程先执行了至于顶层的堆栈帧(函数2产生的),当函数2返回时,对应的堆栈帧就出栈了,接着继续执行函数1的堆栈帧,直到栈空了。
消息队列:
一个 js运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因而创建了一个初始堆栈帧)。当栈再次为空的时候,也就意味着消息处理结束。
添加消息:
在浏览器里,添加事件可以是当一个事件出现且有一个事件监听器被绑定时,消息被随时添加。也可以是在调用setTimeout等函数时候,
在将来的某个时间后在消息对列中添加。
setTimeout:
调用该函数时候会在将来的某个时间后在消息对列中添加一个消息,如果执行栈中有其他任务没有完成(假设有一个很耗时的计算),setTimeout消息必须必须等到执行栈的任务完成才会处理,所以说该函数的第二个参数仅仅表示最少的时间 而非确切的时间。
即使你设置零延迟:
setTimeout(function cb1() {
console.log(‘我是第二个被执行的’);
}, 0);
console.log(‘我是第一个被执行的’); //先打印这句
事实上,js中规定,定时器的第二个参数设置最少不能小于4ms, 小于的话就按最小的4ms执行。
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任
务队列"中加入各种事件(click,load)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
三、回头看看开头的栗子
for循环和外面的console.log()是主线程上的同步任务,他们按循序执行,先执行for循环结束(此时i变为5),再执行console.log();
for循环中的setTimeout是在任务队列中,它只有等在前主线程中的栈空了之后,到一个时间才会被被执行,此时作用域中的i已经变为5,此时setTimeout中的回调函数所读取的i就是5了。
还有一个问题?
输出5的时间间隔是多少?答案显而易见是,立即打印一个5,1000ms之后几乎同时输出5个5; why???
正如上面解释的,for循环一次,会在任务队列中加上一个setTimeou任务(该任务是在1000ms后执行回调函数),这样循环结束,任务列表里面就有了5个setTimeou任务,且当主线程中栈空了之后,任务列表就开始进栈,等待1000ms之后执行回调,(注意此时的i变量已经变成5了)所以后面的5个5几乎在同时依次打印出来。
四、任务队列
JS分为同步任务和异步任务;
同步任务都是在主线程上执行,形成一个执行栈;
主线程之后,事件触发线程管理着一个任务队列【宏任务(MacroTask)和微任务(MicroTask)】,只要异步任务有了运行结果,就会往任务队列里面放置一个事件。
一旦执行栈中的所有同步任务执行完毕,此时JS引擎空闲,系统就会读取任务队列,将之前异步任务的结果(事件)添加到可执行栈中,开始执行。
4.1 宏任务
task(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:
task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
4.2微任务
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
4.3运行机制
在事件循环中,每进行一次循环操作称为 tick,但关键步骤如下:
- 执行所有执行栈中的任务(宏任务)
- 执行栈中执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 当执行栈空了之后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
JS运行机制之 Event Loop 的思考的更多相关文章
- Js 运行机制和Event Loop
一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...
- 浏览器工作原理(三):js运行机制及Event Loop
参考:https://segmentfault.com/a/1190000012925872#articleHeader4 一.为什么有Event Loop Javascript设计之初就是一门单线程 ...
- JavaScript 运行机制以及Event Loop(事件循环)
一.JavaScript单线程 众所周知JavaScript是一门单线程语言,也就是说,在同一时间内JS只能做一件事.为什么JavaScript不能有多个线程呢?这样不是能够提高效率吗? JavaSc ...
- Js 运行机制 event loop
Js - 运行机制 (Even Loop) Javascript 的单线程 - 引用思否的说法: JavaScript的一个语言特性(也是这门语言的核心)就是单线程.什么是单线程呢?简单地说就是同一时 ...
- js运行机制详解:event loop
总结 阮一峰老师的博客 一.为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程 那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript ...
- js 运行机制
<script> console.log(1) setTimeout(function(){ console.log(3) },0) console.log(2) </script& ...
- 如何通过setTimeout理解JS运行机制详解
setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行.它返回一个整数,表示定时器timer的编号,可以用来取消该定时器. 例子 ? 1 2 3 4 5 console.log(1 ...
- Js 运行机制 (重点!!)
一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: 这一题看似很简单,但如果你不了解JavaScript运行机制,很容易就答错了.题目的答案是依次输出1 2 3 ...
- 理解js事件循环(event loop)
队列:先进先出 栈:后进先出 javascript的Event Loop 和 Node.js的Event Loop 区别: js(运行在浏览器),有主线程.异步任务队列的概念: node.js使用li ...
随机推荐
- Java面向对象之构造函数 入门实例
一.基础概念 1.什么时候定义构造函数: 当对象创建时,需要对象必须具备的内容,通过构造函数完成. 2.一般函数和构造函数的区别: 定义上:构造函数只为对象的初始化,只执行一次.一般函数定义对象应该具 ...
- POJ-1328 Radar Installation--区间选点问题(贪心)
题目链接: https://vjudge.net/problem/POJ-1328 题目大意: 假设陆地的海岸线是一条无限延长的直线,海岛是一个个的点,现需要在海岸线上安装雷达,使整个雷达系统能够覆盖 ...
- jacascript DOM节点——节点关系与操作
前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 节点关系 DOM可以将任何HTML描绘成一个由多层节点构成的结构.每个节点都拥有各自的特点.数据和方法,也 ...
- Java入门2
一.Arrays工具类 1.数组地址的比较 int [] arr1={1,2,3,4,5}; int [] arr2={1,2,3,4,5}; System.out.println(arr1==arr ...
- windows平台安装maven
Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. 一.安装maven3.5.3 安装环境(条件):Windows10.jdk1.7+ 1.下载m ...
- 一 Django模型层简介(一)
模型 django提供了一个强大的orm(关系映射模型)系统. 模型包含了你要在数据库中创建的字段信息及对数据表的一些操作 使用模型 定义好模型后,要告诉django使用这些模型,你要做的就是在配置文 ...
- java中的引用传递问题
---恢复内容开始--- 第一个引用传递案例: class Message{ private int num; public Message(int num){ this.num=num; } pub ...
- Jenkins + Gradle + pgyer + Android自动发布
Jenkins配置与必要的环境配置 一:Jenkins服务端(Linux系统为例说明): 1.jdk安装与配置 2.SDK安装与配置 3.安装配置对应的gradle版本(建议gradle版本在4.1版 ...
- 机器学习基石:14 Regularization
一.正则化的假设集合 通过从高次多项式的H退回到低次多项式的H来降低模型复杂度, 以降低过拟合的可能性, 如何退回? 通过加约束条件: 如果加了严格的约束条件, 没有必要从H10退回到H2, 直接使用 ...
- Docker入门之--定制镜像
1. 首先定制一个Web 服务器为例 1.1 启动镜像 执行下面命令 docker run --name webserver -d -p 80:80 nginx 1.2 查看容器和镜像状态 然后执行下 ...