一、背景

最近在翻看以前的老书《node.js开发指南》,恰好碰到 for 循环 + setTimeout 的经典例子,于是重新梳理了思路并记录下。

二、写在前面,setTimeout 和 setInterval 的执行机制

在日常编码中,你会发现,给 setTimeout 和 setInterval 设定延迟时间往往并不准,或者干脆 setTimeout(function(){xxx},0) 也不是立马执行(特别是有耗时代码在前),这是因为 js 是单线程的,有一个事件队列机制,setTimeout 和 setInterval 的回调会到了延迟时间塞入事件队列中,排队执行。

setTimeout :延时 delay 毫秒之后,啥也不管,直接将回调函数加入事件队列。
setInterval :延时 delay 毫秒之后,先看看事件队列中是否存在还没有执行的回调函数( setInterval 的回调函数),如果存在,就不要再往事件队列里加入回调函数了。

看下面示例:

for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

结果:1 秒之后,同时输出 5 个 5。

因为 for 循环会先执行完(同步优先于异步优先于回调),这时五个 setTimeout 的回调全部塞入了事件队列中,然后 1 秒后一起执行了。

三、正文

接下来就是那道经典的代码:

for (var i = 0; i < 5; i++) {
setTimeout(function (){
console.log(i);
},1000);
}

结果:5 5 5 5 5

为什么不是 1 2 3 4 5,问题出在作用域上。

因为 setTimeout 的 console.log(i); 的i是 var 定义的,所以是函数级的作用域,不属于 for 循环体,属于 global。等到 for 循环结束,i 已经等于 5 了,这个时候再执行 setTimeout 的五个回调函数(参考上面对事件机制的阐述),里面的 console.log(i); 的 i 去向上找作用域,只能找到 global下 的 i,即 5。所以输出都是 5。

解决办法:人为给 console.log(i); 创造作用域,保存i的值。

解决办法一

for (var i = 0; i < 5; i++) {
(function(i){ //立刻执行函数
setTimeout(function (){
console.log(i);
},1000);
})(i);
}

这里用到立刻执行函数。这样 console.log(i); 中的i就保存在每一次循环生成的立刻执行函数中的作用域里了。

解决办法二

for (let i = 0; i < 5; i++) {     //let 代替 var
setTimeout(function (){
console.log(i);
},1000);
}

let 为代码块的作用域,所以每一次 for 循环,console.log(i); 都引用到 for 代码块作用域下的i,因为这样被引用,所以 for 循环结束后,这些作用域在 setTimeout 未执行前都不会被释放。

四、补充

在写示例代码的过程中,发现一个语法点:

 function a(i){
console.log(i);
} for (var i = 0; i < 5; i++) {
setTimeout(a(i),1000);
}

报错:

TypeError: "callback" argument must be a function
at setTimeout (timers.js:421:11)
……

百度了下,原来 setTimeout 不支持传带参数的函数,有两种解决方案:

(1)匿名函数包装

function a(i){
console.log(i);
} for (var i = 0; i < 5; i++) {
setTimeout(function(){ //用匿名函数包装
a(i);
},1000);
}

(2)setTimeout 的第 3+ 个参数

setTimeout(func, delay, param1, param2, ...)
第三个参数及以后的参数都可以作为 func 函数的参数

function a(i){
console.log(i);
} for (var i = 0; i < 5; i++) {
setTimeout(a,1000,i); //传入第3个参数
}
 参考:https://www.cnblogs.com/xjnotxj/p/7452698.html
 

