setTimeout 简介
setTimeout 是一个 js 内置的函数,用于延时执行代码
- 参数1:回调函数,延迟一段时间后执行的代码
- 参数2:延迟的时间,单位是毫秒。(默认为 0 毫秒)
- 返回值:计时器的ID,是一个整数(例子中的 timer)。
const timer = setTimeout(function() { console.log(1); // 3 秒后,打印 1 }, 3000);
清除 setTimeout
clearTimeout(timer); // timer 为计时器的ID
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年5月29日14时11分23秒 sleep 执行完毕————当前时间:2024年5月29日14时11分24秒 结束————当前时间:2024年5月29日14时11分24秒 执行 setTimeout————当前时间:2024年5月29日14时11分25秒
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年5月29日14时21分20秒 同步任务执行结束————当前时间:2024年5月29日14时21分20秒 第1个setTimeout延时2秒执行————当前时间:2024年5月29日14时21分22秒 第2个setTimeout延时2秒执行————当前时间:2024年5月29日14时21分22秒 setTimeout延时3秒执行————当前时间:2024年5月29日14时21分23秒 本段代码总耗时: 3.005s
- 所有 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); }); }
答案
10 10 10 10 10 10 10 10 10 10
解析 :
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); }