如何通過setTimeout理解JS運行機制詳解

来源:https://www.cnblogs.com/good10000/archive/2019/03/23/10585260.html
-Advertisement-
Play Games

setTimeout()函數:用來指定某個函數或某段代碼在多少毫秒之後執行。它返回一個整數,表示定時器timer的編號,可以用來取消該定時器。 例子 ? 1 2 3 4 5 console.log(1); setTimeout(function () { console.log(2); }, 0); ...


setTimeout()函數:用來指定某個函數或某段代碼在多少毫秒之後執行。它返回一個整數,表示定時器timer的編號,可以用來取消該定時器。

例子

?
1 2 3 4 5 console.log(1); setTimeout(function () {  console.log(2); }, 0); console.log(3);

問:最後的列印順序是什麼?(如果不瞭解js的運行機制就會答錯)

正確答案:1 3 2

解析:無論setTimeout的執行時間是0還是1000,結果都是先輸出3後輸出2,這就是面試官常常考查的js運行機制的問題,接下來我們要引入一個概念,JavaScript 是單線程的。

二、 JavaScript 單線程jquery特效

JavasScript引擎是基於事件驅動和單線程執行的,JS引擎一直等待著任務隊列中任務的到來,然後加以處理,瀏覽器無論什麼時候都只有一個JS線程在運行程式,即主線程。

通俗的說:JS在同一時間內只能做一件事,這也常被稱為 “阻塞式執行”。

任務隊列

那麼單線程的JavasScript是怎麼實現“非阻塞執行”呢?

答:非同步容易實現非阻塞,所以在JavaScript中對於耗時的操作或者時間不確定的操作,使用非同步就成了必然的選擇。
諸如事件點擊觸發回調函數、ajax通信、計時器這種非同步處理是如何實現的呢?

答:任務隊列

所有任務可以分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。

任務隊列:一個先進先出的隊列,它裡面存放著各種事件和任務。

同步任務

同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。

  • 輸出
  • 如:console.log()
  • 變數的聲明
  • 同步函數:如果在函數返回的時候,調用者就能夠拿到預期的返回值或者看到預期的效果,那麼這個函數就是同步的。

非同步任務

  • setTimeout和setInterval
  • DOM事件
  • Promise
  • process.nextTick
  • fs.readFile
  • http.get
  • 非同步函數:如果在函數返回的時候,調用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那麼這個函數就是非同步的。

除此之外,任務隊列又分為macro-task(巨集任務)與micro-task(微任務),在ES5標準中,它們被分別稱為task與job。

巨集任務

  1. I/O
  2. setTimeout
  3. setInterval
  4. setImmdiate
  5. requestAnimationFrame

微任務

  1. process.nextTick
  2. Promise
  3. Promise.then
  4. MutationObserver

巨集任務和微任務的執行順序

一次事件迴圈中,先執行巨集任務隊列里的一個任務,再把微任務隊列里的所有任務執行完畢,再去巨集任務隊列取下一個巨集任務執行。

註:在當前的微任務沒有執行完成時,是不會執行下一個巨集任務的。

三、setTimeout運行機制jQuery插件

setTimeout 和 setInterval的運行機制是將指定的代碼移出本次執行,等到下一輪 Event Loop 時,再檢查是否到了指定時間。如果到了,就執行對應的代碼;如果不到,就等到再下一輪 Event Loop 時重新判斷。

這意味著,setTimeout指定的代碼,必須等到本次執行的所有同步代碼都執行完,才會執行。

優先關係:非同步任務要掛起,先執行同步任務,同步任務執行完畢才會響應非同步任務。

四、進階

?
1 2 3 4 5 console.log('A'); setTimeout(function () {  console.log('B'); }, 0); while (1) {}

大家再猜一下這段程式輸出的結果會是什麼?

答:A

註:建議先註釋掉while迴圈代碼塊的代碼,執行後強制刪除進程,不然會造成“假死”。

同步隊列輸出A之後,陷入while(true){}的死迴圈中,非同步任務不會被執行。

類似的,有時addEventListener()方法監聽點擊事件click,用戶點了某個按鈕會卡死,就是因為當前JS正在處理同步隊列,無法將click觸發事件放入執行棧,不會執行,出現“假死”。

五、圖片插件定時獲取介面更新數據

?
1 2 3 4 5 for (var i = 0; i < 4; i++) {  setTimeout(function () {  console.log(i);  }, 1000); }

輸出結果為,隔1s後一起輸出:4 4 4 4

for迴圈是一個同步任務,為什麼連續輸出四個4?

答:因為有隊列插入的時間,即使執行時間從1000改成0,還是輸出四個4。

那麼這個問題是如何產生和解決的呢?請接著閱讀

非同步隊列執行的時間

執行到非同步任務的時候,會直接放到非同步隊列中嗎?

答案是不一定的。

因為瀏覽器有個定時器(timer)模塊,定時器到了執行時間才會把非同步任務放到非同步隊列。
for迴圈體執行的過程中並沒有把setTimeout放到非同步隊列中,只是交給定時器模塊了。4個迴圈體執行速度非常快(不到1毫秒)。定時器到了設置的時間才會把setTimeout語句放到非同步隊列中。

即使setTimeout設置的執行時間為0毫秒,也按4毫秒算。

這就解釋了上題為什麼會連續輸出四個4的原因。

HTML5 標準規定了setTimeout()的第二個參數的最小值,即最短間隔,不得低於4毫秒。如果低於這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。

