← JavaScript高阶函数 JavaScript setInterval() →

JavaScript setTimeout()

原创 2026-03-14 JavaScript 已有人查阅

JavaScript setTimeout() 方法指南:定时执行的秘密武器

一、setTimeout 是什么

setTimeout 是 JavaScript 中用于延迟执行某个函数的方法。就是"过一会儿再做某件事"。它属于 Web API,由浏览器提供(Node.js 中也有对应的实现)。

// 最简单的例子:2秒后弹出提示
setTimeout(() => {
    console.log('2秒时间到了!');
}, 2000);

console.log('定时器已设置,继续执行他代码');
// 输出顺序:
// "定时器已设置,继续执行他代码"
// (2秒后)"2秒时间到了!"

二、基本语法和参数

let timerId = setTimeout(函数, 延迟时间, 参数1, 参数2, ...);
参数 说明
函数 要执行的函数(可以是函数名或箭头函数)
延迟时间 单位毫秒,1000 = 1秒,默认0
参数1... 传给函数的参数(可选)
 
// 带参数的例子
function greet(name, greeting) {
    console.log(`${greeting},${name}!`);
}

// 3秒后执行,并传递参数
setTimeout(greet, 3000, '张三', '早上好');
// 3秒后输出:"早上好,张三!"

// 用箭头函数也可以
setTimeout((msg) => {
    console.log(`收到消息:${msg}`);
}, 2000, 'Hello World');

三、实际应用场景

1. 延迟提示或通知

class NotificationManager {
    constructor() {
        this.notifications = [];
    }
    
    showMessage(msg, duration = 3000) {
        console.log(`[通知] ${msg}`);
        
        // 延迟后自动消失
        setTimeout(() => {
            console.log(`[通知消失] ${msg}`);
            this.notifications = this.notifications.filter(n => n !== msg);
        }, duration);
        
        this.notifications.push(msg);
    }
    
    showDelayedMessage(msg, delay = 2000, duration = 3000) {
        console.log(`消息已排队:${msg},将在${delay}ms后显示`);
        
        setTimeout(() => {
            this.showMessage(msg, duration);
        }, delay);
    }
}

// 使用示例
const notifier = new NotificationManager();
notifier.showMessage('欢迎登录系统'); // 立即显示,3秒后消失
notifier.showDelayedMessage('您有一条新消息', 5000); // 5秒后显示

2. 搜索输入防抖

class SearchBox {
    constructor(inputId) {
        this.input = document.getElementById(inputId);
        this.searchTimeout = null;
        this.setupListener();
    }
    
    setupListener() {
        this.input.addEventListener('input', (e) => {
            const keyword = e.target.value;
            
            // 清除之前的定时器
            if (this.searchTimeout) {
                clearTimeout(this.searchTimeout);
            }
            
            // 设置新的定时器:用户停止输入500ms后才搜索
            this.searchTimeout = setTimeout(() => {
                this.performSearch(keyword);
            }, 500);
        });
    }
    
    performSearch(keyword) {
        if (keyword.length < 2) {
            console.log('关键词太短,不搜索');
            return;
        }
        
        console.log(`正在搜索:${keyword}`);
        // 这里可以发AJAX请求
        // fetch(`/api/searchq=${keyword}`)
    }
}

// 使用(假设有id为"searchInput"的输入框)
// const search = new SearchBox('searchInput');

3. 轮询操作

class PollingManager {
    constructor(interval = 5000) {
        this.interval = interval;
        this.timeoutId = null;
        this.isPolling = false;
    }
    
    // 开始轮询
    startPolling() {
        if (this.isPolling) return;
        
        this.isPolling = true;
        console.log('开始轮询...');
        
        const poll = () => {
            if (!this.isPolling) return;
            
            // 执行轮询操作
            this.checkForUpdates();
            
            // 设置下一次轮询
            this.timeoutId = setTimeout(poll, this.interval);
        };
        
        // 立即执行第一次
        poll();
    }
    
    // 停止轮询
    stopPolling() {
        this.isPolling = false;
        if (this.timeoutId) {
            clearTimeout(this.timeoutId);
            this.timeoutId = null;
            console.log('轮询已停止');
        }
    }
    
    checkForUpdates() {
        const now = new Date();
        console.log(`[${now.toLocaleTimeString()}] 检查更新...`);
        
        // 模拟检查更新的逻辑
        if (Math.random() > 0.7) {
            console.log('发现新内容!');
        }
    }
}

// 使用示例
const poller = new PollingManager(3000); // 每3秒检查一次
// poller.startPolling();

// 10秒后停止
setTimeout(() => {
    poller.stopPolling();
}, 10000);

4. 动画效果

class FadeAnimation {
    constructor(element) {
        this.element = element;
        this.opacity = 1;
    }
    
