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
```javascript
const 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, now
const 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