setInterval

setInterval 允许我们重复运行一个函数,从一段时间间隔之后开始运行,之后以该时间间隔连续重复运行该函数。

  1. let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
  1. // 每 2 秒重复一次
  2. let timerId = setInterval(() => console.log('tick'), 2000);
  3. // 5 秒之后停止
  4. setTimeout(() => { clearInterval(timerId); console.log('stop'); }, 5000);

setTimeout

基本使用

setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。

  1. 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);

  1. 执行clearTimeout之后,sayHi将不会被执行<br />注意:如果想给第一个参数func传递入参,不要写成箭头函数 箭头函数没有arguments
  2. ```javascript
  3. const timerId = setTimeout(()=>{
  4. console.log(arguments);
  5. sayHi();
  6. }, 1000, 'JOY', 'RJQ', 'HAHA'); //注意
  7. //=======正确的姿势
  8. function sayHi() {
  9. console.log('sayHi', Array.from(arguments).join('')); //JOYRJQHAHA
  10. }
  11. const timerId = setTimeout(
  12. function test() {
  13. console.log(arguments);
  14. sayHi(...arguments);
  15. },
  16. 1000,
  17. 'JOY',
  18. 'RJQ',
  19. 'HAHA'
  20. );

用setTimeout实现setInterval

嵌套的 setTimeout 能够精确地设置两次执行之间的延时,而 setInterval 却不能。

  1. function sayHi() {
  2. console.log('sayHi');
  3. }
  4. setTimeout(() => {
  5. sayHi();
  6. setTimeout(sayHi, 2000);
  7. }, 1000);
  8. =========================
  9. function tick(){console.log('tick')}
  10. setTimeout(function run() {
  11. tick();
  12. setTimeout(run, 1000);
  13. }, 1000);
  14. =========================
  15. function printNumbers(from, to) {
  16. let current = from;
  17. setTimeout(function go() { //这里命名为go 方便嵌套中直接使用
  18. console.log(current);
  19. if (current < to) {
  20. setTimeout(go, 1000);
  21. }
  22. current++;
  23. }, 1000);
  24. }
  25. // 用例:
  26. printNumbers(5, 10);

嵌套的 setTimeout 就能确保延时的固定(这里是 100 毫秒)
image.png
使用 setInterval 时,func 函数的实际调用间隔要比代码中设定的时间间隔要短!
因为 func 的执行所花费的时间“消耗”了一部分间隔时间。
也可能出现这种情况,就是 func 的执行所花费的时间比我们预期的时间更长,并且超出了 100 毫秒。

极端情况下,如果函数每次执行时间都超过 delay 设置的时间,那么每次调用之间将完全没有停顿。
image.png

最小延迟4ms

根据 HTML5 标准 所讲:“经过 5 重嵌套定时器之后,时间间隔被强制设定为至少 4 毫秒”。

需要同时满足嵌套层级超过 5 层,timeout 小于 4ms,才会设置 4ms

对于服务端的 JavaScript,就没有这个限制,并且还有其他调度即时异步任务的方式。例如 Node.js 的 setImmediate。因此,这个提醒只是针对浏览器环境的。

手动实现setTimeout-rAF

  1. const element = document.getElementById('app');
  2. let start;
  3. function step(timestamp) {
  4. if (start === undefined) start = timestamp;
  5. const elapsed = timestamp - start;
  6. //这里使用`Math.min()`确保元素刚好停在200px的位置。
  7. element.style.transform =
  8. 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';
  9. if (elapsed < 10000) {
  10. // 在两秒后停止动画
  11. window.requestAnimationFrame(step);
  12. }
  13. }
  14. window.requestAnimationFrame(step);
  1. let setTimeout = (fn, timeout, ...args) => {
  2. // 初始当前时间
  3. const start = +new Date()
  4. let timer, now
  5. const loop = () => {
  6. timer = window.requestAnimationFrame(loop)
  7. // 再次运行时获取当前时间
  8. now = +new Date()
  9. // 当前运行时间 - 初始当前时间 >= 等待时间 ===>> 跳出
  10. if (now - start >= timeout) {
  11. fn.apply(this, args)
  12. window.cancelAnimationFrame(timer)
  13. }
  14. }
  15. window.requestAnimationFrame(loop)
  16. }
  17. function showName(){
  18. console.log("Hello")
  19. }
  20. let timerID = setTimeout(showName, 1000);
  21. // 在 1 秒后打印 “Hello”

this丢失

  1. let user = {
  2. firstName: "John",
  3. sayHi() {
  4. console.log(`Hello, ${this.firstName}!`);
  5. }
  6. };
  7. setTimeout(user.sayHi, 1000); // Hello, undefined!
  8. ===========相当于=======
  9. let f = user.sayHi;
  10. setTimeout(f, 1000); // 丢失了 user 上下文

浏览器中的setTimeout方法有些特殊:它会为函数调用设定this=window(node中this为计时器对象)
所以意图获取window.firstName,变量不存在 所以undefined

垃圾回收

当一个函数传入 setInterval/setTimeout 时,将为其创建一个内部引用,并保存在调度程序中。这样,即使这个函数没有其他引用,也能防止垃圾回收器(GC)将其回收。

  1. // 在调度程序调用这个函数之前,这个函数将一直存在于内存中
  2. setTimeout(function() {...}, 100);

对于 setInterval,传入的函数也是一直存在于内存中,直到 clearInterval 被调用。

这里还要提到一个副作用。如果函数引用了外部变量(译注:闭包),那么只要这个函数还存在,外部变量也会随之存在。它们可能比函数本身占用更多的内存。因此,当我们不再需要调度函数时,最好取消它,即使这是个(占用内存)很小的函数。

References:

https://mp.weixin.qq.com/s/cxkFx3Rzxb_F-Coqu8aZCA