闭包应用之延迟函数setTimeout

根据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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

并行编程2——多核体系架构

1.1 多核处理器定义 多内核处理器架构是指&#xff1a;芯片设计工程师在单个处理器中集成两个或多个 “执行内核&#xff08;即计算引擎&#xff09;”。多内核处理器可直接插入到单一处理器基座中。但是&#xff0c;操作系统会把它的每个执行内核作为独立的逻辑处理器&#x…

21:苹果和虫子2

团队QQ&#xff1a;466373640个人博客&#xff1a;www.doubleq.winc/noi/信息学奥数博客&#xff1a;http://www.cnblogs.com/zwfymqz 1:苹果和虫子2 查看提交统计提问总时间限制:1000ms内存限制:65536kB描述你买了一箱n个苹果&#xff0c;很不幸的是买完时箱子里混进了一条虫子…

php运行代码运行退出为0,php – Selenium测试用例返回进程以退出代码0结束

你使用“phpunit yourTestCase.php”而不是“php yourTestCase.php”吗&#xff1f;我使用phpunit(3.5.14)和“selenium-server-standalone-2.0rc2.jar”运行你的testfile,没有问题(除了测试本身失败)&#xff1a;PHPUnit 3.5.14 by Sebastian Bergmann.ETime: 10 seconds, Mem…

Xcode6中使用initWithTitle:title image:image selectedImage:自定义图片

使用xcode6来运行项目&#xff0c;发现使用原生的tabbar上的图片不显示了。这个问题是因为xcode6中的一些api方法被废弃了,同时tabbar上图片的渲染方式发生了改变。先看xcode6中的tabbar api方法的变更&#xff1a;- (void)setFinishedSelectedImage:(UIImage *)selectedImage …

[Node.js]get/post请求

摘要 在很多情况下&#xff0c;我们的web服务器都需要接受客户端浏览器传递的参数或者数据。最常见的是get和post请求。 获取get请求的内容 get请求传递的参数在url中&#xff0c;参数部分在?后面。因此可以手动解析后面的内容作为get请求的参数。node.js中url模块中的parse函…

MyEclipse10 Tomcat7 JDK1.7 配置

第一步.MyEclipse10 Tomcat7 JDK1.7下载 MyEclipse10http://downloads.myeclipseide.com/downloads/products/eworkbench/indigo/installers/myeclipse-10.0-offline-installer-windows.exe Tomcat http://tomcat.apache.org/ Java SE Development Kit 7 WINDOWS版 http://www…

类的静态成量变量必须初始化

因为类的静态成员变量是所有实例共用的.所以得在类外初始化. 调用的时候可以通过对象调用,也可以通过类直接调用 classA { public: inti; //有默认值}; classB { public: staticintn;staticA Aobj;}; intB::n 1; //静态成员变量的初始化A B::Aobj; //静态…

2阶节IIR算法C语言源码

纯C语言软件算法&#xff0c;没有做过多优化&#xff0c;只是实现了基本IIR算法 /****************************************************************************** * 二阶IIR滤波器单元,采用直接II型 * 由多个2阶节&#xff0c;可以组成更多高阶的滤波器 * 根据参数的不同&a…

HDU 3709 Balanced Number (数位DP)

题意 求出[x, y] 范围内的平衡数&#xff0c;平衡数定义为&#xff1a;以数中某个位为轴心&#xff0c;两边的数的偏移量为矩&#xff0c;数位权重&#xff0c;使得整个数平衡。 思路 外层枚举平衡点&#xff0c;然后数位DP即可。设计状态&#xff1a; dp[pos][o][left_right] …

跳過 Windows RT的UI

RT启动进入常规桌面 微软Surface RT发布的时间已经不短了&#xff0c;相信很多朋友都已经熟悉了这个全新的平板&#xff0c;并且已经上手。Surface RT开机默认进入的界面为Windows UI&#xff0c;这对于经常使用App的朋友来说并没有什么&#xff0c;但是对于那些经常使用Office…

matlab调用mstg,实验五 双线性变换法设计IIR数字滤波器

实验五 IIR 数字滤波器设计一、实验目的(1)熟悉用双线性变换法设计IIR 数字滤波器的原理与方法&#xff1b;(2)学会调用MATLAB 信号处理工具箱中滤波器设计函数设计各种IIR 数字滤波器&#xff0c;学会根据滤波需求确定滤波器指标参数。(3)掌握IIR 数字滤波器的MATLAB 实现方法…

Android知识点剖析系列:深入了解layout_weight属性

前言 Android中layout_weight这个属性对于经常捣鼓UI的我们来说&#xff0c;肯定不会陌生。但是我们在真正使用这个属性时&#xff0c;经常会出现一些莫名奇妙的布局效果&#xff1b;如果仅仅知其然而不知其所以然&#xff0c;一些意外的布局效果一定让我们颇为头疼。在本文中&…

C++ 中explicit的使用

C提供了关键字explicit&#xff0c;可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。 C中&#xff0c; 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)&#xff0c; 承担了两个角色。1…

BZOJ 1026 windy数 (数位DP)

题意 区间[A,B]上&#xff0c;总共有多少个不含前导零且相邻两个数字之差至少为2的正整数&#xff1f; 思路 状态设计非常简单&#xff0c;只需要pos、limit和一个前驱数pre就可以了&#xff0c;每次枚举当前位时判断是否与上一位相差2即可。一个需要注意的地方是第一位不用比较…

oracle诊断,Oracle 诊断事件列表

Oracle 诊断事件列表(2013-03-26 18:05:26)标签&#xff1a;oracle诊断事件itORA-10000: controlfile debug event, name control_fileORA-10001: controlfile crash event1ORA-10002: controlfile crash event2ORA-10003: controlfile crash event3ORA-10004: controlfile cra…

考研数学:【以错补错】 降低做题出错率

考研数学&#xff1a;以错补错 降低做题出错率  众所周知&#xff0c;数学需要做题&#xff0c;需要通过做题来巩固掌握&#xff0c;但很多同学却陷入了题海战术&#xff0c;把所有的精力都放在数学练习上&#xff0c;一门心思做题&#xff0c;可几个月下来却没有进展&#x…

treeview右键添加新节点

private void advTree1_MouseDown(object sender, MouseEventArgs e){if (e.Button MouseButtons.Right)//判断你点的是不是右键{Point ClickPoint new Point(e.X, e.Y);Node CurrentNode advTree1.GetNodeAt(ClickPoint);if (CurrentNode ! null)//判断你点的是不是一个节点…

RPM方式安装MySQL5.6

RPM方式安装MySQL5.6 rpm -ivh MySQL-server-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-client-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-devel-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-embedded-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -iv…

centos7静默搭建oracle11g,Linux静默安装Oracle方法(centos7+oracle11g)

1、 增加虚拟内存ddif/dev/zero of/swapadd bs1024 count2006424mkswap /swapaddswapon /swapadd2、 检查依赖包rpm -q binutils compat-libstdc-33 elfutils-libelf elfutils-libelf-devel gcc gcc-c glibc-2.5 glibc-common glibc-devel glibc-headers ksh libaio libaio-dev…

Ms SQL Server 约束和规则

一、SQL约束 约束定义关于列中允许值的规则&#xff0c;是强制完整性的标准机制。 使用约束优先于使用触发器、规则和默认值。查询优化器也使用约束定义生成高性能的查询执行计划。 1&#xff1a;类型 约束的类型一共分三种 域约束&#xff1a; 涉及一个或多个列&#xf…