-
定时器种类及定义
-
setTimeout
- 解析:在定时器到期后执行一个函数或指定的一段代码
- 语法:
- var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
- var timeoutID = scope.setTimeout(function[, delay]);
- var timeoutID = scope.setTimeout(code[, delay]);
-
setInterval
- 解析:重复调用一个函数或执行一个代码片段,在每次调用之间具有固定的时间间隔
- 语法:
- var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
- var timeoutID = scope.setTimeout(function[, delay]);
- var timeoutID = scope.setTimeout(code[, delay]);
-
返回值 timeoutID
- 返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。
- 需要注意的是setTimeout()和setInterval()共用一个编号池,技术上,clearTimeout()和 clearInterval() 可以互换。但是,为了避免混淆,不要混用取消定时函数。
- 在同一个对象上(一个window或者worker),setTimeout()或者setInterval()在后续的调用不会重用同一个定时器编号。但是不同的对象使用独立的编号池。
-
-
常见问题
- setTimeout和setInterval怎么传参
- setTimeout和setInterval从第三个参数开始后面都是回调执行函数的参数
setTimeout((...arguments)=>{ console.log('接收到了参数',[...arguments]) // ['a','b','c','d'] },1000,'a','b','c','d')
- setTimeout和setInterval为什么不常用字符串形式
- 向setTimeout()传递一个字符串而不是函数会遭受到与使用eval一样的风险.【字符串会在全局作用域内被解释执行,所以当setTimeout()函数执行完毕后,字符串中的变量不可用.】
- 注意字符串时,该字符串是要被解析执行的,所以不能是函数名,必须是函数执行【
fn ()
】
* 推荐 * setTimeout(fn,1000) * 不推荐 * setTimeout(`fn()`,1000) function fn(){console.log(222)} * 推荐 * window.setTimeout(function() { alert("Hello World!"); }, 500); * 不推荐 * window.setTimeout("alert(\"Hello World!\");", 500);
- 浏览器对setTimeout和setInterval的极限支持时间范围是多少【最大和最小】,低于最小值时会怎样?超出最大值溢出后会怎样?
- setTimeout 嵌套最少间隔4毫秒【所以,写0和写4没区别】
- 最小延时 >=4ms
setTimeout(cb, 0); function cb() {setTimeout(cb, 0)}
, 在Chrome 和 Firefox中, 定时器的第5次调用被阻塞了;在Safari是在第6次- 其实在前 5 次执行中时间间隔会是 0 毫秒,后面每次的调用最小时间间隔是 4 毫秒。之所以出现这样的情况,是因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒
- 浏览器其内部以32个bit整数存储延时值的。32bit最大只能存放的数字是2147483647,这就会导致如果一个延时(delay)大于 2147483647 毫秒 (大约24.8 天)时就会溢出。【导致定时器将会被立即执行】
* 溢出会立即执行 * function fn() { setTimeout(() => { console.log('go') }, 2147483648) }
- setTimeout 嵌套最少间隔4毫秒【所以,写0和写4没区别】
- setTimeout和setInterval的this指向什么问题
- setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。
- 这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的。
let myArray = ["zero", "one", "two"]; myArray.myMethod = function (sProperty) { alert(arguments.length > 0 ? this[sProperty] : this); }; setTimeout(myArray.myMethod, 1000); // prints "[object Window]" setTimeout(myArray.myMethod, 1000, "1"); // prints "undefined" <!-- 破解方案 --> // 箭头函数 setTimeout(() => {myArray.myMethod()}, 1000); // prints "zero,one,two",this now is 'myArray' setTimeout(() => {myArray.myMethod('1')}, 1000); // prints "one" ,this now is 'myArray' // 包装函数 setTimeout(function(){myArray.myMethod()}, 1000); // prints "zero,one,two",this now is 'myArray' setTimeout(function(){myArray.myMethod('1')}, 1000); // prints "one" ,this now is 'myArray'
- setTimeout和setInterval与闭包的那点事
- 闭包是很容易照成内存泄露的一种场景,因为变量得不到释放
- setTimeout是宏任务,在任务栈中最后执行
- 经典的循环闭包题【破解方案中利用闭包让变量不被清除覆盖,缓存当时的变量值,只要当宏任务执行的时候取到对应上下文中的变量】
for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i);// 3,3,3 }, 1000); } <!-- 破解方案 --> for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i);// 1,2,3 }, 1000); } for (var i = 0; i < 3; i++) { (function(i) { setTimeout('console.log(' + i + ')', 1000 * i); })(i); } 或者 for (var i = 1; i <= 3; i++) { setTimeout((function(i) { return function() { console.log(i); }; })(i), 1000 * i); }
- setTimeout和setInterval为什么时间不准,和单线程微宏任务有什么关系?
- setTimeout 什么时候执行不取决于设置的时间
- setTimeout和setInterval是宏任务,得等到微任务执行完,才能排队执行宏任务,若前一个宏任务一直执行没有执行完,下一个宏任务就一直在队列挂起状态
- 函数的第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。JavaScript是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()的第二个参数只是告诉JavaScript引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
- setInterval为什么在生产中不常用
- 会跳帧,时间不准
- setInterval()在实践中很少会在生产环境下使用,因为一个任务结束和下一个任务开始之间的时间间隔是无法保证的,有些循环定时任务可能会因此而被跳过。而像前面这个例子中一样使用setTimeout()则能确保不会出现这种情况。一般来说,最好不要使用setInterval()
- 如下面的例子
function click() { setInterval(function() { // process ... // 假如这里面是个长耗时的代码片段,假设耗时3s }, 1000); } // 当点击启动循环定时器以后,第一次1s后执行process,第2s的时候执行第二次,第3s的时候执行第三次,第4s的时候执行第四次, // 但是第一次的process执行完需要3s,所以当第一次process执行完以后,队列里面已经有了第二次的和第三次的,但是js引擎只允许有一份未执行的process代码,所以会把第二次的抛弃 // 由此可见,会跳帧 // setTimeout就不会有这样的问题,他会在复合条件时执行,不会像setInterval时不停的执行,所以一般生产上会用setTimeout模拟setInterval // 尤其当循环请求接口的场景更要避免setInterval【requestanimationframe方案,这里不在展开】
- 易内存泄漏
- 定时器也可能会悄悄地导致内存泄漏。下面的代码中,定时器的回调通过闭包引用了外部变量,只要定时器一直运行,回调函数中引用的name就会一直占用内存。垃圾回收程序当然知道这一点,因而就不会清理外部变量,变量得不到释放,很容易内存泄露【使用JavaScript闭包很容易在不知不觉间造成内存泄漏】
let name = 'Jake'; setInterval(() => { console.log(name); }, 1000);
- 所以生产环境一般遇到需要重复执行的场景,一般用setTimeout模拟
- 会跳帧,时间不准
- 怎么样setTimeout模拟setInterval,且可清除
- 两个简单的封装
let timeoutID = null; timeoutID = setTimeout(fn, time); function fn(){ if(xxx){//在条件范围内循环执行 timeoutID = setTimeout(fn, time); }else{//超出条件,直接清除 clearTimeout(timeoutID) } }
let timeoutID = null; timeoutID = setTimeout(function(){ if(xxx){//在条件范围内循环执行 timeoutID = setTimeout(arguments.callee, time); }else{//超出条件,直接清除 clearTimeout(timeoutID) } }, time);
- setTimeout和setInterval有什么不同
- setInterval除非主动清除或者页面关闭,否则不会停止
- setTimeout页面关闭会停止
- setTimeout和setInterval怎么传参
-
参考文献
- MDN介绍【 developer.mozilla.org/zh-CN/docs/… 】【推荐】
- 红宝书【BOM定时器、内存泄露】【 www.dedao.cn/ebook/reade… 】 【推荐】