-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
setTimeout 倒计时为什么会出现误差? #21
Comments
首先setTimeout的执行会出现在事件队列里面,当在这个作用域从上到下,从左到右把当前的代码执行完之后,才会执行事件队列里面的任务,根据先进先出的原则;但是执行这些都需要一定的时间,所以才会导致倒计时会有误差哦,萌萌哒!!!!嘤嘤怪 |
setTimeout是一个异步的宏任务,当执行setTimeout时是将回调函数在指定的时间之后放入到宏任务队列。但如果此时主线程有很多同步代码在等待执行,或者微任务队列以及当前宏任务队列之前还有很多任务在排队等待执行,那么要等他们执行完成之后setTimeout的回调函数才会被执行,因此并不能保证在setTimeout中指定的时间立刻执行回调函数 |
这里涉及到JavaScript的Event Loop,首先js引擎在执行代码的时候,会把整个script看作一个宏任务,在执行内部代码的时候如果遇到同步任务就进入主线程等到它执行完成,遇到异步任务就会把它放入事件表注册,当事件满足执行条件的时候就把他的放入任务队列,当主线程空闲的时候就会依次清空任务队列,但是需要注意的是异步任务又微任务和宏任务之分,当主线程空闲时会先清空微任务列表的所有任务,然后再执行宏任务的一个任务。 |
setTimeout倒计时为什么会出现误差?首先,js是单线程,同一时间只能做一件事情。如果前面一个任务执行时间很长(比如网络请求),后面就必须的等待很长时间。为了解决这个问题,js分为同步任务和异步任务。js会先执行同步任务,执行完后,才会去执行异步任务,异步任务一般放在异步队列中。也就是执行完同步任务后,会不断从异步队列中取出要执行的任务放在主栈中执行,这个过程就称为"event-loop"。
微任务队列包括:
微任务队列执行顺序大于宏任务队列。 所以,setTimeout出现误差是因为:
|
1.由于js是单线程的,也就是阻塞的,定时可定会不准。无论setTimeout()还是setInterval(),都有问题; (微信名:RUN) |
单线程JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。 任务队列单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。 于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。) (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 Event Loop(事件循环)主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。 为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。 定时器除了放置异步任务的事件,"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。 定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。(区别在于前者指定的代码是一次性执行,后者则为反复执行。)
setInterval指定的是“开始执行”之间的间隔,并不考虑每次任务执行本身所消耗的时间。因此实际上,两次执行之间的间隔会小于指定的时间。比如,setInterval指定每 100ms 执行一次,每次执行需要 5ms,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始。 var timer = setTimeout(function f() {
// ...
timer = setTimeout(f, 1000);
}, 1000); 上面代码可以确保,下一次执行总是在本次执行结束之后的1000毫秒开始。(当然存在较小误差,不然就没这道题了。。)
答案是不会。因为必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会在下一轮事件循环一开始就执行。 setTimeout倒计时为什么会出现误差?setTimeout作为异步任务,在实现倒计时功能的时候,除了执行我们功能的实现代码,还会有主线程对任务队列的读取及执行等过程,这些过程也需要耗费一些时间,所以会因为event loop的机制出现些许误差。 参考文章(搬砖原址): 虽然楼上几位答得很不错了,但这个问题毫无疑问又是我的知识盲区,还是没有很快的弄明白是咋回事,要继续跟着小夕姐学习,加油!!! |
赞赞赞,已经充分补习了 Event-Loop~ |
setTimeuot是异步的任务,当遇到异步任务的时候会把该任务到事件表注册,当时间到的时候会把setTimeout的回调函数加入事件队列,只有当同步任务执行完成之后才会执行事件队列的任务,所以会有偏差,所以严格来说setTimeout 只是让开始执行时间不小于规定的时间。 |
setTimeout 倒计时为什么会出现误差?线程与进程
定义讲到线程,那么肯定也得说一下进程。其实在本质上,两个名词都是 CPU 工作时间片的一个描述。 进程(process) 指的是CPU 在 运行指令及加载和保存上下文所需的时间,放在应用上是指计算机中已运行的程序。 线程(thread) 是操作系统能够进行运算的最小单位。它被包含在 进程 之中,描述了执行一段指令所需的时间。
浏览器中的线程浏览器中的线程分了以下几类:
执行栈执行栈可以理解为是用来存储函数调用的栈,遵循先进后出的原则。 事件循环node端
Event Loop 6 个阶段:
浏览器端
执行顺序如下:
setTimeout 误差上面讲了定时器是属于 宏任务(macrotask) 。如果当前 执行栈 所花费的时间大于 定时器 时间,那么定时器的回调在 宏任务(macrotask) 里,来不及去调用,所有这个时间会有误差。 我们看以下代码: setTimeout(function () {
console.log('biubiu');
}, 1000);
某个执行时间很长的函数(); 如果定时器下面的函数执行要 5秒钟,那么定时器里的log 则需要 5秒之后再大圆,函数占用了当前 执行栈 ,要等执行栈执行完毕后再去读取 微任务(microtask),等 微任务(microtask) 完成,这个时候才会去读取 宏任务(macrotask) 里面的 setTimeout 回调函数执行。setInterval 同理,例如每3秒放入宏任务,也要等到执行栈的完成。 还有一种情况如下: setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
setTimeout(function() {
console.log('嘤嘤嘤');
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0); 在最新的规范里有这么一句: 所以意思就是意思就是如果timeout嵌套大于 5层,而时间间隔小于4ms,则时间间隔增加到4ms。 |
因为setTimeout是异步宏任务,如果执行栈中的执行所用的时间超过了定时器设置的间隔时间,根据事件轮询机制,需清理完执行栈,task队列才会进入主线程执行,执行所有微任务,最后才是执行宏任务,所以setTimeout开始执行时间会被延迟,出现误差。 |
setTimeout 倒计时为啥会出现误差说道这个问题,可以先了解下javascript 的事件循环 事件循环js 是单线程。为了不让某个耗时比较长的任务,比如aajax请求,让引擎一直等待返回才执行其他任务,一般将任务分为同步任务和异步任务。
Js 引擎存在管理进程的进程,会一直不停检查主线程。一旦为空就会去读取事件队列的任务。 而setTimeout 刚好是异步进程,他的执行往往要等主线程,才会去从事件队列,调用setTimeout的回调函数,所以也就导致,会与自己写的延迟时间不相符合。 进一步说一下, 任务还可以微任务和宏任务。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。 |
下面这这道题有助于我们去理解
先简单介绍几个概念。 单线程(single thread)JS的本质核心就是单线程,这也就意味着所有的任务都得排队,前面的必须处理好,后面的才执行 执行栈(execution context stack)执行栈也称为调用栈,在主线程上执行,形成一个 任务队列(queues)任务分为 宏任务和微任务(macroTask、MicroTask)宏任务
微任务
微任务队列的执行顺序先于宏任务队列,这样就很好解释开文提到的那段代码的结果
事件循环(Event Loop)主线程从
|
如果你对前面这句话不是非常理解,那么有必要了解一下 JS的运行机制。 JS的运行机制(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 (2)主线程之外,还存在"任务队列"(task queue)。 (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 (4)主线程不断重复上面的第三步。 如 如何减少
|
JS语言的一大特点就是单线程,同一个时间只能做一件事情。 所有的任务分成两类,一种是同步任务,一种是异步任务。同步任务指的是,在主线程上排队执行的任务。异步任务是不进入主线程,而进入任务队列(task queue)的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。 JS中有两种异步任务 事件循环(event-loop) 1.选择当前要执行的宏任务队列,选择一个最先进入任务队列的宏任务,如果没有宏任务可以选择,则会跳转至microtask的执行步骤。 |
总结: |
因为js是单线程的,所以的要等队列中的同步任务执行完成才会开始执行setTimeout,而不是从你打开网页算起。如果前面队列任务执行的时间较长,自然就会出现延迟。 |
1因为js是单线程的解析器,因此一定时间内只能执行一段代码。 |
js为什么是单线程的我们都知道 JavaScript 是一门 单线程 语言,也就是说同一时间只能做一件事。这是因为 JavaScript 生来作为浏览器脚本语言,主要用来处理与用户的交互、网络以及操作 DOM。这就决定了它只能是单线程的,否则会带来很复杂的同步问题。 同步和异步既然 Javascript 是单线程的,它就像是只有一个办业务,客户不得不排队一个一个的等待办理。同理 JavaScript 的任务也要一个接一个的执行,如果某个任务是个耗时任务,那浏览器岂不得一直卡着?为了防止主线程的阻塞,JavaScript 有了 同步 和 异步 的概念 异步如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。比如说发一个网络请求,我们告诉主程序等到接收到数据后再通知我,然后我们就可以去做其他的事情了。当异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,再去执行。 这也就是定时器并不能精确在指定时间后输出回调函数结果的原因setTimeout(() => { for (let i = 0; i < 100000000; i += 1) { js的两种异步任务宏任务(macrotasks)和微任务(microtasks) 任务队列单线程就意味着,所有的任务需要排队,前一个任务结束,才会执行后一个任务。如果前面一个任务耗时太长,后面的任务就得等着。 也就是说执行顺序是:开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行 -> … 这样循环往复 |
例如: setTimeout(function() {
alert("Hello world!");
}, 1000); 第一个参数是函数,第二个参数表示等待多长时间的毫秒数,但经过该时间后指定的代码不一定会执行。为什么不一定过了1000毫秒马上执行呢? |
JavaScript是单线程的,代码是从上到下执行的,前一个任务结束,后一个才能开始,这样容易形成任务阻塞。所以在js中可以使用setTimeout、setInterval、promise、async/await等异步处理任务,防止任务阻塞。 任务分成同步任务和异步任务。所有同步任务都会加入主线程,形成一个执行栈。而异步任务就会进入任务队列,异步任务又分为宏任务(macrotask)和微任务(microtask)。当异步任务执行完会通知主线程并等待。 当主线程中的所有同步任务(也属于宏任务)都执行完,等待中的异步任务就会从任务队列中进入主线程,先执行所有的微任务,异步任务中的宏任务再进入主线程执行,依次循环,形成一个event loop(事件循环)。 回到问题本身,setTimeout倒计时为什么会出现误差?setTimeout属于异步任务中的宏任务,根据JS的运行机制,若设置的延时执行时间到了,主线程中的同步任务还未执行完,setTimeout就无法执行,必须继续等待,那么就会出现误差。 |
setTimeout为什么会出现倒计时误差 首先说一下任务队列 js代码在执行的时候,会创建一个执行队列,会将代码区分为同步任务还是异步任务,从而进入不同的空间,同步任务会进入主线程,主线程的事件会立即执行,异步任务会进入任务队列,等待主线程的任务执行完毕之后 ,主线程没有正在执行的任务,那么这时主线程 或执行任务队列(我理解的,大佬称为任务队列)会通知任务队列即(所有异步任务的集合),异步任务又分为微任务与宏任务 , 执行异步任务时会先将微任务执行完毕,在去执行宏任务 依次循环 这个称之为事件循环。 常见的微任务有:
常见的宏任务有:
倒计时误差产生的原因 setTimout属于异步任务的宏任务 后面的参数代表延迟执行的时间,如果延迟时间完毕,主线程没有正在执行的任务,那么会立即执行settimeout,如果尚有正在执行的任务,那么会等待主线程任务执行完毕之后在执行,所以会产生误差。 |
参考以上各位大佬的总结 单线程:所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务不得不一直等着。任务队列:分为同步任务和异步任务。
(1)所有同步任务都在主线程上执行,形成一个执行栈。 定时器除了放置异步任务的时间,“任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。 setTimeout倒计时为什么出现误差?setTimeout作为异步任务,在实现倒计时功能的时候,除了执行我们功能的实现代码,还会有主线程对任务队列的读取及执行等过程,这些过程也需要耗费一些时间,所以因为event loop的机制出现些许误差。 |
这个问题要从根本上理解,请先认真读这篇文章,我每次读一遍,都能领悟到一些东西。 倒计时为啥会出现误差在基于上面的文章的内容,因为可能在它推入事件列表时,主线程还不空闲,正在执行其他的代码,所以自然有误差。 |
浏览器timer的最小时间延迟为什么是4ms,而不是2ms或者3ms? 但仍然不清楚为何是4ms |
....这个不是早就有解决方案了嘛. Math.round就已经解决了延误问题. 延误时差往往在4ms左右, 直接Math.round 就改成1秒整就行. 通过对比上一秒和下一秒时间戳差值. |
No description provided.
The text was updated successfully, but these errors were encountered: