根据HTML 5标准,setTimeout推迟执行的时间,最少是5毫秒。如果小于这个值,会被自动增加到5ms。

每一个setTimeout在执行时,会返回一个唯一ID,把该ID保存在一个变量中,并传入clearTimeout,可以清除定时器。

在setTimeout内部,this绑定采用默认绑定规则,也就是说,在非严格模式下,this会指向window;而在严格模式下,this指向undefined。

setTimeout不止有2个参数,第一个参数是回调函数,第二个参数是时间,第三个参数以后都是第一个回调函数的参数。

一、用setTimeout代替setInterval

由于setInterval间歇调用定时器会因为在定时器代码未执行完毕时又向任务队列中添加定时器代码,导致某些间隔被跳过等问题,所以应使用setTimeout代替setInterval。

setTimeout(function myTimer() {/*** 需要执行的代码* setTimeout会等到定时器代码执行完毕后才会重新调用自身(递归),记得给匿名函数添加一个函数名,以便调用自身。*/setTimeout(myTimer, 1000);
}, 1000);

这样做的好处是,在前一个定时器执行完毕之前,不会向任务队列中插入新的定时器代码,可以避免任何缺失的间隔,还可以保证在下一次定时器代码执行前,至少要等待指定的间隔,避免了连续执行。这个模式主要用于重复定时器。

// 代码段1,间歇性输出1到10
let num = 0;
let max = 10;
setTimeout(function myTimer() {num++;console.log(num);if (num === max) {return;}setTimeout(myTimer, 500);
}, 500);

// 代码段2,间歇性输出1到10
setTimeout(function myTimer() {num++;console.log(num);if (num < max) {setTimeout(myTimer, 500);}
}, 500);

二、在for循环中创建setTimeout定时器

1、根据事件循环和任务队列的原理,定时器通常在循环结束后才会加入到任务队列执行。

2、定时器是循环创建的。

3、定时器几乎是同时开始计时的。

4、定时器中的回调函数属于闭包,包含着对循环后全局变量i的引用。在块作用域和定时器外创建一个函数作用域时,此时不会查找全局作用域。

5、定时器的第二个参数不属于闭包的一部分,其值与循环i的值相同。

程序运行遵循同步优先异步靠边回调垫底

// 代码段1,输出6个5
for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, 1000 * i);
}
console.log(i);

第1个5直接输出,1 秒之后,输出 5 个 5,并且每隔1s输出一个,一共用时4s。

for循环和循环体外部的console是同步的,所以先执行for循环,再执行外部的console.log。等for循环执行完,就会给setTimeout传参,最后执行。

JavaScript单线程如何处理回调呢?JavaScript同步的代码是在堆栈中顺序执行的,而setTimeout回调会先放到消息队列,for循环每执行一次,就会放一个setTimeout到消息队列排队等候,当同步的代码执行完了,再去调用消息队列的回调方法。这个消息队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由setTimeout定义的操作。而这些操作进入队列的顺序,则由设定的延迟时间来决定,消息队列遵循先进先出(FIFO)原则。因此,即使我们将延迟时间设置为0,它定义的操作仍然需要等待所有代码执行完毕后才开始执行。这里的延迟时间,并非相对于setTimeout执行这一刻,而是相对于其他代码执行完毕这一刻。

先执行for循环,按顺序放了5个setTimeout回调到消息队列,然后for循环结束,下面还有一个同步的console,执行完console之后,堆栈中已经没有同步的代码了,就去消息队列找,发现找到了5个setTimeout,注意setTimeout是有顺序的。

JavaScript在把setTimeout放到消息队列的过程中,循环的i是不会及时保存进去的,相当于你写了一个异步的方法,但是ajax的结果还没返回,只能等到返回之后才能传参到异步函数中。

for循环结束之后,因为i是用var定义的,所以var是全局变量(这里没有函数,如果有就是函数内部的变量),这个时候的i是5,从外部的console输出结果就可以知道。那么当执行setTimeout的时候,由于全局变量的i已经是5了,所以传入setTimeout中的每个参数都是5。很多人都会以为setTimeout里面的i是for循环过程中的i,这种理解是不对的。

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

