1.setTimeout

setTimeout是异步执行的,堆栈中碰到setTimeout会交给浏览器内核处理,等待setTimeout达到触发条件(即设定的时间),再返回给执行队列。简而言之,就是计时的这个操作是在浏览器端进行的,在计时完成后,将settimeout中的操作放入事件队列中

例一:

setTimeout(() => {console.log('计时器1 计时三秒');
}, 3000);setTimeout(() => {console.log('计时器2 计时一秒')
}, 1000);

结果:

// 计时器2 计时一秒
// 计时器1 计时三秒

由此可以看出,在几个计时器同时执行时,并不是按照代码顺序执行,这也就说明了,settimeout是异步执行,和顺序无关,只和计时时长有关

上面代码指的是:两个计时器都同时在浏览器上开始计时,当第二个计时器到计时时长后,浏览器就将其推送到js线程中,因为js线程是空的,所以立即执行,而第一个计时器还在计时中,当它计时完成(3秒),然后浏览器将其推送到js线程中,因为此时js线程空闲,所以也立即执行。

例二:

setTimeout(() => {console.log('计时器')
}, 3000);console.time();
for (let index = 0; index < 10000000; index++) {index.toString = '这是1' + index;// console.log()
}
console.timeEnd();
// default: 5030.498779296875ms
// 计时器

在5030ms(每次执行时间不定,误差在100ms内)后,两个打印先后出现

有人会疑问:我计时器只有3秒啊,它是在浏览器端执行的,跟JS没关系啊

原因就是:JS线程和浏览器互不影响,浏览器只是在计时结束后将计时器中的逻辑推送到JS线程中

所以上面的代码执行流程是:JS执行到setTimeout时,将其交给浏览器去计时(哪怕时间是0),然后去执行同步的for循环,因为例子中的for循环执行需要5s左右,在for循环执行3秒时,浏览器已经把之前的计时器计时完毕,然后推送到JS的事件队列里,当做下一个task任务执行,当前任务(for循环)不受影响,当for循环结束后,JS线程空了,然后去事件队列中取微任务或者新的任务(setTimeout),然后执行。

PS:计时器和上面的default不是严格意义上的同时打出,计时器会稍稍延后一些。

例三:在上面例子上再加一个计时器呢?

setTimeout(() => {console.log('3秒计时器');
}, 3000);console.time();
for(let index = 0; index < 10000000; index++) {index.toString = '这是' + index;
}
console.timeEnd();setTimeout(() => {console.log('2秒计时器');
}, 2000);

结果:

// default: 5030.498779296875ms
// 3秒计时器(稍慢)
// 2秒计时器(2s后)

运行原理是什么呢?

当JS运行到第一个计时器时,将其交给浏览器去计时,然后开始执行同步操作(for循环),在for循环进行3秒后(完成需要5s),浏览器将第一个计时器计时完成,然后将其返回到事件队列中等待,等到for循环执行完,然后执行被存到事件队列中的第一个计时器。然后代码走到第二个计时器时,再交给浏览器去执行,现在js线程是空闲状态,等到浏览器计时结束后,浏览器将其推送到js线程中。

以上就是我们看到的打印结果。

有的人可能就很好奇了,那如果在同步任务执行完,第一个计时器还没计时结束,那结果是什么?

setTimeout(() => {console.log('3秒计时器');
}, 3000);console.time();
for(let index = 0; index < 1000; index++) {index.toString = '这是' + index;
}
console.timeEnd();setTimeout(() => {console.log('2秒计时器');
}, 2000);

结果:

// default: 0.31396484375 ms
//  2秒计时器
//  3秒计时器

总的来说,当我们遇到计时器时,就将其推送到浏览器去计时,当计时结束就将其推送到当前task任务之后,当前task任务结束后即开始执行之前被推送进来的计时器。

2.setTimeout(func,0)含义

setTimeout(function () {func1(); //后执行
}, 0)
func2();  //先执行

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f,0),那么会立刻执行吗? 答案是不会。

因为setTimeout运行机制说过,必须要等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行setTimeout指定的任务。也就是说,setTimeout的真正作用是,在“任务队列”的现有事件的后面再添加一个事件,规定在指定时间执行某段代码。setTimeout添加的事件,会在下一次Event Loop执行。

setTimeout(f,0)将第二个参数设为0,作用是让f在现有的任务(脚本的同步任务和“任务队列”中已有的事件)一结束就立刻执行。也就是说,setTimeout(f,0)的作用是,尽可能早地执行指定的任务。

setTimeout(function (){console.log("你好!");
}, 0);

上面代码的含义是,尽可能早地显示“你好!”。

setTimeout(f,0)指定的任务,最早也要到下一次Event Loop才会执行。请看下面的例子。

