从一个例子引发对JS运行机制之 Event Loop 的思考

栗子如下:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(‘i: ‘,i);        //一秒之后输出几乎没有时间间隔依次输出5个5
    }, 1000);
}
console.log(i);                      //立即输出5

想必很多人看到立马能看出答案吧,但是为什么定时器不能依次打印出1,2,3,4,5呢?答案稍后分晓。

那到底怎么才能依次输出我们想要的结果呢?大家可能都想到是利用闭包,或者是利ES6中的let声明,但是今天我们不讲这个。

一、为什么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之后执行回调,所以后面的5个5几乎在一个点依次打印出来。

那么js执行的顺序就可以总结为:

主线程(同步任务) =》任务队列(异步) =》回调

时间: 2024-10-13 00:27:36

从一个例子引发对JS运行机制之 Event Loop 的思考的相关文章

记一次途家面试中问到的JavaScript事件机制:Event Loop

前几天去途家面试,问到了事件机制,以及异步队列的问题.很遗憾,当时答错了.回来之后查了下资料,看到阮一峰老师博客的分析,感觉讲的非常浅显易懂,就分享过来了. 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,

从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理(转)

前言 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正. ----超长文+多图预警,需要花费不少时间.---- 如果看完本文后,还对进程线程傻傻分不清,不清楚浏览器多进程.浏览器内核多线程.JS单线程.JS运行机制的区别.那么请回复我,一定是我写的还不够清晰,我来改... ----正文开始---- 最近发现有不少介绍JS单线程运行机制的文章,但是发现很多都仅仅是介绍某一部分的知识,而且各个地方的说法还不统一,容易造成困惑. 因此准备梳理这块知识点,结合已有的认知,基于网上的大量参

js 运行机制

javascript的运行机制 单线程 任务队列 事件和回调函数 异步IO javascript最大的特点就是单线程,也就是在同一时间只能做一件事情.那为什么会是单线程呢?这还要从javascript的用途来看.javascript的主要用途就是与用户互动以及DOM操作(浏览器中),这就决定的了它必须是单线程的,不然会出现很严重的问题.比如当有两个线程同时对一个DOM进行操作的时候,浏览器不知道到底以哪个线程为准.既然是单线程的,又怎样保证性能呢?接下来我们就来看看javascript的运行机制

js运行机制

先来看一道题目然后再来详细的说明js的运行机制,下面的一段代码执行顺序是什么 console.log(1); setTimeout(function () { console.log(2); }, 0); console.log(3); console.log(4); 测试得到的结果是1,3,4,2,至于为什么会这么执行,我们来看下面的内容

js运行机制 值引用 值传递

1.js是单线程的  为什么是单线程的呢  因为js作为浏览器脚本语言,会有很多和用户的互动,以及操作dom,多个线程会出问题. 2.js有同步任务,异步任务(ajax,用户点击等,settimeout) 异步任务执行机制: a.同步任务都在主线程上执行,形成执行栈: b.主线程外,存在一个任务队列,(虽然js是单线程的,但是浏览器内核是多线程的,在浏览器内部中不同异步操作由不同内核模块调度执行,异步操作回将相关操作添加到任务队列上,不同异步操作添加到任务队列的时机也是不同的 onclick--

js单线程的本质-------Event Loop

怎么判断是浏览器还是node环境? node中window是未定义:setImmediate是定义的,在浏览器中未定义 timer阶段:这个阶段会执行setTimeout和setInterval check阶段:执行setImmediate macro task [task] 宏任务 :script(页面代码).setTimeout.setInterval.I/O事件.UI交互事件(点击事件) micro  task [job] 微任务: Promise.process.nextTick.Pro

(转)浅析JS运行机制

原文 从一个简单的问题谈起: 1 <script type="text/javascript"> 2 alert(i); // ? 3 var i = 1; 4 </script> 输出结果是undefined, 这种现象被称成“预解析”:JavaScript引擎会优先解析var变量和function定义.在预解析完成后,才会执行代码.如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),运行顺序是: 1 step1.

一个例子读懂 JS 异步编程: Callback / Promise / Generator / Async

JS异步编程实践理解 回顾JS异步编程方法的发展,主要有以下几种方式: Callback Promise Generator Async 需求 显示购物车商品列表的页面,用户可以勾选想要删除商品(单选或多选),点击确认删除按钮后,将已勾选的商品清除购物车,页面显示剩余商品. 为了便于本文内容阐述,假设后端没有提供一个批量删除商品的接口,所以对用户选择的商品列表,需要逐个调用删除接口. 用一个定时器代表一次接口请求.那思路就是遍历存放用户已选择商品的id数组,逐个发起删除请求del,待全部删除完成

【JS】JavaScript引擎的内部运行机制

首先看一段小程序: <script> alert('第1'); setTimeout(function(){<pre name="code" class="javascript"><span style="white-space:pre"> </span>alert('第2'); }, 2000); alert('第3');</script> 输出顺序是:第1,第3,第2:再来看一段小程