    fadeOut(duration = 1000) {
        const steps = 20; // 分20步完成
        const interval = duration / steps;
        const step = 1 / steps;
        
        const fade = () => {
            this.opacity -= step;
            this.element.style.opacity = this.opacity;
            
            if (this.opacity > 0) {
                setTimeout(fade, interval);
            } else {
                this.element.style.display = 'none';
                console.log('淡出完成');
            }
        };
        
        fade();
    }
    
    fadeIn(duration = 1000) {
        this.element.style.display = 'block';
        this.opacity = 0;
        this.element.style.opacity = 0;
        
        const steps = 20;
        const interval = duration / steps;
        const step = 1 / steps;
        
        const fade = () => {
            this.opacity += step;
            this.element.style.opacity = this.opacity;
            
            if (this.opacity < 1) {
                setTimeout(fade, interval);
            } else {
                console.log('淡入完成');
            }
        };
        
        fade();
    }
}

// 使用(假设有id为"box"的元素)
// const box = document.getElementById('box');
// const anim = new FadeAnimation(box);
// anim.fadeOut(2000);

四、clearTimeout 取消定时器

setTimeout 返回一个 ID,用 clearTimeout 可以取消执行:

console.log('启动定时器...');

const timerId = setTimeout(() => {
    console.log('这行不会被执行');
}, 5000);

// 3秒后取消(在5秒之前)
setTimeout(() => {
    clearTimeout(timerId);
    console.log('定时器已取消');
}, 3000);

// 实际应用:取消操作
class UploadManager {
    constructor() {
        this.uploadTimeout = null;
        this.uploadStatus = 'pending';
    }
    
    startUpload(file) {
        console.log(`开始上传:${file.name}`);
        this.uploadStatus = 'uploading';
        
        // 设置超时处理
        this.uploadTimeout = setTimeout(() => {
            if (this.uploadStatus === 'uploading') {
                this.uploadStatus = 'timeout';
                console.log(`上传超时:${file.name}`);
                this.cancelUpload('上传时间过长');
            }
        }, 30000); // 30秒超时
        
        // 模拟上传过程
        this.simulateUpload(file);
    }
    
    simulateUpload(file) {
        // 假设上传需要一些时间
        setTimeout(() => {
            if (this.uploadStatus === 'uploading') {
                this.uploadStatus = 'completed';
                clearTimeout(this.uploadTimeout); // 取消超时定时器
                console.log(`上传完成:${file.name}`);
            }
        }, 15000); // 15秒完成
    }
    
    cancelUpload(reason) {
        if (this.uploadTimeout) {
            clearTimeout(this.uploadTimeout);
            this.uploadTimeout = null;
        }
        this.uploadStatus = 'cancelled';
        console.log(`上传已取消:${reason}`);
    }
}

// 使用示例
const uploader = new UploadManager();
const file = { name: 'document.pdf', size: 1024 };
uploader.startUpload(file);

五、setTimeout 的进阶技巧

1. 递归 setTimeout 实现 setInterval

// setInterval 存在的问题
let count = 0;
const intervalId = setInterval(() => {
    console.log(`第${++count}次执行`);
    
    // 如果这里有个耗时操作
    const start = Date.now();
    while (Date.now() - start < 2000) {
        // 模拟耗时2秒
    }
    // setInterval 会在2秒后继续调用,导致重叠执行
}, 1000);

// 更好的做法:递归 setTimeout
class RecursiveTimer {
    constructor() {
        this.count = 0;
        this.timeoutId = null;
        this.isRunning = false;
    }
    
    start() {
        if (this.isRunning) return;
        this.isRunning = true;
        this.schedule();
    }
    
    schedule() {
        if (!this.isRunning) return;
        
        this.timeoutId = setTimeout(() => {
            this.execute();
            this.schedule(); // 执行完后安排下一次
        }, 1000);
    }
    
    execute() {
        const now = new Date();
        console.log(`[${now.toLocaleTimeString()}] 执行第${++this.count}次任务`);
        
        // 即使这里有耗时操作,也不会影响下一次调用的间隔计算
        // const start = Date.now();
        // while (Date.now() - start < 2000) { }
    }
    
    stop() {
        this.isRunning = false;
        if (this.timeoutId) {
            clearTimeout(this.timeoutId);
            this.timeoutId = null;
        }
    }
}

// 使用
const timer = new RecursiveTimer();
// timer.start();
// 10秒后停止
// setTimeout(() => timer.stop(), 10000);

2. 延迟加载和懒执行

class LazyLoader {
    constructor() {
        this.loadedResources = new Set();
    }
    
