setInterval
setInterval 允许我们重复运行一个函数,从一段时间间隔之后开始运行,之后以该时间间隔连续重复运行该函数。
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
// 每 2 秒重复一次let timerId = setInterval(() => console.log('tick'), 2000);// 5 秒之后停止setTimeout(() => { clearInterval(timerId); console.log('stop'); }, 5000);
setTimeout
基本使用
setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
- func或者code
- 延迟ms
- args传递给func的入参
- clearTimeout取消执行定时器 ```javascript function sayHi(name, word) { console.log(Array.from(arguments).join(‘’)); //‘JOYRJQHAHA’ console.log(name + ‘:’ + word); }
const timerId = setTimeout(sayHi, 1000, ‘JOY’, ‘RJQ’, ‘HAHA’); //注意
console.log(timerId); clearTimeout(timerId);
执行clearTimeout之后,sayHi将不会被执行<br />注意:如果想给第一个参数func传递入参,不要写成箭头函数 箭头函数没有arguments```javascriptconst timerId = setTimeout(()=>{console.log(arguments);sayHi();}, 1000, 'JOY', 'RJQ', 'HAHA'); //注意//=======正确的姿势function sayHi() {console.log('sayHi', Array.from(arguments).join('')); //JOYRJQHAHA}const timerId = setTimeout(function test() {console.log(arguments);sayHi(...arguments);},1000,'JOY','RJQ','HAHA');
用setTimeout实现setInterval
嵌套的 setTimeout 能够精确地设置两次执行之间的延时,而 setInterval 却不能。
function sayHi() {console.log('sayHi');}setTimeout(() => {sayHi();setTimeout(sayHi, 2000);}, 1000);=========================function tick(){console.log('tick')}setTimeout(function run() {tick();setTimeout(run, 1000);}, 1000);=========================function printNumbers(from, to) {let current = from;setTimeout(function go() { //这里命名为go 方便嵌套中直接使用console.log(current);if (current < to) {setTimeout(go, 1000);}current++;}, 1000);}// 用例:printNumbers(5, 10);
嵌套的 setTimeout 就能确保延时的固定(这里是 100 毫秒)
使用 setInterval 时,func 函数的实际调用间隔要比代码中设定的时间间隔要短!
因为 func 的执行所花费的时间“消耗”了一部分间隔时间。
也可能出现这种情况,就是 func 的执行所花费的时间比我们预期的时间更长,并且超出了 100 毫秒。
极端情况下,如果函数每次执行时间都超过 delay 设置的时间,那么每次调用之间将完全没有停顿。
最小延迟4ms
根据 HTML5 标准 所讲:“经过 5 重嵌套定时器之后,时间间隔被强制设定为至少 4 毫秒”。
需要同时满足嵌套层级超过 5 层,timeout 小于 4ms,才会设置 4ms
对于服务端的 JavaScript,就没有这个限制,并且还有其他调度即时异步任务的方式。例如 Node.js 的 setImmediate。因此,这个提醒只是针对浏览器环境的。
手动实现setTimeout-rAF
const element = document.getElementById('app');let start;function step(timestamp) {if (start === undefined) start = timestamp;const elapsed = timestamp - start;//这里使用`Math.min()`确保元素刚好停在200px的位置。element.style.transform ='translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';if (elapsed < 10000) {// 在两秒后停止动画window.requestAnimationFrame(step);}}window.requestAnimationFrame(step);
let setTimeout = (fn, timeout, ...args) => {// 初始当前时间const start = +new Date()let timer, nowconst loop = () => {timer = window.requestAnimationFrame(loop)// 再次运行时获取当前时间now = +new Date()// 当前运行时间 - 初始当前时间 >= 等待时间 ===>> 跳出if (now - start >= timeout) {fn.apply(this, args)window.cancelAnimationFrame(timer)}}window.requestAnimationFrame(loop)}function showName(){console.log("Hello")}let timerID = setTimeout(showName, 1000);// 在 1 秒后打印 “Hello”
this丢失
let user = {firstName: "John",sayHi() {console.log(`Hello, ${this.firstName}!`);}};setTimeout(user.sayHi, 1000); // Hello, undefined!===========相当于=======let f = user.sayHi;setTimeout(f, 1000); // 丢失了 user 上下文
浏览器中的setTimeout方法有些特殊:它会为函数调用设定this=window(node中this为计时器对象)
所以意图获取window.firstName,变量不存在 所以undefined
垃圾回收
当一个函数传入 setInterval/setTimeout 时,将为其创建一个内部引用,并保存在调度程序中。这样,即使这个函数没有其他引用,也能防止垃圾回收器(GC)将其回收。
// 在调度程序调用这个函数之前,这个函数将一直存在于内存中setTimeout(function() {...}, 100);
对于 setInterval,传入的函数也是一直存在于内存中,直到 clearInterval 被调用。
这里还要提到一个副作用。如果函数引用了外部变量(译注:闭包),那么只要这个函数还存在,外部变量也会随之存在。它们可能比函数本身占用更多的内存。因此,当我们不再需要调度函数时,最好取消它,即使这是个(占用内存)很小的函数。
References:
https://mp.weixin.qq.com/s/cxkFx3Rzxb_F-Coqu8aZCA
