JS | setTimeout 延时器详解

目录

setTimeout 简介

清除 setTimeout

setTimeout 什么时候开始计时?

0 秒延时并不代表立即执行!

多个 setTimeout 的执行顺序 ?

setTimeout 的应用场景

setTimeout 的网红面试题

延展提问:怎样改动实现打印 0 到 9 ?


settimeout 的特点是,它只执行一次,如果想要再次执行,需要重新调用 settimeout。另外,settimeout 的延迟时间并不是准确的,它只是表示最早可能执行的时间,实际执行的时间可能会受到其他代码的影响,比如浏览器的事件循环,或者其他的定时器。

setTimeout 简介

setTimeout 是一个 js 内置的函数,用于延时执行代码

参数1:回调函数,延迟一段时间后执行的代码

参数2:延迟的时间,单位是毫秒。(默认为 0 毫秒)

返回值:计时器的ID,是一个整数(例子中的 timer)

const timer = setTimeout(function() {
    console.log(1); // 3 秒后,打印 1
}, 3000);

清除 setTimeout

clearTimeout(timer); // timer 为计时器的ID

参考:JavaScript setTimeout和setInterval的用法与区别详解 - 儒雅烤地瓜CSDN博客  

setTimeout 什么时候开始计时?

首先, setTimeout 属于 js 异步任务中的宏任务

如上图可见,宏任务需等待同步任务、微任务、DOM渲染完成后,通过事件轮询触发执行,所以存在复杂异步逻辑时,很难精准预判 setTimeout 的开始计时时间。

逻辑简单的,比较好分析,如:

function test() {
  print("开始");

  setTimeout(() => {
    print("执行 setTimeout");
  }, 1000);

  print("结束");
}
  • setTimeout 从打印 “结束” 后开始计时
  • 从计时开始 1 秒后,打印 “ 执行 setTimeout ”

下方代码可以展示得更清晰:

function print(info) {
  let dt = new Date();
  var y = dt.getFullYear();
  var mt = dt.getMonth() + 1;
  var day = dt.getDate();
  var h = dt.getHours(); //获取时
  var m = dt.getMinutes(); //获取分
  var s = dt.getSeconds(); //获取秒
  let str =
    "当前时间:" +
    y +
    "年" +
    mt +
    "月" +
    day +
    "日" +
    h +
    "时" +
    m +
    "分" +
    s +
    "秒";

  console.log(info + "————" + str);
}

function sleep(delay) {
  var start = new Date().getTime();
  while (new Date().getTime() - start < delay) {
    continue;
  }
  print("sleep 执行完毕");
}

function test() {
  print("开始");

  sleep(1000);

  setTimeout(() => {
    print("执行 setTimeout");
  }, 1000);

  print("结束");
}

test();

在开发者工具里,执行后,结果如下:

开始————当前时间:2024年8月29日22时27分7秒
sleep 执行完毕————当前时间:2024年8月29日22时27分8秒
VM156:24 结束————当前时间:2024年8月29日22时27分8秒
VM156:24 执行 setTimeout————当前时间:2024年8月29日22时27分9秒

0 秒延时并不代表立即执行!

print("同步任务执行开始");

setTimeout(function () {
  print("setTimeout延时0秒执行");
}, 0);

print("同步任务执行结束");

一旦添加了 setTimeout ,便是一个异步宏任务,需等同步任务、异步微任务、DOM渲染完成后,通过事件轮询触发执行。

多个 setTimeout 的执行顺序 ?

function test() {
  console.time("本段代码总耗时");

  print("同步任务执行开始");

  setTimeout(function () {
    print("setTimeout延时3秒执行");
    console.timeEnd("本段代码总耗时");
  }, 3000);

  setTimeout(function () {
    print("第1个setTimeout延时2秒执行");
  }, 2000);

  setTimeout(function () {
    print("第2个setTimeout延时2秒执行");
  }, 2000);

  print("同步任务执行结束");
}

test();

 在开发者工具里,执行后,结果如下:

同步任务执行开始————当前时间:2024年8月29日22时39分3秒
同步任务执行结束————当前时间:2024年8月29日22时39分3秒
第1个setTimeout延时2秒执行————当前时间:2024年8月29日22时39分5秒
第2个setTimeout延时2秒执行————当前时间:2024年8月29日22时39分5秒
setTimeout延时3秒执行————当前时间:2024年8月29日22时39分6秒
本段代码总耗时: 3007.1611328125 ms

  • 所有 setTimeout 的开始计时时间几乎相同
  • 延时相同的 setTimeout ,会按 setTimeout 的出现的先后顺序执行
  • 延时不同的 setTimeout ,延时越久的 setTimeout 越晚执行 

setTimeout 的应用场景

5秒后关闭网页两侧的广告栏

window.onload = function () {
    //获取相关元素
    var imgArr = document.getElementsByTagName("img");
    //设置定时器:5秒后关闭两侧的广告栏
    setTimeout(fn,5000);
    function fn(){
        imgArr[0].style.display = "none";
        imgArr[1].style.display = "none";
    }
}

setTimeout 的网红面试题

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  });
}

解析 :

setTimeout 是异步宏任务,for 循环是同步任务,for 循环先执行,依次在Web APIs 中添加了10个setTimeout,待 for 循环完毕,i 的值已变为 10 ,此时才开始事件轮询,setTimeout 依次开始计时,因延时为0秒,最终效果为 for 循环完毕后,立马一次执行 10 次 setTimeout 的回调,即依次打印10个10。

延展提问:怎样改动实现打印 0 到 9 ?

答案1:将 var 改成 let

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  });
}

解析:let 声明的变量具有局部作用域, var 声明的变量是全局作用域,改用 let 后,执行 setTimeout 内的回调函数时,取到的 i 值为 setTimeout 被放入 Web APIs 时的值,即 for 循环时 i 的值。

答案2:改用立即执行函数包裹

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i);
    });
  })(i);
}

解析:立即执行函数的原理和 let 类似,也是形成了局部作用域,实现了预期效果。


番外:‌setTimeout()函数的代码执行顺序主要取决于JavaScript的事件循环机制和代码的编写方式。

在JavaScript中,setTimeout函数通过将代码放入任务队列中,并在指定的延迟时间后执行。这个过程是异步的,意味着它不会阻塞主线程的执行,而是在当前代码执行完毕后才开始计时。因此,setTimeout的代码可能会在串口事件的代码之后执行,具体取决于代码的编写和事件的触发时间‌。

JavaScript的事件循环机制决定了宏任务(如setTimeout)的执行顺序。主任务完成后,微任务队列中的内容会首先被执行,然后是宏任务队列中的内容。这意味着,如果主任务和微任务中的内容都执行完毕时,时间还未结束,那么会等到时间到了再进入宏任务队列并被主线程读取‌。

此外,setTimeout函数的计时开始时间也受到事件循环的影响。在存在复杂异步逻辑时,很难精准预判setTimeout的开始计时时间。但在逻辑简单的场景下,例如当主任务执行完毕后,setTimeout才会开始计时,并在计时完成后将内容放入宏任务队列中等待被主线程读取‌。

综上所述,setTimeout函数的执行顺序受到JavaScript事件循环机制的影响,具体执行顺序取决于代码的编写方式、事件的触发时间以及事件循环中任务的优先级和顺序。


相关参考:

JavaScript之setTimeout详解

同步、异步、回调执行顺序之经典闭包setTimeout分析

setTimeout和setInterval的用法与区别详解 - CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的烤地瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值