基础问答
问题:你在写代码的过程中,在什么时候才会设置 setTimeout 的延时为 0?
回答:有如下几种情况
- 避免同步任务阻塞 UI,即在渲染较多数据的时候,可以通过 setTimeout 分批渲染。
- const data = new Array(1000).fill(1).map((x, idx) => idx + 1);
- function render(list) {
- let index = 0;
- for (; index < list.length; index += 100) {
- console.log('current', index);
- const current = index;
- setTimeout(() => {
- console.log(list.slice(current, current + 100).join(','))
- }, 0);
- }
- }
- render(data);
复制代码
- 获取 DOM 元素的宽高,本质是根据事件循环机制调整了代码的执行顺序。
- function App() {
- const dom = document.querySelector('#app');
- console.log(dom.height);
- setTimeout(() => dom.height, 0);
- }
复制代码
- 代码分片,古早技术,将同步代码分片执行,避免阻塞渲染。
扩展延伸
JavaScript 单线程:JavaScript 是单线程语言,这个是编程语言的设计,在同一时间只能执行一段代码,所有的任务都需要排队,而身为单线程,但是好像我们访问网页的时候还是那么快,这语言优势这么强?这是另一个问题,语言设计上是单线程,只能同步的执行代码,但是浏览器不是,他是多线程的,分出来一个 JS 主线程用于执行 JavaScript 代码,还有如 UI 线程,用于执行渲染等。在 JavaScript 中,通过事件循环来协调任务执行,实现异步编程。
事件循环:这个机制是 JavaScript 的一个核心机制,可以利用这个机制实现高并发,异步编程操作。
核心是 - 调用栈、任务队列、宏任务、微任务。
整个流程为 - JavaScript 代码按照代码依次执行时,检测到同步任务就进入调用栈执行,检测到宏任务,先压入宏任务队列,检测到微任务,则压入微任务队列,当本轮同步任务(宏任务)结束时,检测微任务队列,清空(即执行所有的微任务),这个检测的时机称为“微任务检查点”。
如图,伴随着每个宏任务执行,都有自己对应的微任务队列,直到微任务队列全部执行完成,才会开启下一个宏任务。
setTimeout(callback, delayTime) API:在执行这个 API 时,JS 引擎会将 callback 函数封装成宏任务,挂载到延迟队列中,等待执行。这里再次引入了一个新的概念,延迟队列,这个是浏览器(或者引擎)实现的,当 JavaScript 创建定时器的时候,渲染进程就会将这个定时器的任务添加到延迟队列中。执行完一个任务,计算延迟队列中是否有到期的任务,有就执行,没有继续循环。
面试追问
不会,虽然我们设置为了 0,但是 setTimeout 的回调函数会被封装成一个宏任务,所以他需要等待同步任务执行结束后,从宏任务队列中取出来执行。此外,这个延迟时间虽然可以设置为 0,但是浏览器的最小执行时间实际是不一定的,Chrome 浏览器是 4ms。
- 那延迟时间设置为 400ms,会在 400ms 时执行吗?
不会,原因同上。setTimeout 只能做到“尽快执行”,而不是“立即执行”。
- 你在使用 setTimeout 的时候,有遇到过什么问题吗?
历史代码问题,存在比较多的 setTimeout 导致代码执行的结果不好理解。
this 指针问题,setTimeout 回调函数中的 this 和直觉不符,如果执行的回调函数是一个对象的方法,那么这个对象的方法中 this 并不是指向这个对象,而是全局。
长任务阻塞延迟的回调函数调用,如果当前任务执行的时间比较长,可能会导致回调函数等待。
浏览器优化问题,现在浏览器为了降低对电量的消耗,延长续航时间,会对后台界面的 setTimeout 执行时间间隔延长,一般会大于 1s,但是遇到过更久的,有一个多小时。
有,和动画相关的可以使用 requestAnimationFrame API 来替代,可以保持和浏览器渲染频率一致,而不需要计算每帧的间隔时间来延迟执行。
微任务可以使用 Promise 来创建。
- /**
- * 用 requestAnimationFrame 实现简易 setTimeout
- * @param {number} delay - 延迟时间(毫秒)
- * @returns {number} - RAF的ID,用于取消(对应clearTimeout)
- */
- function rafSetTimeout(callback, delay) {
- // 1. 记录延迟结束的目标时间(当前时间 + 延迟时间)
- const startTime = Date.now();
- const targetTime = startTime + delay;
- // 2. 定义递归执行的RAF回调函数
- function rafCallback() {
- // 3. 检查当前时间是否达到目标时间
- if (Date.now() >= targetTime) {
- // 达到目标时间,执行用户回调
- callback();
- } else {
- // 未达到,继续递归调用RAF,等待下一次重绘
- requestAnimationFrame(rafCallback);
- }
- }
- // 4. 启动第一次RAF,开始等待
- return requestAnimationFrame(rafCallback);
- }
- /**
- * 对应 clearTimeout,取消 rafSetTimeout
- * @param {number} rafId - rafSetTimeout 返回的RAF ID
- */
- function rafClearTimeout(rafId) {
- cancelAnimationFrame(rafId);
- }
复制代码- setTimeout(() => {
- console.log('回调1');
- }, 0);
- // 插入同步任务
- console.log('同步任务');
- setTimeout(() => {
- console.log('回调2');
- }, 0);
复制代码 友情链接:webfem.com
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |