首頁  >  文章  >  web前端  >  JavaScript單線程機制與setTimeout執行原理的介紹(附程式碼)

JavaScript單線程機制與setTimeout執行原理的介紹(附程式碼)

不言
不言轉載
2019-03-27 09:09:013385瀏覽

這篇文章帶給大家的內容是關於JavaScript單執行緒機制與setTimeout執行原理的介紹(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

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)的作用。

使用场景示例:

<input type="text" onkeydown="show(this.value)">  
<p></p>  
<script type="text/javascript">  
  function show(val) {  
    document.getElementsByTagName('p')[0].innerHTML = val;  
  }  
</script>

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

中显示出来。但是实际效果并非如此,可以发现,每按下一个字符时,

中只能显示出之前的内容,无法得到当前的字符。

修改代码:

  <input type="text" onkeydown="var self=this; setTimeout(function(){show(self.value)}, 0)">  
  <p></p>  
  <script type="text/javascript">  
    function show(val) {  
      document.getElementsByTagName('p')[0].innerHTML = val;  
    }  
  </script>

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

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

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

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

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

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

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的JavaScript视频教程栏目!

以上是JavaScript單線程機制與setTimeout執行原理的介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除