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 ...
随机推荐
- linux下的Shell编程(5)循环
Shell Script中的循环有下面几种格式: while [ cond1 ] && { || } [ cond2 ] -; do - done for var in -; do - ...
- python入门(7)Python程序的风格
python入门(7)Python程序的风格 Python采用缩进方式,写出来的代码就像下面的样子: # print absolute value of an integer: a = 100 if ...
- python网络爬虫与信息提取 学习笔记day2
Day2: 查看robots协议: 查看京东的robots协议 查看百度的robots协议,可以看到百度拒绝了搜狗的爬虫233 爬取京东商品页面相关信息: import requests url = ...
- 扫描工具nmap介绍
NMap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包. 简介 nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行在哪些连接端,并且推 ...
- svn介绍和安装
什么是SVN呢,作用是什么: SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS/CVS,它采取了分支管理系统,它的设计目标就是取代CVS.SVN就是用于多个人共同开 ...
- 洛谷P1209-最大公约数与最小公倍数问题
一个萌新的成长之路 Discription 输入二个正整数x0,y0(2<=x0<100000,2<=y0<=1000000),求出满足下列条件的P,Q的个数 条件: 1.P, ...
- A、B同时打开一个页面进行同一条数据库记录进行修改,A修改完成后提交表单,A修改的数据保存完成后;当B也修改完成后,提交数据进行数据修改。此时B修改的内容会覆盖A修改的内容,请问如何避免?
A.B同时打开一个页面进行数据中的一条数据进行修改,A修改完成后提交表单,数据修改保存完成后B开始页面也修改完成,开始提交进行修改.此时B修改的内容会覆盖A的内容,请问如何避免? 通过搜索和我个人总结 ...
- ECSHOP3.6版 钻石小鸟模板修改教程
ecshop3.6版 钻石小鸟 模板修改明细 (1) 钻石小鸟 首页轮播图修改 (2)首页布局设置 (修改前建议先备份下数据库. 后台/数据备份) (3)修改模板头部内容. 如下图. 后台,模板设置 ...
- jquery的attr()方法
一.定义和用法 attr() 方法设置或返回被选元素的属性和值. 当该方法用于返回属性值,则返回第一个匹配元素的值. 当该方法用于设置属性值,则为匹配元素设置一个或多个属性/值对. 二.语法 返回属性 ...
- jmc远程连接windows环境tomcat
新人报道,先发个小贴赚点人气,本人目前还是小菜鸟,想要飞却怎么也飞不高,哈哈,转到正题,最近发现这个JMC挺好用的,而且也不用像Jprofile需要破解,本地连接挺方便的, 但配置服务器确实挺坑的,按 ...