setTimeout(function() {console.log("Timeout");
}, 0);function a(x) {console.log("a() 开始运行");b(x);console.log("a() 结束运行");
}function b(y) {console.log("b() 开始运行");console.log("传入的值为" + y);console.log("b() 结束运行");
}console.log("当前任务开始");
a(42);
console.log("当前任务结束");// 当前任务开始
// a() 开始运行
// b() 开始运行
// 传入的值为42
// b() 结束运行
// a() 结束运行
// 当前任务结束
// Timeout

上面代码说明,setTimeout(f,0)必须要等到当前脚本的所有同步任务结束后才会执行。

0毫秒实际上达不到的。根据HTML5标准,setTimeOut推迟执行的时间,最少是4毫秒。如果小于这个值,会被自动增加到4。这是为了防止多个setTimeout(f,0)语句连续执行,造成性能问题。

另一方面,浏览器内部使用32位带符号的整数,来储存推迟执行的时间。这意味着setTimeout最多只能推迟执行2147483647毫秒(24.8天),超过这个时间会发生溢出,导致回调函数将在当前任务队列结束后立即执行,即等同于setTimeout(f,0)的效果。

2.1 setTimeout(f,0)应用

调整事件的发生顺序

setTimeout(f,0)有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)。

var input = document.getElementsByTagName('input[type=button]')[0];input.onclick = function A() {setTimeout(function B() {input.value +=' input';}, 0)
};document.body.onclick = function C() {input.value += ' body'
};

上面代码在点击按钮后,先触发回调函数A,然后触发函数C。在函数A中,setTimeout将函数B推迟到下一轮Loop执行,这样就起到了,先触发父元素的回调函数C的目的了。 用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的。

document.getElementById('input-box').onkeypress = function(event) {this.value = this.value.toUpperCase();
}

上面代码想在用户输入文本后,立即将字符转为大写。但是实际上,它只能将上一个字符转为大写,因为浏览器此时还没接收到文本,所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用。

document.getElementById('my-ok').onkeypress = function() {var self = this;setTimeout(function() {self.value = self.value.toUpperCase();}, 0);
}

上面代码将代码放入setTimeout之中,就能使得它在浏览器接收到文本之后触发;

原来如此:这也就解释了缘何在使用backbone调用render之时,操纵dom是无效的了,因为当时连dom元素都还没获取到(为何没报错?这牵扯到另一个话题),自然等页面渲染完毕了也没见想要的结果了。

分割耗时任何

众所周知javascript是单线程的,特点就是容易出现阻塞。如果一段程序处理时间很长,很容易导致整个页面hold住。什么交互都处理不了怎么办?

简化复杂度?复杂逻辑后端处理?html5的多线程?…

上面都是ok的做法,但是setTimeout也是处理这种问题的一把好手。setTimeout一个很关键的用法就是分片,如果一段程序过大,我们可以拆分成若干细小的块。由于setTimeout(f,0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f,0)里面执行(分片塞入队列),这样即使在复杂程序没有处理完时,我们操作页面,也是能得到即时响应的。其实就是将交互插入到了复杂程序中执行。

var div = document.getElementsByTagName('div')[0];// 写法一
for(var i=0xA00000;i<0xFFFFFF;i++) {div.style.backgroundColor = '#'+i.toString(16);
}// 写法二
var timer;
var i=0x100000;function func() {timer = setTimeout(func, 0);div.style.backgroundColor = '#'+i.toString(16);if (i++ == 0xFFFFFF) clearInterval(timer);
}timer = setTimeout(func, 0);

上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,而写法二就不会,这就是setTimeout(f,0)的好处。即:可利用setTimeout实现一种伪多线程的概念。 另一个使用这种技巧的例子是,代码高亮的处理。如果代码块很大,就会分成一个个小块,写成诸如setTimeout(highlightNext, 50)的样子,进行分块处理。

3. clearTimeout()

setTimeout和setInterval函数,都返回一个表示计数器编号的整数值,将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器。

var id1 = setTimeout(f,1000);
var id2 = setInterval(f,1000);clearTimeout(id1);
clearInterval(id2);

setTimeout和setInterval返回的整数值是连续的(一定环境下,比如浏览器控制台,或者js执行环境等),也就是说,第二个setTimeout方法返回的整数值,将比第一个的整数值大1。利用这一点,可以写一个函数,取消当前所有的setTimeout。

运行上面代码后,实际上再设置任何setTimeout都无效了。

下面是一个clearTimeout实际应用的例子。有些网站会实时将用户在文本框的输入,通过Ajax方法传回服务器,jQuery的写法如下。

$('textarea').on('keydown', ajaxAction);

这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发 keydown 事件,造成大量的Ajax通信。这是不必要的,而且很可能会发生性能问题。正确的做法应该是,设置一个门槛值,表示两次Ajax通信的最小间隔时间。如果在设定的时间内,发生新的keydown事件,则不触发Ajax通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,将进行Ajax通信将数据发送出去。 这种做法叫做debounce(防抖动)方法,用来返回一个新函数。只有当两次触发之间的时间间隔大于事先设定的值,这个新函数才会运行实际的任务。假定两次Ajax通信的间隔不小于2500毫秒,上面的代码可以改写成下面这样。