利用閉包實現 setTimeout 間歇調用

?
1 2 3 4 5 6 7 for (let i = 0; i < 4; i++) {  (function (j) {  setTimeout(function () {   console.log(j);  }, 1000 * i)  })(i); }

執行後,會隔1s輸出一個值,分別是:0 1 2 3

  • 此方法巧妙利用IIFE聲明即執行的函數表達式來解決閉包造成的問題。
  • 將var改為let,使用了ES6語法。

這裡也可以用setInterval()方法來實現間歇調用。

詳見:setTimeout和setInterval的區別

彈出層插件利用JS中基本類型的參數傳遞是按值傳遞的特征實現

?
1 2 3 4 5 6 7 8 9 var output = function (i) {  setTimeout(function () {  console.log(i);    }, 1000 * i) } for (let i = 0; i < 4; i++) {  output(i); }

執行後,會隔1s輸出一個值,分別是:0 1 2 3

實現原理:傳過去的i值被覆制了。

基於Promise的解決方案

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const tasks = [];   const output = (i) => new Promise((resolve) => {  setTimeout(() => {  console.log(i);  resolve();  }, 1000 * i);   });   //生成全部的非同步操作 for (var i = 0; i < 5; i++) {  tasks.push(output(i)); } //同步操作完成後,輸出最後的i Promise.all(tasks).then(() => {  setTimeout(() => {  console.log(i);  }, 1000) })

執行後,會隔1s輸出一個值,分別是:0 1 2 3 4 5

優點:提高了代碼的可讀性。

註意:如果沒有處理Promise的reject,會導致錯誤被丟進黑洞。

使用ES7中的async await特性的解決方案(推薦)

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const sleep = (timeountMS) => new Promise((resolve) => {  setTimeout(resolve, timeountMS); });   (async () => { //聲明即執行的async  for (var i = 0; i < 5; i++) {  await sleep(1000);  console.log(i);  }    await sleep(1000);  console.log(i);   })();

執行後,會隔1s輸出一個值,分別是:0 1 2 3 4 5

六、事件迴圈 Event Loop

主線程從任務隊列中讀取事件,這個過程是迴圈不斷的,所以整個的這種運行機制又稱為Event Loop。

有時候 setTimeout明明寫的延時3秒,實際卻5,6秒才執行函數,這又是因為什麼?

答:setTimeout 並不能保證執行的時間,是否及時執行取決於 JavaScript 線程是擁擠還是空閑。

瀏覽器的JS引擎遇到setTimeout,拿走之後不會立即放入非同步隊列,同步任務執行之後,timer模塊會到設置時間之後放到非同步隊列中。js引擎發現同步隊列中沒有要執行的東西了,即運行棧空了就從非同步隊列中讀取,然後放到運行棧中執行。所以setTimeout可能會多了等待線程的時間。

這時setTimeout函數體就變成了運行棧中的執行任務,運行棧空了,再監聽非同步隊列中有沒有要執行的任務,如果有就繼續執行,如此迴圈,就叫Event Loop。

七、總結

JavaScript通過事件迴圈和瀏覽器各線程協調共同實現非同步。同步可以保證順序一致,但是容易導致阻塞;非同步可以解決阻塞問題,但是會改變順序性。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • window下navicat錯誤提示:1251-Client does not support authentication protocol requested by server; consider upgrading MySQL client ...
  • MongoDB資料庫的增刪查改 1.插入數據 語法: db.集合名稱.insert(document) db.table_name.insert({name:'gj',gender:1}) db.table_name.insert({_id:"20170101",name:'gj',gender:1 ...
  • 對話框(Dialog)是一個在Android系統開發中經常會用到的小視窗,它提示用戶做決定或者輸入一些東西,對話框並不填充屏幕,是一個模態(Modal)視窗。Dialog類是所有對話框的基類,應避免直接實例化對話框,通常使用它的子類(AlertDialog,DatePickerDialog or T... ...
  • 最近在學習es6的Promise,其中涉及到了Promsie的事件執行機制,因此總結了關於Promise的執行機制,若有錯誤,歡迎糾錯和討論。 在阮一峰老師的書中《es6 標準入門》對Promise的基礎知識做出了詳細的介紹,在此就不一一介紹了,直接開始整體,將Promsie中關於事件執行機制的問題 ...
  • 布爾值、null和undefined、全局對象、包裝對象、不可變的原始值和可變的對象引用。 ...
  • 簡介 每個Node應用都有一個包含該應用元數據的文件 package.json,包含應用名、版本號以及依賴等信息。 我們使用NPM從NPM庫下載並安裝第三方包。 所有下載的包以及其依賴都保存在node_modules文件夾,這個文件夾應該排除在源代碼版本控制(如git、SVN等)外。 Node的包版 ...
  • 經過這幾天的博客瀏覽,讓我見識大漲,其中有一篇讓我感觸猶深,JavaScript語言本身是沒有面向對象的,但是那些大神們卻深深的模擬出來了面向對象,讓我震撼不已。本篇博客就是在此基礎上加上自己的認知,如有錯誤,還請見諒。 具體來說實現模擬面向對象主要是利用JavaScript函數閉包這個概念css3 ...
  • js代碼暫時性死區 只要塊級作用域存在let命令,它所聲明的變數就“綁定”這個區域,不再受外部的影響。這麼說可能有些抽象,舉個例子: ? 1 2 3 4 5 var temp = 123; if(true) { console.log(temp); let temp; } ? 1 2 3 4 5 v ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...