资讯专栏INFORMATION COLUMN

JS单线程与setTimeout执行原理

tabalt / 2925人阅读

摘要:引擎单线程机制首先明确,引擎是单线程机制。是单线程执行的,无法同时执行多段代码。解析是单线程的,所以会先执行再,但这个循环体是死循环,所以永远不会执行。

Javascript 引擎单线程机制

首先明确,JavaScript引擎是单线程机制。

JavaScript 是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个任务队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。

可以理解为:只有在JS线程中没有任何同步代码要执行的前提下才会执行异步代码

所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就 执行。假如当前 JavaScript 线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

浏览器的多线程机制与事件循环(event loop)

首先明确,浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:

javascript 引擎线程

GUI 渲染线程

浏览器事件触发线程

JavaScript 引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序

javascript 引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。

GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自 JavaScript 引擎当前执行的代码块如 setTimeOut,也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。(当线程中没有执行任何同步代码的前提下才会执行异步代码)

事件循环(event loop): 是用来管理我们的异步代码的,它会把它们放在一个线程池当中

JavaScript中setTimeout的实现原理

首先明确,setTimeout函数是异步代码,但其实setTimeout并不是真正的异步操作

由于JS线程的工作机制:当线程中没有执行任何同步代码的前提下才会执行异步代码,setTimeout是异步代码,所以setTimeout只能等js空闲才会执行

前面提到过,如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

也就是说setTimeout只能保证在指定的时间过后将任务(需要执行的函数)插入队列等候,并不保证这个任务在什么时候执行。执行javascript的线程会在空闲的时候,自行从队列中取出任务然后执行它。javascript 通过这种队列机制,给我们制造一个异步执行的假象。

有时setTimeout中的代码会很快得到执行,我们会感觉这段代码是在异步执行,这是因为 javascript 线程并没有因为什么耗时操作而阻塞,所以可以很快地取出排队队列中的任务然后执行它。

实例分析

在具备了上述理论基础之后,我们对以下几个实例进行分析:

===========================================

var t = true;
    
window.setTimeout(function (){
    t = false;
},1000);
    
while (t){}
    
alert("end");

运行结果:程序陷入死循环,t = false 得不到执行,因此 alert("end") 不会执行。
解析:

JS是单线程的,所以会先执行 while(t){} 再 alert,但这个循环体是死循环,所以永远不会执行alert。

为什么不执行 setTimeout?是因为JS的工作机制是:当线程中没有执行任何同步代码的前提下才会执行异步代码,setTimeout是异步代码,所以 setTimeout 只能等JS空闲才会执行,但死循环是永远不会空闲的,所以 setTimeout 也永远得不到执行。

===========================================

var start = new Date();
    
setTimeout(function(){  
    var end = new Date();  
    console.log("Time elapsed: ", end - start, "ms");  
}, 500);  
      
while (new Date - start <= 1000){}

运行结果:"Time elapsed: 1035 ms" (这里的1035不准确 但是一定是大于1000的)
解析:

JS是单线程 setTimeout 异步代码 其回调函数执行必须需等待主线程运行完毕

当while循环因为时间差超过 1000ms 跳出循环后,setTimeout 函数中的回调才得以执行

===========================================

for(var i=0;i<10;i++){
    setTimeout(function() {
        console.log(i);
    }, 0);
}

运行结果:输出10个10
解析:JS单线程 setTimeout 异步代码 任务队列
问:如何修改可以使上述代码输出 0123456789
自执行函数 或 使用ES6中的let关键字

// 自执行函数 形成闭包 记忆其被创建时的环境
for(var i=0;i<10;i++){
    setTimeout((function() {
         console.log(i);
    })(), 0);
}

setTimeout(0)函数的作用

现在我们了解了setTimeout函数执行的原理,那么它有什么作用呢?
setTimeout函数增加了Javascript函数调用的灵活性,为函数执行顺序的调度提供极大便利。
简言之,改变顺序,这正是setTimeout(0)的作用。

使用场景示例:

  

这里绑定了 keydown 事件,意图是当用户在文本框里输入字符时,将输入的内容实时地在

中显示出来。但是实际效果并非如此,可以发现,每按下一个字符时,
中只能显示出之前的内容,无法得到当前的字符。

修改代码:

    
  

这段代码使用setTimeout(0)就可以实现需要的效果了。

这里其实涉及2个任务,1个是将键盘输入的字符回写到输入框中,一个是获取文本框的值将其写入div中。第一个是浏览器自身的默认行为,一个是我们自己编写的代码。很显然,必须要先让浏览器将字符回写到文本框,然后我们才能获取其内容写到div中。改变顺序,这正是setTimeout(0)的作用。

其他应用场景:有时候,加载一些广告的时候,我们用setTimeout实现异步,好让广告不会阻塞我们页面的渲染。

setTimeout 和 setInterval 在执行异步代码的时候有着根本的不同

如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些)

如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103023.html

相关文章

  • 前端原理_异步线程

    摘要:单线程什么是单线程语言的执行环境是单线程所谓单线程,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。 单线程 什么是单线程? Javascript语言的执行环境是单线程(single thread) 所谓单线程,就是指一次只能完成一件任务。 如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。 执行JS代...

    NoraXie 评论0 收藏0
  • Js基础知识(四) - js运行原理机制

    摘要:定时器线程由于是单线程运行,所以不能抽出时间来计时,只能另开辟一个线程来处理定时器任务,等计时完成,把定时器要执行的操作添加到事件任务队列尾,等待引擎线程来处理。已经知道了是单线程运行的,也知道中有同步操作和异步操作。 js运行机制 本章了解一下js的运行原理,了解了js的运行原理才能写出更优美的代码,提高运行效率,还能解决开发中遇到的不理解的问题。 进程与线程 进程是cpu资源分配的...

    LMou 评论0 收藏0
  • Js基础知识(四) - js运行原理机制

    摘要:定时器线程由于是单线程运行,所以不能抽出时间来计时,只能另开辟一个线程来处理定时器任务,等计时完成,把定时器要执行的操作添加到事件任务队列尾,等待引擎线程来处理。已经知道了是单线程运行的,也知道中有同步操作和异步操作。 js运行机制 本章了解一下js的运行原理,了解了js的运行原理才能写出更优美的代码,提高运行效率,还能解决开发中遇到的不理解的问题。 进程与线程 进程是cpu资源分配的...

    leap_frog 评论0 收藏0
  • Js基础知识(四) - js运行原理机制

    摘要:定时器线程由于是单线程运行,所以不能抽出时间来计时,只能另开辟一个线程来处理定时器任务,等计时完成,把定时器要执行的操作添加到事件任务队列尾,等待引擎线程来处理。已经知道了是单线程运行的,也知道中有同步操作和异步操作。 js运行机制 本章了解一下js的运行原理,了解了js的运行原理才能写出更优美的代码,提高运行效率,还能解决开发中遇到的不理解的问题。 进程与线程 进程是cpu资源分配的...

    shevy 评论0 收藏0

发表评论

0条评论

tabalt

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<