立刻输出0 1 2 3 4

间歇性输出5个5

温馨提示:如果在开发者工具console面板运行这段程序,你会看到不一样的结果。
立刻输出0 1 2 3 4
立即输出定时器ID
间歇性输出5个5

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

立即输出0 1 2 3 4。因为setTimeout的第一个参数是函数或者字符串,而此时函数又立即执行了。因此,定时器失效,直接输出0 1 2 3 4。

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

该程序也是立即输出0 1 2 3 4。

三、如何让程序间歇性输出0 1 2 3 4呢?

这里有两种思路,不过原理都相同。

思路1:ES6 let关键字,给setTimeout定时器外层创建一个块作用域。

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

思路1的另一种表达

for (var i = 0; i < 5; i++) {let j = i;  //闭包的块作用域setTimeout(function() {console.log(j);}, 1000 * j);
}

思路2:IIFE,创建函数作用域以形成闭包。

Immediately Invoked Function Expression:声明即执行的函数表达式。

for (var i = 0; i < 5; i++) {(function iife(j) {     //闭包的函数作用域setTimeout(function() {console.log(j);}, 1000 * i);   //这里将i换为j, 可以证明以上的想法。
  })(i);
}

给定时器外层创建了一个IIFE,并且传入变量i。此时,setTimeout会形成一个闭包,记住并且可以访问所在的词法作用域。因此,会间歇输出0 1 2 3 4。

实际上,函数参数,就相当于函数内部定义的局部变量,因此下面的写法也是可以的,思路2的另一种表达。

for (var i = 0; i < 5; i++) {(function iife() {var j = i;setTimeout(function() {console.log(j);}, 1000 * i);   //如果这里将i换为j, 可以证明以上的想法。
  })();
}

思路3

for (var i = 0; i < 5; i++) {setTimeout(function(j) {return function(){console.log('index is ',j);} }(i), 1000 * i);   //如果这里将i换为j, 可以证明以上的想法。
}

思路4

var myTimer = function (i) {setTimeout(function() {console.log(i);}, 1000);
};
for (var i = 0; i < 5; i++) {myTimer(i);  //这里传过去的i值被复制了
}
console.log(i);//5

代码执行时,立即输出5,之后每隔1秒依次立刻输出0 1 2 3 4。

四、如何让程序间歇性输出0 1 2 3 4 5呢?

思路1

for (var i = 0; i < 5; i++) {(function(j) {setTimeout(function() {console.log( j);}, 1000 * j);  //这里修改0~4的定时器时间
  })(i);
}
setTimeout(function() { //这里增加定时器,超时设置为5秒
  console.log(i);
}, 1000 * i);

我们都知道使用Promise处理异步代码比回调机制让代码可读性更高,但是使用Promise的问题也很明显,即如果没有处理Promise的reject,会导致错误被丢进黑洞,好在新版的Chrome和Node 7.x 能对未处理的异常给出Unhandled Rejection Warning,而排查这些错误还需要一些特别的技巧(浏览器、Node.js)

思路2

const myArr = [];
for (var i = 0; i < 5; i++) {   // 这里i的声明不能改成let,如果要改该怎么做?((j) => {myArr.push(new Promise((resolve) => {setTimeout(() => {console.log(new Date, j);resolve();  //这里一定要resolve,否则代码不会按预期执行}, 1000 * j); //定时器的超时时间逐步增加
    }));})(i);
}Promise.all(myArr).then(() => {setTimeout(() => {console.log(new Date, i);}, 1000);   // 注意这里只需要把超时设置为1秒
});

思路3

