先举个栗子,如下:

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 的思考的更多相关文章

  1. Js 运行机制和Event Loop

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

  2. 浏览器工作原理(三):js运行机制及Event Loop

    参考:https://segmentfault.com/a/1190000012925872#articleHeader4 一.为什么有Event Loop Javascript设计之初就是一门单线程 ...

  3. JavaScript 运行机制以及Event Loop(事件循环)

    一.JavaScript单线程 众所周知JavaScript是一门单线程语言,也就是说,在同一时间内JS只能做一件事.为什么JavaScript不能有多个线程呢?这样不是能够提高效率吗? JavaSc ...

  4. Js 运行机制 event loop

    Js - 运行机制 (Even Loop) Javascript 的单线程 - 引用思否的说法: JavaScript的一个语言特性(也是这门语言的核心)就是单线程.什么是单线程呢?简单地说就是同一时 ...

  5. js运行机制详解:event loop

    总结 阮一峰老师的博客 一.为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程 那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript ...

  6. js 运行机制

    <script> console.log(1) setTimeout(function(){ console.log(3) },0) console.log(2) </script& ...

  7. 如何通过setTimeout理解JS运行机制详解

    setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行.它返回一个整数,表示定时器timer的编号,可以用来取消该定时器. 例子 ? 1 2 3 4 5 console.log(1 ...

  8. Js 运行机制 (重点!!)

    一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: 这一题看似很简单,但如果你不了解JavaScript运行机制,很容易就答错了.题目的答案是依次输出1 2 3 ...

  9. 理解js事件循环(event loop)

    队列:先进先出 栈:后进先出 javascript的Event Loop 和 Node.js的Event Loop 区别: js(运行在浏览器),有主线程.异步任务队列的概念: node.js使用li ...

随机推荐

  1. Docker Mysql主从同步配置搭建

    Docker Mysql主从同步配置搭建 建立目录 在虚拟机中建立目录,例如路径/home/mysql/master/data,目录结构如下: Linux中 新建文件夹命令:mkdir 文件夹名 返回 ...

  2. api-gateway实践(06)新服务网关 - 请求监控

    一.实时监控 用户点击服务实例,系统显示服务实例-version下的api列表, 用户点击某个api的如下两个图标 1.API请求次数监控 横轴:时间,粒度为分钟 纵轴:请求访问次数 展示:失败数(红 ...

  3. Python基础题

    1. 执行Python脚本的两种方式: Chmod +x 脚本 ./脚本(路径的方式) Python 脚本 2. 简述位.字节的关系 一个字节=8位 3. 简述ASCII.unicode.utf-8/ ...

  4. Java-Maven(八):IDEA使用本地maven,并配置远程中央仓库

    声明:已经安装了maven,安装请参考:<Java-Maven(一):Maven的简介与安装> 1)一般我们从github.码云(https://gitee.com)上获取代码后,实际上我 ...

  5. 使用 vue-i18n 切换中英文

    兼容性: 支持 Vue.js 2.x 以上版本 安装方法:(此处只演示 npm) npm install vue-i18n 使用方法: 1.在 main.js 中引入 vue-i18n (前提是要先引 ...

  6. Map 接口简明

    Map 接口并没有继承Collection接口 HashMap : 哈希表数据结构,是线程不同步的,快速.允许存储 null 键,null 值.替代了 Hashtable. LinkedHashMap ...

  7. [LeetCode] Coin Path 硬币路径

    Given an array A (index starts at 1) consisting of N integers: A1, A2, ..., AN and an integer B. The ...

  8. [LeetCode] Minimum Genetic Mutation 最小基因变化

    A gene string can be represented by an 8-character long string, with choices from "A", &qu ...

  9. Mlecms 反射型xss && 后台任意文件下载

    应该算0day吧,自己分析出来的,有点鸡肋,不过小cms分析确实比较简单. xss地址:search.php?word=a><img+src=1+onerror=alert`1`>a ...

  10. Centos常用命令之:文件操作

    在centos中,常用的文件操作命令有: ◇touch:建置新文件或者修改文件时间◇cat:从第一行开始显示文件内容◇tac:从最后一行开始显示文件内容,和cat相反◇nl:显示的时候,顺道输出行号◇ ...