我处于一个尴尬的境地
我已经使用纯JavaScript工作了将近3年,我知道JavaScript是单线程语言,
并且可以通过使用setInterval
和setTimeout
函数来模拟异步执行。
但是当我思考它们如何工作时,我不能清楚地理解它们。那么这些函数会如何影响执行上下文呢?
我认为在特定时间只运行代码的一部分,然后切换到另一部分。如果是这样的话,那么大量的setInterval
或setTimeout
调用会影响性能吗?
我处于一个尴尬的境地
我已经使用纯JavaScript工作了将近3年,我知道JavaScript是单线程语言,
并且可以通过使用setInterval
和setTimeout
函数来模拟异步执行。
但是当我思考它们如何工作时,我不能清楚地理解它们。那么这些函数会如何影响执行上下文呢?
我认为在特定时间只运行代码的一部分,然后切换到另一部分。如果是这样的话,那么大量的setInterval
或setTimeout
调用会影响性能吗?
Javascript是单线程的,但浏览器不是。浏览器至少有三个线程:Javascript引擎线程、UI线程和计时线程,其中setTimeout
和setInterval
的计时是由计时线程完成的。
调用setTimeout
或setInterval
时,浏览器中的计时器线程开始倒计时,并在时间到达后将回调函数放入Javascript线程的执行栈中。如果在时间到达时正在执行其他耗时的函数,则setTimeout
的回调函数将无法及时完成。
浏览器有一个定时器函数的 API,就像事件处理函数一样。
'click'
'scroll'
假设您在应用程序中有以下代码:
function listener(){
...
}
setTimeout(listener, 300)
function foo(){
for(var i = 0; i < 10000; i++){
console.log(i)
}
}
foo()
![查看JavaScript中函数执行的工作原理][1] [1]: https://istack.dev59.com/j6M6b.webp
根据我们以上编写的代码,此时我们的调用堆栈(Call Stack)将如下所示
调用堆栈 -> foo
假设foo需要1秒才能完成它的执行,由于我们在代码中定义了一个1秒的timeout,并在“foo”完成执行之前即在300ms运行它
那么会发生什么呢?
JavaScript会停止执行foo并开始执行setTimeout吗?
不是这样的
正如我们已经知道的那样,JavaScript是单线程的,因此必须在继续之前完成foo的执行,但是浏览器如何确保在foo执行完毕后“setTimeout”将执行呢?
这就是JavaScript魔法的作用
当300毫秒过去时,浏览器的“Timer API”启动并将超时处理程序放入“消息队列(Message Queue)”中。
此时上面的图片中的“消息队列”将如下所示
消息队列 -> setTimout:listner
然后
调用堆栈 -> foo
当“调用堆栈”变为空即foo完成执行时,上图中的“Event Loop”将从消息队列中获取消息并将其推送到堆栈中
“Event Loop”的唯一工作是在“调用堆栈”变为空且“消息队列”中有条目时,将消息从“消息队列”中出列并将其推送到“调用堆栈”中
此时上面的图片中的消息队列将如下所示
消息队列 ->
然后
调用堆栈 -> listener
这就是setTimeout和setInterval的工作原理,即使我们在setTimeout中指定了300ms,它也会在foo完成执行后执行,即在此案例中1秒后执行。
这就是为什么在setTimeout/setInterval中指定的计时器表示函数执行的最小时间延迟。
Javascript是单线程的,但浏览器不是。
有1个栈用于执行函数和语句。 有1个队列用于排队等待执行的函数。 还有Web API可以在特定时间内保持函数,这些时间定义在setTimeout和setInterval的事件表中。
当Javascript引擎逐行执行Js文件时,如果它找到一行声明或函数调用,它会将其加载到堆栈中并执行。但如果它是setTimeout或setInterval调用,则TIME API(浏览器的Web API之一)会取出与setTimeout或setInterval关联的函数处理程序并保留它。直到规定时间结束。
一旦时间结束,Time Api将该函数放在执行队列的末尾。
现在,该函数的执行取决于队列中前面的其他函数调用。
注意:这个函数调用是在window对象上调用的。
setTimeout(function () {console.log(this)}, 300)
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
JavaScript是一种单线程的脚本语言,因此它每次只能执行一段代码(由于其单线程的本质),这些代码块中的每一个都会“阻塞”其他异步事件的进程。这意味着当异步事件发生时(例如鼠标点击、定时器触发或XMLHttpRequest完成),它会被排队等待以便稍后执行。
setTimeout() 当你使用setTimeout()时,它只有在队列中轮到它的时候才会执行,如果先前的事件(setTimeout事件)由于某种原因而阻塞,则setTimeout可能会延迟超过setTimeout()函数中指定的时间。在执行setTimeout回调函数期间,如果出现任何事件(例如单击事件),它会被排队等待稍后执行。
setTimeout(function(){
/* Some long block of code... */
setTimeout(arguments.callee, 10);
}, 10);
setInterval(function(){
/* Some long block of code... */
}, 10);
setInterval()
setInterval()与setTimeout类似,但会在每次调用函数时(每隔一定时间)继续执行,直到被取消。
setTimeout代码执行前,至少会有10ms的延迟(可能会更长,但不会更短),而setInterval会尝试在每10ms执行一次回调函数,无论上一个回调函数何时执行。
如果计时器被阻止立即执行,它将被延迟到下一个可能的执行点(这将比所需的延迟更长)。如果间隔函数需要执行的时间足够长(大于指定的延迟时间),则它们可以连续执行而没有延迟。
关于我的经验,如果你要使用setTimeout(),该函数总是会被延迟执行(我指的是稍后执行),或者在未被“延时”的其他代码之后执行。即使是delay=0的函数也可能会出现这种情况,这是由事件循环引起的。
下面是示例:
setTimeout(function() {
console.log('Second');
}, 0)
console.log('First');
while(true);
,那么超时和间隔永远不会运行。 - Jon