const myArr = []; //这里存放异步操作的Promise
const myTimer = (i) => new Promise((resolve) => {setTimeout(() => {console.log(new Date, i);resolve();}, 1000 * i);
});
// 生成全部的异步操作
for (var i = 0; i < 5; i++) {myArr.push(myTimer(i));
}
// 异步操作完成之后,输出最后的 i
Promise.all(myArr).then(() => {setTimeout(() => {console.log(new Date, i);}, 1000);
});

思路4:使用ES7中的async await特性

// 模拟其他语言中的sleep,实际上可以是任何异步操作。
const sleep = (timeountMS) => new Promise((resolve) => {setTimeout(resolve, timeountMS);
});
(async () => {  //声明即执行的async函数表达式for (var i = 0; i < 5; i++) {await sleep(1000);console.log(new Date, i);}await sleep(1000);console.log(new Date, i);
})();

五、清除定时器

function fn1(){for(var i = 0;i < 5; i++){var tc = setTimeout(function(i){console.log(i);clearTimeout(tc);},10,i);}
}
fn1();//0 1 2 3

解读fn1,这个tc是定义在闭包外面的,也就是说tc并没有被闭包保存,所以这里的tc指的是最后一个循环留下来的tc,所以最后一个4被清除了,没有输出。

function fn2(){for(var i = 0;i < 5; i++){var tc = setInterval(function(i,tc){console.log(i);clearInterval(tc);},10,i,tc);}
}
fn2();//0 1 2 3 4 4 4 4

解读fn2,可以发现最后一个定时器没被删除。在浏览器中单步调试,在第一次循环的时候tc并没有被赋值,所以是undefined,在第二次循环的时候,定时器其实清理的是上一个循环的定时器。所以导致每次循环都是清理上一次的定时器,而最后一次循环的定时器没被清理,导致一直输出4。

六、阅读下列程序,说出运行结果顺序。

let a = new Promise(function(resolve, reject) {console.log(1);setTimeout(() => console.log(2), 0);console.log(3);console.log(4);resolve(true);}
);
a.then(v => {console.log(8);
});
let b = new Promise(function() {console.log(5);setTimeout(() => console.log(6), 0);}
)
console.log(7);

输出结果:1 3 4 5 7 8 2 6。

程序结果分析如下:

1、a变量是一个Promise,Promise本身是同步的,Promise的then()和catch()方法是异步的,所以这里先执行a变量内部的Promise同步代码,输出1 3 4。(同步优先)至于setTimeout回调,先去消息队列排队等着吧。(回调垫底)执行resolve(true),进入then(),then是异步,下面还有同步没执行呢,所以then也去消息队列排队等候吧。(异步靠边)

2、b变量也是一个Promise,和a一样,执行内部的同步代码,输出5,setTimeout滚去消息队列排队等候。

3、最下面同步输出7。

4、同步的代码执行完了,JavaScript就跑去消息队列呼叫异步的代码。这里只有一个异步then,所以输出8。

5、异步执行结束,终于轮到回调啦。这里有2个回调在排队,他们的时间都设置为0,所以不受时间影响,只跟排队先后顺序有关。这时,先输出a里面的回调2,最后输出b里面的回调6。

我们还可以稍微做一点修改,把a里面Promise的 setTimeout(() => console.log(2), 0)改成 setTimeout(() => console.log(2), 2),对,时间改成了2ms,为什么不改成1试试呢?1ms的话,浏览器都还没有反应过来呢。你改成大于或等于2的数字就能看到2个setTimeout的输出顺序发生了变化。所以回调函数正常情况下是在消息队列顺序执行的,但是使用setTimeout的时候,还需要注意时间的大小也会改变它的顺序。

转载于:https://www.cnblogs.com/camille666/p/js_setTimeout.html

