全球实时:setTimeout是什么意思?彻底理解setTimeout
之前在网上看了很多关于setTimeout的文章,但我感觉都只是点到为止,并没有较深入的去剖析,也可能是我脑袋瓜笨,不容易被点解。后面看了《你不知道的javascript-上卷》一书,决定重新再来理一次。这次我觉得我应该整明白了。于是分享给大家,文中解释有错误的部分还希望大家留言指正。
(相关资料图)
首先我们还是来看那道大家再熟悉不过的前端面试题:
for (var i = 1;i <= 5;i ++) { setTimeout(function timer() { console.log(i) },i * 1000)}
我想刚入门的童鞋或者对JS作用域、闭包以及事件循环等概念不了解的童鞋会想当然的认为这道题的答案应该是: 第一次循环,隔一秒输出1; 第二次循环,隔两秒输出2; 第三次循环,隔三秒输出3; 第四次循环,隔四秒输出4; 第五次循环,隔五秒输出5; 或者还有同学预期的结果是分别输出数字1~5,每秒一次,每次一个。
但实际结果大家去控制台打印了都知道:以一秒的频率连续输出五个6! 相信对于很多童鞋第一次看到这个结果是懵的,包括我第一次看到结果是懵逼的!
然而还没等你反应过来,面试官又要求你改动一下代码,要它以一秒的频率分别输出1,2,3,4,5。如果你不了解或者没有深入理解JS中的作用域、闭包以及事件循环,那么就可以和面试官说拜拜了。
这道题涉及到的知识点我上面已经提到过两次,这里我们还是先简单地过一下这些知识点: 1、作用域:这里我引用《你不知道的javascript》中的一个比喻,可以把作用域链想象成一座高楼,第一层代表当前执行作用域,楼的顶层代表全局作用域。我们在查找变量时会先在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上找,以此类推。到达顶层后(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。 2、闭包:我的理解是在传递函数类型的变量时,该函数会保留定义它的所在函数的作用域。读起来可能比较绕,或者可以简单的这么理解,A函数中定义了B函数并且它返回了B函数,那么不管B函数在哪里被调用或如何被调用,它都会保留A函数的作用域。 3、事件循环:这个概念深入起来很复杂,下面新开一个段落只说一些跟本文相关的内容。
说起事件循环,不得不提起任务队列。事件循环只有一个,但任务队列可能有多个,任务队列可分为宏任务(macro-task)和微任务(micro-task)。XHR回调、事件回调(鼠标键盘事件)、setImmediate、setTimeout、setInterval、indexedDB数据库操作等I/O以及UI rendering都属于宏任务(也有文章说UI render不属于宏任务,目前还没有定论),process.nextTick、Promise.then、Object.observer(已经被废弃)、MutationObserver(html5新特性)属于微任务。注意进入到任务队列的是具体的执行任务的函数。比如上述例子setTimeout()中的timer函数。另外不同类型的任务会分别进入到他们所属类型的任务队列,比如所有setTimeout()的回调都会进入到setTimeout任务队列,所有then()回调都会进入到then队列。当前的整体代码我们可以认为是宏任务。事件循环从当前整体代码开始第一次事件循环,然后再执行队列中所有的微任务,当微任务执行完毕之后,事件循环再找到其中一个宏任务队列并执行其中的所有任务,然后再找到一个微任务队列并执行里面的所有任务,就这样一直循环下去。这就是我所理解的事件循环。来,还是看个栗子:
console.log("global")setTimeout(function () { console.log("timeout1") new Promise(function (resolve) { console.log("timeout1_promise") resolve() }).then(function () { console.log("timeout1_then") })},2000)for (var i = 1;i <= 5;i ++) { setTimeout(function() { console.log(i) },i*1000) console.log(i)}new Promise(function (resolve) { console.log("promise1") resolve() }).then(function () { console.log("then1")})setTimeout(function () { console.log("timeout2") new Promise(function (resolve) { console.log("timeout2_promise") resolve() }).then(function () { console.log("timeout2_then") })}, 1000)new Promise(function (resolve) { console.log("promise2") resolve()}).then(function () { console.log("then2")})
我们来一步一步分析以上代码:
1)、首先执行整体代码,“global”会被第一个打印出来。这是第一个输出.
2)、执行到第一个setTimeout时,发现它是宏任务,此时会新建一个setTimeout类型的宏任务队列并派发当前这个setTimeout的回调函数到刚建好的这个宏任务队列中去,并且轮到它执行时要延迟2秒后再执行。
3)、代码继续执行走到for循环,发现是循环5次setTimeout(),那就把这5个setTimeout中的回调函数依次派发到上面新建的setTimeout类型的宏任务队列中去,注意,这5个setTimeout的延迟分别是1到5秒。此时这个setTimeout类型的宏任务队列中应该有6个任务了。再执行for循环里的console.log(i),很简单,直接输出1,2,3,4,5,这是第二个输出。
4)、再执行到new Promise,Promise构造函数中的第一个参数在new的时候会直接执行,因此不会进入任何队列,所以第三个输出是"promise1",上面有说到Promise.then是微任务,那么这里会生成一个Promise.then类型的微任务队列,这里的then回调会被push进这个队列中。
5)、再继续走,执行到第二个setTimeout,发现是宏任务,派发它的回调到上面setTimeout类型的宏任务队列中去。
6)、再走到最后一个new Promise,很明显,这里会有第四个输出:"promise2",然后它的then中的回调也会被派发到上面的Promise.then类型的微任务队列中去。
7)、第一轮事件循环的宏任务执行完成(整体代码可以看做宏任务)。此时微任务队列中只有一个Promise.then类型微任务队列,它里面有两个任务。宏任务队列中也只有一个setTimeout类型的宏任务队列。
8)、下面执行第一轮事件循环的微任务,很明显,会分别打印出"then1",和"then2"。分别是第五和第六个输出。此时第一轮事件循环完成。
9)、开始第二轮事件循环:执行setTimeout类型队列(宏任务队列)中的所有任务。发现都有延时,但延时最短的是for循环中第一次循环push进来的那个setTimeout和上面第5个步骤中的第二个setTimeout,它们都只延时1s。它们会被同时执行,但前者先被push进来,所以先执行它!它的作用就是打印变量i,在当前作用域找变量i,木有!去它上层作用域(这里是全局作用域)找,找到了,但此时的i早已是6了。(为啥不是5,那你得去补补for循环的执行流程了~)所以这里第七个输出是延时1s后打印出6。
10)、紧接着执行第二个setTimeout,它会先后打印出"timeout2"和"timeout2_promise",这分别是第八和第九个输出。但这里发现了then,又把它push到上面已经被执行完的then队列中去。
11)、这里要注意,因为出现了微任务then队列,所以这里会执行该队列中的所有任务(此时只有一个任务),即打印出"timeout2_then"。这是第十个输出。
11)、继续回过头来执行宏任务队列,此时是执行延时为2s的第一个setTimeout和for循环中第二次循环的那个setTimeout,跟上面一样,前者是第一个被push进来的,所以它先执行。这里会延时1秒(原因下面会解释)分别输出“timeout1”和“timeout1_promise”,但发现了里面也有一个then,于是push到then微任务队列并立即执行,输出了"timeout1_then"。紧接着执行for中第二次循环的setTimeout,输出6。注意这三个几乎是同时被打印出来的。他们分别是第十一到十三个输出。
12)、再就很简单了,把省下的for循环中后面三次循环被push进来的setTimeout依次执行,于是每隔1s输出一个6,连续输出3次。
13)、第二轮事件循环结束,全部代码执行完毕。
所以上代码的执行结果为:
global12345promise1promise2then1then2//延迟1s6timeout2timeout2_promisetimeout2_then//延迟1stimeout117 timeout1_promise20 timeout1_then6//每隔1s输出3个6
这里解释下为什么上面第11步不是延迟2秒再输出“timeout1”和“timeout1_promise”,这时需要理解setTimeout()延时参数的意思,这个延迟时间始终是相对主程序执行完毕的那个时间算的 ,并且多个setTimeout执行的先后顺序也是由这个延迟时间决定的。
再回过头来看上面那个问题,理解了事件循环的机制,问题就很简单了。for循环时setTimeout()不是立即执行的,它们的回调被push到了宏任务队列当中,而在执行任务队列里的回调函数时,变量i早已变成了6。那如何得到想要的结果呢?很简单,原理就是需要给循环中的setTimeout()创建一个闭包作用域,让它执行的时候找到的变量i是正确的。
知道了原理,解决方案就很多了,下面给出5种方案,
(1)引入IIFE
for(var i = 0;i<5;i ++) { (function(i){ setTimeout(function timer() { console.log(i) }, i * 1000); })(i);}
(2)利用ES 6引入的let关键字
for(let i = 0;i<5;i++) { setTimeout(function timer(){ console.log(i); }, i * 1000);}
for 循环头部的let 声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
(3)利用ES 5引入的bind函数
for (var i=1; i<=5; i++) { setTimeout( function timer(i) { console.log(i); }.bind(null,i), i*1000 );}
(4)利用setTimeout第三个参数
for (var i=1; i<=5; i++) { setTimeout( function timer(i) { console.log(i); }, i*1000,i );}
注:setTimeout函数第三个参数及以后的参数都可以作为timer函数的参数。
(5)把setTimeout用一个方法单独出来形成闭包
var loop = function (i) { setTimeout(function timer() { console.log(i); }, i*1000);};for (var i = 1;i <= 5; i++) { loop(i);}
好了,文章到此基本就结束了,第一次写技术类的文章,肯定有不足,希望大家能留言指出。最后请尊重我的劳动成果,转载请务必注明出处。
参考:http://www.jianshu.com/p/12b9f73c5a4fhttp://www.jianshu.com/p/e5225ba4a025
作者:jia58960 链接:https://www.jianshu.com/p/3e482748369d 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
标签:
相关推荐:
最新新闻:
- 全球实时:setTimeout是什么意思?彻底理解setTimeout
- 【环球聚看点】蕾哈娜抖音首次晒娃:这表情 确定是亲生的!
- 手游《古惑狼:全速冲锋》将停服 发售不到2年|焦点速递
- 读书郎学生电脑如何下载?读书郎电脑下载步骤
- Ubuntu中snap是什么意思?介绍一些常用命令|焦点播报
- LOL各英雄的原型来源你了解几个? LOL背后的小故事
- 如何在excel中画斜线?在excel中画斜线的方法|天天新消息
- 激光焊接机价格怎么样?激光焊接机价格参考_世界滚动
- 全球滚动:电烤箱烤羊肉串怎么做?电烤箱烤羊肉串做法步骤
- sd卡数据恢复软件有哪些?sd卡数据恢复的软件
- 手机多媒体没声音是怎么回事?手机多媒体没声音怎么修?
- ISO9000和ISO9001有什么特点?ISO9000和ISO9001作用详解_环球头条
- 抖音可以查访客记录吗?抖音访客记录怎么看?
- 当前焦点!DOTA2前EG队长Fly求婚成功 晒出甜蜜合影
- 小键盘指法怎么操作?小键盘指法练习技巧|当前快播
- Windows 11壁纸有新玩法 用户可以自己画 焦点简讯
- 手机闪存卡哪款好?经典手机闪存卡有哪些推荐?|信息
- 电阻式触摸屏好用吗?电阻式触摸屏工作原理|全球信息
- 人生中的第一个Java程序:HelloWorld:天天百事通
- 爱宁牌电饼铛怎样?爱宁牌电饼铛的优势介绍
- vice versa是什么意思?vice versa通常翻译 世界快播
- 《冷漠 鸣神学园的七大不可思议》大型DLC12月23日上线
- 视讯!文件删除了怎么恢复?文件删除了三种恢复方法
- 联想服务器linux系统raid驱动 IntelRAID 6.12版RAID卡驱动官方正式版下载
- 全球快看点丨这款国产操作系统界面竟与Windows 11如出一辙
- 【环球快播报】除了币安,币圈最危险的大麻烦:稳定币USDT
- 浏览器市场占有率排行表 2020年8月国内浏览器排行
- GSC将推出上坂堇可动手办:声优首次figma化!
- 笔记本电脑品牌有哪些推荐?五款热门品牌推荐-焦点滚动
- 英特尔发力 游戏本将迎来24核心处理器-即时看
- 焦点速看:有哪些好看的电影推荐?吐血推荐250部必看电影
- 看热讯:华为荣耀4C详细评测 再次刷新安卓手机性价比
- 节能灯具有哪些品牌?节能灯具品牌介绍
- 苹果手机助手哪个好?推荐几款好用的苹果手机助手:世界今热点
- 磊科升级版NW360怎么样?磊科升级版NW360详细介绍-动态焦点
- RX 7900系列开卖被秒光 诸多用户回归N卡阵营:环球速看料
- 如何打开关闭Excel2007/2010兼容性检查器?关闭技巧:全球要闻
- 炉石传说美服天梯第一的冠军贼卡组分享 详细介绍 当前热点
- hr是什么意思?管理的五大原则|全球聚看点
- 我们为什么要上学?奥巴马开学演讲稿:全球今热点
- 全球微资讯!咸鱼Maya笔记 Maya界面是怎么组成的?
- E. Border是什么?拓展欧几里得+mod分析
- 安卓怎么开启启动模式?Android四种启动模式_环球速读
- 520还在画玫瑰?教你用MATLAB画个玫瑰花球|实时焦点
- 我的世界android制作教程 我的世界怎么去月球?
- AssemblyInfo.cs文件的作用是什么?AssemblyInfo.cs文件详情
- 南阳五中2021年高考成绩查询时间 南阳市五中举行2021年春期开学典礼
- 天天热推荐:Windows 11整个桌面将可做白板使用?
- APP(ios、Android)实现充值的方案 ios中充值功能的2种方案
- Cubase延音踏板怎么设置?Cubase延音踏板设置延音效果 最新消息
- 世界热点!计算机拨号连接无法建立连接怎么办?电信拨号上网连接不上的解决方法
- E. Border是什么?拓展欧几里得+mod分析
- 520还在画玫瑰?教你用MATLAB画个玫瑰花球|实时焦点
- 热门看点:币安将以10.22亿美元的价格收购加密货币借贷平台Voyager的资产
- IPO大潮退去,美股繁盛时期上市的大批股票现在面临退市风险
- 卡梅隆封神往事:一个天才疯子和《阿凡达》的20年_世界实时
- 神谷英树将《猎天使魔女:起源》比作童心绘本-全球播报
- Steam喜加一:圣诞主题像素排球游戏《Jollyball》
- 《碟中谍7》官方幕后花絮:阿汤哥太拼 3000米飞车跳崖|全球快播报
- 联想USB 3.0扩展坞29元限时秒:4个USB接口 支持Type-C供电-世界热点
- 苹果新一代显示器来了:屏幕升级为mini LED
- NVIDIA CES新品发布会官宣:RTX 4070 Ti、RTX 40笔记本显卡要来了:热门看点
- 性价比还得看AMD 6核锐龙+显卡+主板套装1239元:当前要闻
- 环球快消息!ZOL科技早餐:骁龙8 Gen2新机2999元,魅族19外观将揭晓
- 《水浒风云传》确定12月22日登陆Xbox和Switch 天天观速讯
- 【天天新视野】《原神》剧情视频「秋津羽戏」讲述人与妖的友情
- 高手用虚幻5做出《刺客信条》粉丝最期待的游戏功能
- 96核心192线程!AMD Zen4线程撕裂者7000将至 当前速递
- 冬日特别福利!即刻上手领取你的穿越火线限定礼包吧:今日热讯
- 环球时讯:CES 2023 多家电脑厂商预热新款大屏游戏本
- 三强联手:Steam与苹果正式加盟特斯拉_天天短讯
- 马斯克狂卖特斯拉股票:三天套现250+亿人民币
- 姆巴佩决赛失利首度发声:我们会回来的! 每日观点
- 【世界独家】艰难的阿根廷,比梅西更需要这座大力神杯
- 美国国防部:没有任何证据显示UFO与外星人有关 今日讯
- Epic喜加一:《小马格斗》免费领取
- 【天天热闻】《装甲核心6:境界天火》不会包含战役合作模式
- 《模拟人生4》玩家合计游戏时长达14亿小时_环球讯息
- ARPG《致命躯壳:完整版》推出Switch版|当前视讯
- Epic Games支付5.2亿美元以解决侵犯儿童隐私问题
- 【爆料】英特尔 i5-13400 评测流出:6 大核 4 小核,性能接近 i5-12600K
- 每日讯息!旺旺因虚假广告被罚:宣传称富含蛋白质实际不达标
- 这一百多年前的病,喜来乐也治不好|环球实时
- 手工耿自制多功能硬核鱼竿:外形似大狙 还能放风筝 天天时讯
- 《英雄传说:创之轨迹》明年7月7日登陆欧美 新预告公开:动态