    // 延迟加载资源
    loadResource(resource, delay = 0) {
        console.log(`资源 ${resource.name} 计划在 ${delay}ms 后加载`);
        
        setTimeout(() => {
            if (!this.loadedResources.has(resource.name)) {
                // 模拟加载资源
                console.log(`正在加载:${resource.name}`);
                this.loadedResources.add(resource.name);
                
                // 资源加载完成后的回调
                if (resource.callback) {
                    resource.callback(resource);
                }
            }
        }, delay);
    }
    
    // 批量延迟加载
    loadResources(resources, baseDelay = 1000) {
        resources.forEach((resource, index) => {
            const delay = baseDelay * (index + 1);
            this.loadResource(resource, delay);
        });
    }
}

// 使用示例
const loader = new LazyLoader();

const resources = [
    { name: '图片1.jpg', type: 'image', size: '2MB' },
    { name: '图片2.jpg', type: 'image', size: '3MB' },
    { name: '视频.mp4', type: 'video', size: '50MB', callback: (r) => {
        console.log(`${r.name} 加载完成,开始播放`);
    }},
    { name: '文档.pdf', type: 'document', size: '1MB' }
];

// 每隔1秒加载一个资源
loader.loadResources(resources, 1000);

3. 实现简单的任务队列

class TaskQueue {
    constructor(concurrency = 1) {
        this.queue = [];
        this.running = 0;
        this.concurrency = concurrency;
        this.delayedTasks = [];
    }
    
    addTask(task, delay = 0) {
        if (delay > 0) {
            // 延迟添加任务
            setTimeout(() => {
                this.queue.push(task);
                this.processQueue();
            }, delay);
        } else {
            this.queue.push(task);
            this.processQueue();
        }
    }
    
    processQueue() {
        if (this.running >= this.concurrency || this.queue.length === 0) {
            return;
        }
        
        const task = this.queue.shift();
        this.running++;
        
        console.log(`开始执行任务,当前并发:${this.running}`);
        
        // 模拟异步任务
        setTimeout(() => {
            task();
            this.running--;
            console.log(`任务完成,剩余并发:${this.running}`);
            this.processQueue(); // 继续处理下一个
        }, Math.random() * 2000 + 1000);
    }
    
    addDelayedTask(task, executeDelay) {
        console.log(`任务将在 ${executeDelay}ms 后执行`);
        
        setTimeout(() => {
            this.addTask(task);
        }, executeDelay);
    }
}

// 使用示例
const queue = new TaskQueue(2); // 同时执行2个任务

for (let i = 1; i <= 5; i++) {
    queue.addTask(() => {
        console.log(`执行任务 ${i}`);
    }, i * 1000); // 每个任务延迟不同时间加入队列
}

queue.addDelayedTask(() => {
    console.log('这是延迟添加的特殊任务');
}, 8000);

六、常见和注意事项

1. this 指向问题

// 错误示例
const obj = {
    name: '测试对象',
    greet: function() {
        setTimeout(function() {
            console.log(`Hello, ${this.name}`); // this 指向 window/undefined
        }, 1000);
    }
};
obj.greet(); // 输出: Hello, undefined

// 解决方案1:箭头函数
const obj2 = {
    name: '测试对象',
    greet: function() {
        setTimeout(() => {
            console.log(`Hello, ${this.name}`); // this 继承自外部
        }, 1000);
    }
};

// 解决方案2:bind
const obj3 = {
    name: '测试对象',
    greet: function() {
        setTimeout(function() {
            console.log(`Hello, ${this.name}`);
        }.bind(this), 1000);
    }
};

// 解决方案3:保存 this
const obj4 = {
    name: '测试对象',
    greet: function() {
        const self = this;
        setTimeout(function() {
            console.log(`Hello, ${self.name}`);
        }, 1000);
    }
};

2. 小延迟时间

// 实际小延迟是4ms
console.time('timer1');
setTimeout(() => {
    console.timeEnd('timer1'); // 显示4ms以上
}, 0);

// 嵌套超时的小延迟
let start = Date.now();
setTimeout(function nested() {
    const end = Date.now();
    console.log(`实际延迟:${end - start}ms`);
    
    if (end - start < 100) {
        start = end;
        setTimeout(nested, 0);
    }
}, 0);

3. 定时器不准的问题

// setTimeout 不保证精确的时间
class AccurateTimer {
    constructor(callback, interval) {
        this.callback = callback;
        this.interval = interval;
        this.expected = 0;
        this.timeoutId = null;
    }
    
    start() {
        this.expected = Date.now() + this.interval;
        this.schedule();
    }
    
    schedule() {
        const drift = Date.now() - this.expected;
        this.timeoutId = setTimeout(() => {
            this.callback();
            this.expected += this.interval;
            this.schedule();
        }, Math.max(0, this.interval - drift));
    }
    
    stop() {
        clearTimeout(this.timeoutId);
    }
}

