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 ...
随机推荐
- Jetty入门(1-1)Jetty入门教程
一.Jetty是什么? 1.Jetty 是一个Java语言编写的,开源的Servlet容器和应用服务器. Jetty 极度轻量级.高便携性.功能强大.灵活和扩展性好,而且支持各种技术如SPDY.Web ...
- copy代码(含static对象)留下的致命错误
本来以为这个bug快改不好了,然而发现了问题所在 copy代码没有完全改掉对象名称,导致对象重复创建了,由于是static所以debug过程中 注释了addProperty(gridRowDetail ...
- Extensions in UWP Community Toolkit - Mouse Cursor
概述 UWP Community Toolkit Extensions 中有一个为 Mouse 提供的扩展 - Mouse Cursor Extensions,本篇我们结合代码详细讲解 Mouse C ...
- MySQLdb、 flask-MySQLdb 、MySQL-python 安装失败
今天在学习flask的时候,学习到数据库部分,连接mysql生成表,运行程序报错误:No module named MySQLdb 此时 需要安装 以下两个中任何一个 pip install flas ...
- Mysql 测试题
一. 表结构和数据 作业要求 /* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL ...
- 谈谈spring-boot不同包结构下,同样的类名冲突导致服务启动失败解决方案
项目背景: 某日,有需求要在三天的时间内完成两个大项目的项目合并,因为之前两个项目的包结构和类名都很多相同,于是开始考虑使用加一级包进行隔离,类似于这种结构 但是在启动的过程中,抛出来这样的异常: C ...
- join()的用法
Python中有join()和os.path.join()两个函数,具体作用如下: join(): 连接字符串数组.将字符串.元组.列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 ...
- 原生js中实现全选和反选功能
<!DOCTYPE html> <html> <head lang="en"> <meta char ...
- ashx页面怎么调用Handler的Session
aspx里面直接可以用Session["Name"]进行赋值和取值,ashx中就得继承接口IRequiresSessionState.然后使用! 实现: public class ...
- C#之设计模式之六大原则(转载)
设计模式之六大原则(转载) 关于设计模式的六大设计原则的资料网上很多,但是很多地方解释地都太过于笼统化,我也找了很多资料来看,发现CSDN上有几篇关于设计模式的六大原则讲述的比较通俗易懂,因此转载过来 ...