如何通过setTimeout理解JS运行机制详解
setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行。它返回一个整数,表示定时器timer的编号,可以用来取消该定时器。
例子
1
2
3
4
5
|
console.log(1); setTimeout( function () { console.log(2); }, 0); console.log(3); |
问:最后的打印顺序是什么?(如果不了解js的运行机制就会答错)
正确答案:1 3 2
解析:无论setTimeout的执行时间是0还是1000,结果都是先输出3后输出2,这就是面试官常常考查的js运行机制的问题,接下来我们要引入一个概念,JavaScript 是单线程的。
二、 JavaScript 单线程jquery特效
JavasScript引擎是基于事件驱动和单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行程序,即主线程。
通俗的说:JS在同一时间内只能做一件事,这也常被称为 “阻塞式执行”。
任务队列
那么单线程的JavasScript是怎么实现“非阻塞执行”呢?
答:异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。
诸如事件点击触发回调函数、ajax通信、计时器这种异步处理是如何实现的呢?
答:任务队列
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
任务队列:一个先进先出的队列,它里面存放着各种事件和任务。
同步任务
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
- 输出
- 如:console.log()
- 变量的声明
- 同步函数:如果在函数返回的时候,调用者就能够拿到预期的返回值或者看到预期的效果,那么这个函数就是同步的。
异步任务
- setTimeout和setInterval
- DOM事件
- Promise
- process.nextTick
- fs.readFile
- http.get
- 异步函数:如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。
除此之外,任务队列又分为macro-task(宏任务)与micro-task(微任务),在ES5标准中,它们被分别称为task与job。
宏任务
- I/O
- setTimeout
- setInterval
- setImmdiate
- requestAnimationFrame
微任务
- process.nextTick
- Promise
- Promise.then
- MutationObserver
宏任务和微任务的执行顺序
一次事件循环中,先执行宏任务队列里的一个任务,再把微任务队列里的所有任务执行完毕,再去宏任务队列取下一个宏任务执行。
注:在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
三、setTimeout运行机制jQuery插件
setTimeout 和 setInterval的运行机制是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。
这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。
优先关系:异步任务要挂起,先执行同步任务,同步任务执行完毕才会响应异步任务。
四、进阶
1
2
3
4
5
|
console.log( 'A' ); setTimeout( function () { console.log( 'B' ); }, 0); while (1) {} |
大家再猜一下这段程序输出的结果会是什么?
答:A
注:建议先注释掉while循环代码块的代码,执行后强制删除进程,不然会造成“假死”。
同步队列输出A之后,陷入while(true){}的死循环中,异步任务不会被执行。
类似的,有时addEventListener()方法监听点击事件click,用户点了某个按钮会卡死,就是因为当前JS正在处理同步队列,无法将click触发事件放入执行栈,不会执行,出现“假死”。
五、图片插件定时获取接口更新数据
1
2
3
4
5
|
for ( var i = 0; i < 4; i++) { setTimeout( function () { console.log(i); }, 1000); } |
输出结果为,隔1s后一起输出:4 4 4 4
for循环是一个同步任务,为什么连续输出四个4?
答:因为有队列插入的时间,即使执行时间从1000改成0,还是输出四个4。
那么这个问题是如何产生和解决的呢?请接着阅读
异步队列执行的时间
执行到异步任务的时候,会直接放到异步队列中吗?
答案是不一定的。
因为浏览器有个定时器(timer)模块,定时器到了执行时间才会把异步任务放到异步队列。
for循环体执行的过程中并没有把setTimeout放到异步队列中,只是交给定时器模块了。4个循环体执行速度非常快(不到1毫秒)。定时器到了设置的时间才会把setTimeout语句放到异步队列中。
即使setTimeout设置的执行时间为0毫秒,也按4毫秒算。
这就解释了上题为什么会连续输出四个4的原因。
HTML5 标准规定了setTimeout()的第二个参数的最小值,即最短间隔,不得低于4毫秒。如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。
利用闭包实现 setTimeout 间歇调用
1
2
3
4
5
6
7
|
for (let i = 0; i < 4; i++) { ( function (j) { setTimeout( function () { console.log(j); }, 1000 * i) })(i); } |
执行后,会隔1s输出一个值,分别是:0 1 2 3
- 此方法巧妙利用IIFE声明即执行的函数表达式来解决闭包造成的问题。
- 将var改为let,使用了ES6语法。
这里也可以用setInterval()方法来实现间歇调用。
详见:setTimeout和setInterval的区别
弹出层插件利用JS中基本类型的参数传递是按值传递的特征实现
1
2
3
4
5
6
7
8
9
|
var output = function (i) { setTimeout( function () { console.log(i); }, 1000 * i) } for (let i = 0; i < 4; i++) { output(i); } |
执行后,会隔1s输出一个值,分别是:0 1 2 3
实现原理:传过去的i值被复制了。
基于Promise的解决方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
const tasks = []; const output = (i) => new Promise((resolve) => { setTimeout(() => { console.log(i); resolve(); }, 1000 * i); }); //生成全部的异步操作 for ( var i = 0; i < 5; i++) { tasks.push(output(i)); } //同步操作完成后,输出最后的i Promise.all(tasks).then(() => { setTimeout(() => { console.log(i); }, 1000) }) |
执行后,会隔1s输出一个值,分别是:0 1 2 3 4 5
优点:提高了代码的可读性。
注意:如果没有处理Promise的reject,会导致错误被丢进黑洞。
使用ES7中的async await特性的解决方案(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { //声明即执行的async for ( var i = 0; i < 5; i++) { await sleep(1000); console.log(i); } await sleep(1000); console.log(i); })(); |
执行后,会隔1s输出一个值,分别是:0 1 2 3 4 5
六、事件循环 Event Loop
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop。
有时候 setTimeout明明写的延时3秒,实际却5,6秒才执行函数,这又是因为什么?
答:setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
浏览器的JS引擎遇到setTimeout,拿走之后不会立即放入异步队列,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。js引擎发现同步队列中没有要执行的东西了,即运行栈空了就从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。
这时setTimeout函数体就变成了运行栈中的执行任务,运行栈空了,再监听异步队列中有没有要执行的任务,如果有就继续执行,如此循环,就叫Event Loop。
七、总结
JavaScript通过事件循环和浏览器各线程协调共同实现异步。同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性。
如何通过setTimeout理解JS运行机制详解的更多相关文章
- js运行机制详解:event loop
总结 阮一峰老师的博客 一.为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程 那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript ...
- 从setTimeout谈js运行机制
众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执行完才能有机会执行,不像人一 ...
- JavaScript运行机制详解
JavaScript运行机制详解 var test = function(){ alert("test"); } var test2 = function(){ alert(& ...
- JS执行机制详解,定时器时间间隔的真正含义
壹 ❀ 引 通过结果倒推过程是我们常用的思考模式,我在上一篇学习promise笔记中,有少量关于promise执行顺序的例子,通过倒推,我成功让自己对于js执行机制的理解一塌糊涂,js事件机制,事件 ...
- JavaScript 运行机制详解:深入理解Event Loop
Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...
- js异步梳理:1.从浏览器的多进程到JS的单线程,理解JS运行机制
大家很早就知道JS是一门单线程的语言.但是也时不时的会看到进程这个词.首先简单区分下线程和进程的概念 1. 简单理解进程 - 进程是一个工厂,工厂有它的独立资源 - 工厂之间相互独立 - 线程是工厂中 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
- javascript运行机制详解: 再谈Event Loop(转)
作者: 阮一峰 日期: 2014年10月 8日 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts ...
- JavaScript 运行机制详解
一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...
随机推荐
- 数字(Number)类型(一)
多行语句 Python 通常是一行写完一条语句,但如果语句很长,我们可以使用反斜杠(\)来实现多行语句,例如: total = item_one + \ item_two + \ item_three ...
- 操作系统下cache的几个概念
Cache是一种容量比较小,但访问速度比较快存储器.由于处理器的速度远高于主存,处理器直接从内存中存取数据要等待一定周期,而Cache位于处理器与主存之间,保存着最近一段时间处理器涉及到的主存块内容. ...
- Python - 关于代码阅读的一些建议
初始能力 让阅读思路保持清晰连贯,主力关注在流程架构和逻辑实现上,不被语法.技巧和业务流程等频繁地阻碍和打断. 建议基本满足以下条件,再开始进行代码阅读: 具备一定的语言基础:熟悉基础语法,常用的函数 ...
- Ajax 提交 数据含特殊字符 出现500错误
Ajax 请求API接口 传输过去的数据 含有特殊字符,Ajax请求失败出现500错误. 假设你要post过去的数据为 :data ==>> {a:'!@!@!@!@#$#{}|> ...
- C# 请求接口返回中文乱码→???
在工作过程中,调用第三方接口出现当返回的数据是中文的时候,中文数据便会变成 这样??? 迷~ ,一开始我以为是发送成功后接收字符编码是不是不对,在换过UTF-8,Unicode,...都是不行. 最后 ...
- TypeError: Cannot red property 'style' of null 错误解决
错误信息如下: JSP代码如下: <c:if test ="${not empty excelErrors}"> <div id="excelError ...
- Android主线程的消息系统(Handler\Looper)
前言: 之前的文章写的都是关于Bitmap和内存的优化技术,这一篇文章给大家谈谈Handler. Handler是Android系统中比较重要的一个知识,在Android多线程面试经常会被问到,在实际 ...
- 《JavaScript总结》深拷贝和浅拷贝
在javascript中,数据主要分基本类型和引用类型两种. 基本类型的赋值比较简单,但是引用类型的赋值,会存在一些问题,那我们用代码来分析一下. 一.浅拷贝 var one = "测试1& ...
- Long类型时间如何转换成视频时长?
数据库中存放的视频时长是一个Long类型的毫秒/秒时间,现在需要把这个时间转换成标准的视频时长格式,在我看来这应该是一个很常用的转化有一个很常用的转换方法工具才对,可是我百度找了许久,没有一个简单直观 ...
- c#调用c++ dll的一个例子
1.创建一个简单的c++ 项目生成动态库. Vs 创建完工程后,打开SampleOperation.cpp文件,看到有个导出函数的例子. 复制这段代码, 修改函数(返回值, 参数列表, 函数的实现) ...