目錄
 引言
 一、為什麼會有異步?
 二、什麼是事件佇列?
 三、事件队列有何作用?
# 總結
首頁 web前端 js教程 為什麼會有異步? 什麼是事件隊列?

為什麼會有異步? 什麼是事件隊列?

Jun 21, 2017 pm 01:26 PM
javascript js 事件 循環 深入 理解

 引言

  相信所有學過JavaScript 都知道它是一門單線程的語言,這也意味著JS 無法進行多線程編程,但是JS 當中卻有著無處不在的異步概念。在初期許多人會把非同步理解成類似多執行緒的程式模式,其實他們中有著很大的差別,要完全理解異步,就需要了解JS 的運行核心-事件循環(event loop)。在之前我對事件循環的認識也是一知半解的,直到我看了Philip Roberts 的演講 What the heck is the event loop anyway?,我才對事件循環有了一個全面的認識,所以我想寫一篇介紹JS事件循環的文章,供大家學習和參考。

 一、為什麼會有異步?

  為什麼 JS 當中會有異步?我們想像一下,如果我們同步的執行一下程式碼會發生什麼:

1 $.get(url, function(data) {2     //do something3 });
登入後複製

  在我們使用ajax 進行通訊的時候,我們都預設了它是異步的,但是如果我們設定其為同步執行,會發生什麼事?如果你自己寫一個小的測試程序,將後台程式碼延遲5s你會發現瀏覽器會出現阻塞,直到 ajax 回應了之後才會正常運作。這便是非同步模式要解決的首要問題,如何使瀏覽器非阻塞的運作任務。想像一下如果我們同步的執行ajax 請求的話,我們的等待的時間是一個未知數,在網絡通信中可能很快也可能很慢,也可能永遠也不會響應,這也就會導致瀏覽器會阻塞在一個未知的任務上面,這也是我們不希望看到的。所以我們希望有一種方式能夠異步的處理程序,我們並不需要關心一個ajax 請求會在何時完成,甚至它可以永遠不會響應,我們只需要知道在請求響應後該如何處理,並且在等待響應的這段時間內我們還可以做一些其他的工作。因此,便有了 JavaScript Event Loop。

 二、什麼是事件佇列?

  首先,我們先來看一段簡單的程式碼:

1 console.log("script start");2 3 setTimeout(function () {4     console.log("setTimeout");5 }, 1000);6 7 console.log("script end");
登入後複製

  你可以在這裡查看結果:

   我們可以看到,首先,程式輸出'script start''script end',大約1s之後輸出了'setTimeout' 。程式的 'script end' 並沒有等待1s之後輸出,而是立即輸出。這是因為 setTimeout 是一個非同步的函數。意思也就是說當我們設定延遲函數的時候,目前腳本並不會阻塞,它只是會在瀏覽器的事件表中進行記錄,程式會繼續向下執行。當延遲的時間結束之後,事件表會將回呼函數加入到事件佇列(task queue)中,事件佇列拿到了任務過後便將任務壓入執行堆疊(stack)當中,執行堆疊執行任務,輸出'setTimeout'

  事件隊列是一個存放著待執行任務的隊列,其中的任務嚴格按照時間先後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行。事件佇列每次僅執行一個任務,在該任務執行完畢之後,再執行下一個任務。執行棧則是類似函數呼叫堆疊的運行容器,當執行棧為空時,JS 引擎便檢查事件佇列,如果不為空的話,事件佇列便將第一個任務壓入執行棧中執行。

  現在,我們對上面的程式碼做一點修改:

1 console.log("script start");2 3 setTimeout(function () {4     console.log("setTimeout");5 }, 0);6 7 console.log("script end");
登入後複製

  將延遲時間設為0,看看程式會以何種順序輸出?無論我們設定多少的延遲時間,'setTimeout' 總是會在 'script end' 之後輸出。有些瀏覽器可能會有一個最小延遲時間,有的是15ms,有的是10ms,這個在很多書當中都有提到,這可能會給同學們造成一種錯覺:由於程式運行速度很快,並且有最小延遲時間,所以'setTimeout' 會在'script end' 之後輸出。現在讓我們在稍微變一下,來消除你的錯覺:

 1 console.log("script start"); 2  3 setTimeout(function () { 4     console.log("setTimeout"); 5 }, 0); 6  7 //具体数字不定,这取决于你的硬件配置和浏览器 8 for(var i = 0; i < 999999999; i ++){ 9     //do something10 }11 12 console.log("script end");
登入後複製

  你可以在这里查看结果:


   可以看出,无论后面我们做了多少延迟性的工作,'setTimeout' 总是会在 'script end' 之后输出。所以究竟发生了什么?这是因为 setTimeout 的回调函数只是会被添加至事件队列,而不是立即执行。由于当前的任务没有执行结束,所以 setTimeout 任务不会执行,直到输出了 'script end' 之后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。


  执行栈则像是函数的调用栈,是一个树状的栈:


 三、事件队列有何作用?

  通过以上的 demo 相信同学们都会对事件队列和执行栈有了一个基本的认识,那么事件队列有何作用?最简单易懂的一点就是之前我们所提到的异步问题。由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。所以事件循环的运行机制大致分为以下步骤:

  1.   检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行 2;

  2.   取出事件队列的首部,压入执行栈;

  3.        执行任务;

  4.        检查执行栈,如果执行栈为空,则跳回第 1 步;如不为空,则继续检查;

  然而目前为止我们讨论的仅仅是 JS 引擎如何执行 JS 代码,现在我们结合 Web APIs 来讨论事件循环在当中扮演的角色。

  在开始我们讨论过 ajax 技术的异步性和同步性,通过事件循环机制,我们则不需要等待 ajax 响应之后再进行工作。我们则是设置一个回调函数,将 ajax 请求挂起,然后继续执行后面的代码,至于请求何时响应,对我们的程序不会有影响,甚至它可能永远也不响应,也不会使浏览器阻塞。而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。事件监听器的回调函数也是一个任务,当我们注册了一个事件监听器时,浏览器事件表会进行登记,当我们触发事件时,事件表便将回调函数添加至事件队列当中。


  我们知道 DOM 操作会触发浏览器对文档进行渲染,如修改排版规则,修改背景颜色等等,那么这类操作是如何在浏览器当中奏效的?至此我们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的情况,这也使 JS 动画成为可能, jQuery 动画在底层均是使用 setTimeout 和 setInterval 来进行实现。想象一下如果我们同步的执行动画,那么我们不会看见任何渐变的效果,浏览器会在任务执行结束之后渲染窗口。反之我们使用异步的方法,浏览器会在每一个任务执行结束之后渲染窗口,这样我们就能看见动画的渐变效果了。

  考虑如下两种遍历方式:

 1 var arr = new Array(999); 2 arr.fill(1); 3 function asyncForEach(array, handler){ 4     var t = setInterval(function () { 5         if(array.length === 0){ 6             clearInterval(t); 7         }else { 8             handler(arr.shift()); 9         }10     }, 0);11 }12 13 //异步遍历14 asyncForEach(arr, function (value) {15     console.log(value);16 });17 18 //同步遍历19 arr.forEach(function (value, index, arr) {20     console.log(value);21 });
登入後複製

  經過測試,我們可以看出,採用同步遍歷的方法,當數組長度上升到3位數的時候,便會出現阻塞,但是異步遍歷卻不會出現阻塞現象(除非數組長度非常大,那是因為計算機的記憶體空間不足)。這是因為同步遍歷方法是一個單獨的任務,這個任務會將所有的陣列元素遍歷一遍,然後才會開始下一個任務。而非同步遍歷的方法將每一次遍歷拆分成一個單獨的任務,一個任務只遍歷一個數組元素,所以在每個任務之間,我們的瀏覽器可以進行渲染,所以我們不會看見阻塞的情況。下面這個 demo 示範了在非同步遍歷前後發生的事情:


# 總結

  現在,相信你已經認識了 JavaScript 的真實面目了吧。 JavaScript 是一門單線程的語言,但是其事件循環的特性使得我們可以異步的執行程序。這些非同步的程式也就是一個又一個獨立的任務,這些任務包含了 setTimeout、setInterval、ajax、eventListener 等等。關於事件循環,我們需要記住以下幾點:

  •   事件佇列嚴格按照時間先後順序將任務壓入執行堆疊執行;

  •        當執行堆疊為空時,瀏覽器會一直不停的檢查事件佇列,如果不為空,則取出第一個任務;

  •        在每個任務結束之後,瀏覽器會對頁面進行渲染;

  本文demo 放在jsfiddle 上,如需轉載,註明下出處就好了。若您發現本文有所紕漏,歡迎在評論區指出。

以上是為什麼會有異步? 什麼是事件隊列?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

lambda表達式跳出循環 lambda表達式跳出循環 Feb 20, 2024 am 08:47 AM

lambda表達式跳出循環,需要具體程式碼範例在程式設計中,循環結構是常用的一種重要語法。然而,在特定的情況下,我們可能希望在循環體內滿足某個條件時,跳出整個循環,而不是僅僅終止當前的循環迭代。在這個時候,lambda表達式的特性可以幫助我們達成跳脫循環的目標。 lambda表達式是一種匿名函數的宣告方式,它可以在內部定義簡單的函數邏輯。它與普通的函數聲明不同,

jQuery中如何實作select元素的改變事件綁定 jQuery中如何實作select元素的改變事件綁定 Feb 23, 2024 pm 01:12 PM

jQuery是一個受歡迎的JavaScript函式庫,可以用來簡化DOM操作、事件處理、動畫效果等。在web開發中,常常會遇到需要對select元素進行改變事件綁定的情況。本文將介紹如何使用jQuery實作對select元素改變事件的綁定,並提供具體的程式碼範例。首先,我們需要使用標籤來建立一個包含選項的下拉式選單:

PHP傳回數組所有值,組成一個數組 PHP傳回數組所有值,組成一個數組 Mar 21, 2024 am 09:06 AM

這篇文章將為大家詳細講解有關PHP返回數組所有值,組成一個數組,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章後可以有所收穫。使用array_values()函數array_values()函數傳回陣列中所有值的陣列。它不會保留原始數組的鍵。 $array=[&quot;foo&quot;=&gt;&quot;bar&quot;,&quot;baz&quot;=&gt;&quot;qux&quot;];$values=array_values($array);//$values將是[&quot;bar&quot;,&quot;qux&quot;]使用迴圈可以使用循環手動獲取數組的所有值並將其新增至一個新

如何使用 PHP 建立基於事件的應用程式 如何使用 PHP 建立基於事件的應用程式 May 04, 2024 pm 02:24 PM

在PHP中建構基於事件的應用程式的方法包括:使用EventSourceAPI建立事件來源,並在客戶端使用EventSource物件監聽事件。使用伺服器傳送的事件(SSE)傳送事件,並在客戶端使用XMLHttpRequest物件監聽事件。一個實用的例子是在電子商務網站中使用EventSource即時更新庫存計數,在伺服器端透過隨機更改庫存並發送更新來實現,客戶端則透過EventSource監聽庫存更新並即時顯示。

Java Iterator 與 Iterable:邁入編寫優雅程式碼的行列 Java Iterator 與 Iterable:邁入編寫優雅程式碼的行列 Feb 19, 2024 pm 02:54 PM

Iterator介面Iterator介面是一個用於遍歷集合的介面。它提供了幾個方法,包括hasNext()、next()和remove()。 hasNext()方法傳回布林值,指示集合中是否還有下一個元素。 next()方法傳回集合中的下一個元素,並將其從集合中刪除。 remove()方法從集合中刪除目前元素。以下程式碼範例示範如何使用Iterator介面來遍歷集合:Listnames=Arrays.asList("John","Mary","Bob");Iterator

深入研究jQuery中的關閉按鈕事件 深入研究jQuery中的關閉按鈕事件 Feb 24, 2024 pm 05:09 PM

深入理解jQuery中的關閉按鈕事件在前端開發過程中,經常會遇到需要實現關閉按鈕功能的情況,例如關閉彈跳窗、關閉提示框等。而在使用jQuery這個流行的JavaScript函式庫時,實作關閉按鈕事件也變得異常簡單又方便。本文將深入探討如何利用jQuery來實現關閉按鈕事件,並提供具體的程式碼範例,幫助讀者更好地理解和掌握這個技術。首先,我們需要了解在HTML中如何定

See all articles