for + setTimeout的更多相关文章

  1. setTimeout 的黑魔法

    setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字--定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次 ...

  2. 你所不知道的setTimeout

    JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成.它们向任务队列添加定时任务.初始接触它的人都觉得好简单 ...

  3. setTimeout那些事儿

    一.setTimeout那些事儿之单线程 一直以来,大家都在说Javascript是单线程,浏览器无论在什么时候,都且只有一个线程在运行JavaScript程序. 但是,不知道大家有疑问没——就是我们 ...

  4. 深入理解定时器系列第一篇——理解setTimeout和setInterval

    × 目录 [1]setTimeout [2]setInterval [3]运行机制[4]作用[5]应用 前面的话 很长时间以来,定时器一直是javascript动画的核心技术.但是,关于定时器,人们通 ...

  5. 前端开发:setTimeout与setInterval 定时器与异步循环数组

    前端开发:setTimeout与setInterval 定时器与异步循环数组 前言: 开通博客园三个月以来,随笔记录了工作中遇到的大大小小的难题,也看过无数篇令人启发的文章,我觉得这样的环境是极好的, ...

  6. javascript函数setInterval和setTimeout的使用区别详解

    setTimeout和setInterval的使用 这两个方法都可以用来实现在一个固定时间段之后去执行JavaScript.不过两者各有各的应用场景. 方 法 实际上,setTimeout和setIn ...

  7. UNITY实现FLASH中的setTimeout

    setTimeout是一个很方便的DELAY处理方法 if (this.startUpDelay > 0){            StartCoroutine(DelayedStart()); ...

  8. setTimeout和setInterval从入门到精通

    我们在日常web前端开发中,经常需要用到定时器方法. 前端中的定时器方法是浏览器提供的,并不是ECMAScript规范中的.是window对象的方法. 浏览器中的定时器有两种, 一种是每间隔一定时间执 ...

  9. setTimeout和setInterval定时器使用详解测试

    var len=4; while(len--){ var time=setTimeout(function(){ console.log(len); },0); console.log(time); ...

  10. setTimeout 和 throttle 那些事儿

    document.querySelector('#settimeout').onclick= function () { setTimeout(function () { console.log('t ...

随机推荐

  1. MySQL的启动和关闭

    1.Windows下 启动服务 mysqld --console 或 net start mysql 关闭服务 mysqladmin -uroot shudown 或 net stop mysql 2 ...

  2. JavaWeb--简单分页技术

    分页需要的技术点:1.前台分页标签的使用 2.前台上一页,下一页显示的业务逻辑 3.MSQL用到的语句  limit 4.封装pageBean对象 这个是PageBean用到的 分页公式: int t ...

  3. Shiro学习笔记四(Shiro集成WEB)

    这两天由于家里出了点事情,没有准时的进行学习.今天补上之前的笔记 -----没有学不会的技术,只有不停找借口的人 学习到的知识点: 1.Shiro 集成WEB 2.基于角色的权限控制 3.基于权限的控 ...

  4. POJ 3278 Catch That Cow(赶牛行动)

    POJ 3278 Catch That Cow(赶牛行动) Time Limit: 1000MS    Memory Limit: 65536K Description - 题目描述 Farmer J ...

  5. PHP 内置函数fgets读取文件

    php fgets()函数从文件指针中读取一行 语法: fgets(file,length) 参数 描述 file  必需.规定尧要读取的文件 length 可选 .规定尧都区的字节数.默认是102字 ...

  6. mysql行转列(多行转一列)

    场景 比如说一个订单对应多条数据,当状态(status)=1的时候,  数量(num)=25,当状态(status)=2的时候,  数量(num)=45,现在想用一条sql记录下不同状态对应的数量为多 ...

  7. _ai_creature

  8. _pvp_killed_loot

    该表控制玩家被击杀时掉落物品,包括角色身上装备,背包物品,银行物品 comment 备注 entry 掉落的物品ID lootCount 掉落的物品数量 chance 掉落的几率,例如50,则50%几 ...

  9. 【BZOJ】3209: 花神的数论题

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3209 显然是按照二进制位进行DP. 考虑预处理$F[i][j]$表示到了二进制的第$i$位 ...

  10. 【三十二】thinkphp之连接数据库、实例化模型

    1.连接数据库 Thinlphp内置了抽象数据库访问层,把不同的数据操作封装起来.我们只需要调用公共的DB类进行操作即可.DB类会自动调用相应的数据库驱动来处理. 在应用目录/common/conf/ ...