$('textarea').on('keydown', debounce(ajaxAction, 2500))

利用setTimeout和clearTimeout,可以实现debounce方法。该方法用于防止某个函数在短时间内被密集调用,具体来说,debounce方法返回一个新版的该函数,这个新版函数调用后,只有在指定时间内没有新的调用,才会执行,否则就重新计时。

function debounce(fn, delay){var timer = null; // 声明计时器return function(){var context = this;var args = arguments;clearTimeout(timer);timer = setTimeout(function(){fn.apply(context, args);}, delay);};
}// 用法示例
var todoChanges = debounce(batchLog, 1000);
Object.observe(models.todo, todoChanges);

现实中,最好不要设置太多个setTimeout和setInterval,它们耗费CPU。比较理想的做法是,将要推迟执行的代码都放在一个函数里,然后只对这个函数使用setTimeout或setInterval。

参考文章链接:

详解setTimeout()

setTimeout 详解

setTimeout理解相关推荐

  1. 深入理解 requestAnimationFrame

    原文地址:http://web.jobbole.com/91578/ 在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,css3 可以使用 ...

  2. 【nodejs原理源码杂记(8)】Timer模块与基于二叉堆的定时器

    [摘要] timers模块部分源码和定时器原理 示例代码托管在:http://www.github.com/dashnowords/blogs 一.概述 Timer模块相关的逻辑较为复杂,不仅包含Ja ...

  3. requestAnimationFrame用法

    在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,css3 可以使用 transition 和 animation 来实现,html5 中 ...

  4. JavaScript 中的异步:Event Loop 及其他

    写作时间 2016-09-30 异步 简单地说,JavaScript 是单线程执行的语言,但在使用中有很多异步执行的情况.异步的本质是用其他方式(相对同步)控制程序的执行顺序,这与其他语言中的多线程模 ...

  5. 高逼格的帧动画-requestAnimationFrame

    在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,css3 可以使用 transition 和 animation 来实现,html5 中 ...

  6. 我之理解---计时器setTimeout 和clearTimeout

    今天在写个图片切换的问题 有动画滞后的问题,才动手去查setTimeout 和clearTimeout.之前写的图片播放器也有类似的问题,有自动start按钮 和stop按钮, 其他都正常,问题出在每 ...

  7. 每日题(js):setTimeout与setInterval(深入理解)

    题目:setInterval/setTimeout 详细理解 定时器:setTimeout setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码. var t ...

  8. 彻底理解setTimeout()

    之前在网上看了很多关于setTimeout的文章,但我感觉都只是点到为止,并没有较深入的去剖析,也可能是我脑袋瓜笨,不容易被点解.后面看了<你不知道的javascript-上卷>一书,决定 ...

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

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

最新文章

  1. 现在的Java面试已经和2年前完全不一样了!
  2. 先进先出置换算法(FIFO)
  3. Understanding Stock Types in SAP EWM
  4. 销售必备心灵鸡汤(转)
  5. 用Delphi设计能携带附件的EMail
  6. mysql用supervisor管理_使用Supervisor管理进程
  7. RHEL6入门系列之十,常用命令3
  8. oracle 设置 shmmax,安装ORACLE时在Linux上设置内核参数的含义
  9. Python 学习 DAY1
  10. Memtest移植到海思上面测试DDR
  11. 小米上的lineageos
  12. QQ2013的PC版协议,0825包和0826的数据分析
  13. 自己动手打造mini型QQ
  14. 自激多谐振荡电路实验总结,小白电路测试
  15. 服务器主板最多支持内存频率,b360主板支持内存频率多少
  16. ORACLE sql 高级查询
  17. 小说阅读 (支持txt文本文件浏览)
  18. 捷联惯导速度更新_划桨效应补偿算法推导(双子样)
  19. Android学习——APP内容共享
  20. 衡水二中2021清华北大高考成绩查询,衡水中学2020高考成绩多少人考上清华北大...

热门文章

  1. 网络工程师资料-永久有效
  2. 第二讲 Java语言概述
  3. 主动学习(Active Learning) 概述、策略和不确定性度量
  4. 小程序CMS动态处理数据之内容模型和内容集合的使用
  5. IDEA类和方法注释模板设置
  6. 数据库编程需要下载什么软件
  7. seekbar自定义android,Android自定义SeekBar实现视频播放进度条
  8. 如何使用远程桌面连接云服务器 (以阿里云为例)
  9. 三菱PLC之间的N:N无线通讯的常见问题汇总【1】
  10. MASM32编程完善SysInfo遇到奇怪故障,真切感受全局变量和局部变量之别……