// 使用
const timer = new AccurateTimer(() => {
    console.log('滴答', new Date().toLocaleTimeString());
}, 1000);

// timer.start();
// 10秒后停止
// setTimeout(() => timer.stop(), 10000);

七、性能优化和实践

1. 清理不再需要的定时器

class Component {
    constructor() {
        this.timers = new Set();
    }
    
    setTimeout(callback, delay) {
        const id = setTimeout(() => {
            this.timers.delete(id);
            callback();
        }, delay);
        
        this.timers.add(id);
        return id;
    }
    
    clearAllTimeouts() {
        this.timers.forEach(id => clearTimeout(id));
        this.timers.clear();
        console.log('所有定时器已清理');
    }
    
    // 组件销毁时调用
    destroy() {
        this.clearAllTimeouts();
    }
}

// 使用
const comp = new Component();
comp.setTimeout(() => console.log('任务1'), 1000);
comp.setTimeout(() => console.log('任务2'), 2000);
comp.setTimeout(() => console.log('任务3'), 3000);

// 组件被销毁
setTimeout(() => {
    comp.destroy();
}, 2500);
// 任务3不会执行

2. 使用 requestAnimationFrame 代替 setTimeout 做动画

class SmoothAnimation {
    constructor(element) {
        this.element = element;
        this.position = 0;
        this.requestId = null;
    }
    
    // 不好的做法:用 setTimeout 做动画
    badAnimate(target, duration = 1000) {
        const start = this.position;
        const startTime = Date.now();
        
        const step = () => {
            const elapsed = Date.now() - startTime;
            this.position = start + (target - start) * (elapsed / duration);
            this.element.style.transform = `translateX(${this.position}px)`;
            
            if (elapsed < duration) {
                setTimeout(step, 16); // 约60fps
            }
        };
        
        step();
    }
    
    // 好的做法:用 requestAnimationFrame
    goodAnimate(target, duration = 1000) {
        const start = this.position;
        const startTime = performance.now();
        
        const step = (currentTime) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);
            
            this.position = start + (target - start) * progress;
            this.element.style.transform = `translateX(${this.position}px)`;
            
            if (progress < 1) {
                this.requestId = requestAnimationFrame(step);
            }
        };
        
        this.requestId = requestAnimationFrame(step);
    }
    
    stop() {
        if (this.requestId) {
            cancelAnimationFrame(this.requestId);
        }
    }
}

八、面试常见问题

// 1. 下面的代码输出什么?
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出顺序:1, 3, 2

// 2. 如何实现 sleep 函数?
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function () {
    console.log('开始');
    await sleep(2000);
    console.log('2秒后');
}

// 3. setTimeout 和 setInterval 的区别
function showDifference() {
    // setInterval:固定间隔执行
    setInterval(() => {
        console.log('setInterval 每1秒执行一次');
    }, 1000);
    
    // setTimeout 递归:上一个执行完后再等1秒
    function timeoutLoop() {
        setTimeout(() => {
            console.log('setTimeout 循环执行');
            timeoutLoop();
        }, 1000);
    }
    timeoutLoop();
}

九、学习编程的建议

学习 setTimeout 可以按照这个顺序:

  1. 先掌握基础用法:延迟执行、取消定时器

  2. 理解异步执行机制:事件循环、宏任务

  3. 结合实际场景练习:防抖、轮询、动画

  4. 注意常见:this指向、内存泄漏

  5. 对比他方案:requestAnimationFrame、Web Worker

// 练习:实现一个简单的倒计时
class Countdown {
    constructor(seconds, onTick, onComplete) {
        this.remaining = seconds;
        this.onTick = onTick;
        this.onComplete = onComplete;
        this.timeoutId = null;
    }
    
    start() {
        this.tick();
    }
    
    tick() {
        if (this.remaining <= 0) {
            this.onComplete.();
            return;
        }
        
        this.onTick.(this.remaining);
        this.remaining--;
        
        this.timeoutId = setTimeout(() => this.tick(), 1000);
    }
    
    stop() {
        if (this.timeoutId) {
            clearTimeout(this.timeoutId);
        }
    }
}

new Countdown(
    5,
    (remaining) => console.log(`剩余:${remaining}秒`),
    () => console.log('倒计时结束!')
).start();

setTimeout 看似简单,但深入理解后会发现它涉及 JavaScript 事件循环、异步编程、性能优化等多个重要概念。掌握好它,对理解整个 JavaScript 运行机制都有帮助。

记住:定时器不是精确的时间控制工具,而是"至少延迟指定时间后执行"的机制。在需要精确计时的场景(如动画、游戏),要考虑使用更合适的技术。

← JavaScript高阶函数 JavaScript setInterval() →
分享笔记 (共有 篇笔记)
验证码:
微信公众号