闭包应用之延迟函数setTimeout相关推荐

  1. 【夯实基础】《JavaScript设计模式与开发实践》笔记——闭包和高阶函数

    虽然 JavaScript 是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言的特性. 函数式语言的鼻祖是 LISP,JavaScript 在设计之初参考了 LISP 两大方言之一的 ...

  2. 【Groovy】闭包 Closure ( 闭包参数绑定 | curry 函数 | rcurry 函数 | ncurry 函数 | 代码示例 )

    文章目录 一.闭包参数绑定 1.闭包参数绑定 curry 函数 2.闭包参数绑定 rcurry 函数 3.闭包参数绑定 ncurry 函数 二.完整代码示例 一.闭包参数绑定 闭包 Closure 提 ...

  3. 面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么

    大家好,我是若川.最近组织了源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列> ...

  4. python函数闭包和递归_python函数基础3--闭包 + 递归 + 函数回调

    一.闭包 1. 函数嵌套 defouter():print("外层函数")definner():print("内层函数")returninner() outer ...

  5. 可延迟函数、内核微线程以及工作队列

    本文研究多个用于在内核环境当中延迟处理的方法(特别是在 Linux 内核版本 2.6.27.14 当中). 尽管这些方法针对 Linux 内核,但方法背后的理念, 对于系统架构研究具有更广泛的意义.例 ...

  6. 深入理解JavaScript定时函数setTimeout

    以一段javascipt代码为例分析说明 1 function test() { 2 var a = 0; 3 a = 1; 4 setTimeout(function() { 5 alert(a); ...

  7. 对于javaScript闭包,立即执行函数的用法的一些理解

    最近在刷freeCodeCamp上面的题目,发现了这么一道有趣的题目,加深了我对于js中闭包和立即执行表达式的一些理解,题目如下: 给一个正整数num,返回小于或等于num的斐波纳契奇数之和. 斐波纳 ...

  8. linux msleep 头文件,Linux延迟函数

    从Linux应用层和内核两方面来区分下延迟函数: 1. 应用层 1> sleep usleep 微秒级 头文件为: 3> nanosleep 相比标准UNIX 的sleep 调用具有更高高 ...

  9. js 闭包函数 构造函数_JavaScript中的闭包,库里函数和酷抽象

    js 闭包函数 构造函数 In this article, we will talk about closures and curried functions and we'll play aroun ...

最新文章

  1. Beta 冲刺(6/7)
  2. 把一个dataset的表放在另一个dataset里面_使用中文维基百科语料库训练一个word2vec模型并使用说明...
  3. Faster R-CNN论文及源码解读
  4. 重写Gallery中方法去处理Gallery滑动时的惯性
  5. P4457-[BJOI2018]治疗之雨【期望dp,高斯消元】
  6. 量化感知训练_如何评估训练质量?常被训练者忽视的内部负荷
  7. 领导让我重写测试代码,我该怎么办?
  8. 二、RabbitMQ常用交换器
  9. java交接文档_如何写好交接文档
  10. . mindoc linux amd64,搭建minDoc文件接口服务器
  11. 微信小程序-detail详情页静态页面搭建
  12. 正弦波形多波形叠加的音频文件生成工具v1.0使用说明
  13. USB-PPI数据电缆驱动
  14. 北航计算机学院王静远,北京航空航天大学计算机学院导师教师师资介绍简介-李帅...
  15. leaflet地图原理_leaflet绘制区域(仿高德地图效果)
  16. 招聘渠道超全汇总,最适合你的是哪一类?
  17. 关于IP网络号和主机号的原理
  18. 基于51单片机的硬币电子秤的实现
  19. 压力测试概念及方法(TPS/并发量)
  20. python info函数的使用方法_sysinfo函数使用方法

热门文章

  1. 浓缩摘要_浓缩咖啡的收益递减
  2. Java字节码反编译工具
  3. mysql 图片 格式_mysql存储图片 用什么格式
  4. java随机星星怎么闪_canvas画随机闪烁的星星
  5. python程序如何封装成接口_python接口自动化如何封装获取常量的类
  6. c++ ftp服务端_FTP客户端软件介绍及使用
  7. 5.Lambert光照Diffuse Shader
  8. 李宏毅机器学习课程5~~~分类:逻辑回归
  9. Ubuntu15.04 64位安装Theano(已经测试可执行)
  10. 对vue虚拟dom的研究