JS的執行還分是誰發起的?

来源:https://www.cnblogs.com/cybozu/archive/2023/08/21/17645578.html
-Advertisement-
Play Games

##### 4 屬性選擇器 屬性選擇器是通過元素的屬性及屬性值來選擇元素的。下麵介紹屬性選擇器的用法。 1. 第一種用法 ``` 作用:選擇含有指定屬性的元素。 語法:[屬性名]{} ``` 示例如下: ```html 屬性選擇器 用戶名: 密 碼: 數據量: ``` 運行結果: ![image]( ...


這一部分首先我們考慮一下,如果我們是瀏覽器或者 Node 的開發者,我們該如何使用 JavaScript 引擎。

當拿到一段 JavaScript 代碼時,瀏覽器或者 Node 環境首先要做的就是;傳遞給 JavaScript 引擎,並且要求它去執行。

然而,執行 JavaScript 並非一錘子買賣,宿主環境當遇到一些事件時,會繼續把一段代碼傳遞給 JavaScript 引擎去執行,此外,我們可能還會提供 API 給 JavaScript 引擎,比如 setTimeout 這樣的 API,它會允許 JavaScript 在特定的時機執行。

所以,我們首先應該形成一個感性的認知:一個 JavaScript 引擎會常駐於記憶體中,它等待著我們(宿主)把 JavaScript 代碼或者函數傳遞給它執行。

在 ES3 和更早的版本中,JavaScript 本身還沒有非同步執行代碼的能力,這也就意味著,宿主環境傳遞給 JavaScript 引擎一段代碼,引擎就把代碼直接順次執行了,這個任務也就是宿主發起的任務。

但是,在 ES5 之後,JavaScript 引入了 Promise,這樣,不需要瀏覽器的安排,JavaScript 引擎本身也可以發起任務了。

由於我們這裡主要講 JavaScript 語言,那麼採納 JSC 引擎的術語,我們把宿主發起的任務稱為巨集觀任務,把 JavaScript 引擎發起的任務稱為微觀任務。

 

巨集觀和微觀任務

JavaScript 引擎等待宿主環境分配巨集觀任務,在操作系統中,通常等待的行為都是一個事件迴圈,所以在 Node 術語中,也會把這個部分稱為事件迴圈。

不過,術語本身並非我們需要重點討論的內容,我們在這裡把重點放在事件迴圈的原理上。在底層的 C/C++ 代碼中,這個事件迴圈是一個跑在獨立線程中的迴圈,我們用偽代碼來表示,大概是這樣的:

while(TRUE) {
    r = wait();
    execute(r);}

我們可以看到,整個迴圈做的事情基本上就是反覆“等待 - 執行”。當然,實際的代碼中並沒有這麼簡單,還有要判斷迴圈是否結束、巨集觀任務隊列等邏輯,這裡為了方便你理解,我就把這些都省略掉了。

這裡每次的執行過程,其實都是一個巨集觀任務。我們可以大概理解:巨集觀任務的隊列就相當於事件迴圈。

在巨集觀任務中,JavaScript 的 Promise 還會產生非同步代碼,JavaScript 必須保證這些非同步代碼在一個巨集觀任務中完成,因此,每個巨集觀任務中又包含了一個微觀任務隊列:

有了巨集觀任務和微觀任務機制,我們就可以實現 JavaScript 引擎級和宿主級的任務了,例如:Promise 永遠在隊列尾部添加微觀任務。setTimeout 等宿主 API,則會添加巨集觀任務。

接下來,我們來詳細介紹一下 Promise。

 

Promise

Promise 是 JavaScript 語言提供的一種標準化的非同步管理方式,它的總體思想是,需要進行 io、等待或者其它非同步操作的函數,不返回真實結果,而返回一個“承諾”,函數的調用方可以在合適的時機,選擇等待這個承諾兌現(通過 Promise 的 then 方法的回調)。

Promise 的基本用法示例如下:

function sleep(duration) {
    return new Promise(function(resolve, reject) {
        setTimeout(resolve,duration);
    })}sleep(1000).then( ()=> console.log("finished"));

這段代碼定義了一個函數 sleep,它的作用是等候傳入參數指定的時長。

Promise 的 then 回調是一個非同步的執行過程,下麵我們就來研究一下 Promise 函數中的執行順序,我們來看一段代碼示例:

var r = new Promise(function(resolve, reject){
    console.log("a");
    resolve()});r.then(() => console.log("c"));console.log("b")

我們執行這段代碼後,註意輸出的順序是 a b c。在進入 console.log(“b”) 之前,毫無疑問 r 已經得到了 resolve,但是 Promise 的 resolve 始終是非同步操作,所以 c 無法出現在 b 之前。

接下來我們試試跟 setTimeout 混用的 Promise。

在這段代碼中,我設置了兩段互不相干的非同步操作:通過 setTimeout 執行 console.log(“d”),通過 Promise 執行 console.log(“c”)。

var r = new Promise(function(resolve, reject){
    console.log("a");
    resolve()});setTimeout(()=>console.log("d"), 0)r.then(() => console.log("c"));console.log("b")

我們發現,不論代碼順序如何,d 必定發生在 c 之後,因為 Promise 產生的是 JavaScript 引擎內部的微任務,而 setTimeout 是瀏覽器 API,它產生巨集任務。

為了理解微任務始終先於巨集任務,我們設計一個實驗:執行一個耗時 1 秒的 Promise。

setTimeout(()=>console.log("d"), 0)var r = new Promise(function(resolve, reject){
    resolve()});r.then(() => { 
    var begin = Date.now();
    while(Date.now() - begin < 1000);
    console.log("c1") 
    new Promise(function(resolve, reject){
        resolve()
    }).then(() => console.log("c2"))});

這裡我們強制了 1 秒的執行耗時,這樣,我們可以確保任務 c2 是在 d 之後被添加到任務隊列。

我們可以看到,即使耗時一秒的 c1 執行完畢,再 enque 的 c2,仍然先於 d 執行了,這很好地解釋了微任務優先的原理。

通過一系列的實驗,我們可以總結一下如何分析非同步執行的順序:

  • 首先我們分析有多少個巨集任務;

  • 在每個巨集任務中,分析有多少個微任務;

  • 根據調用次序,確定巨集任務中的微任務執行次序;

  • 根據巨集任務的觸發規則和調用次序,確定巨集任務的執行次序;

  • 確定整個順序。

我們再來看一個稍微複雜的例子:

function sleep(duration) {
    return new Promise(function(resolve, reject) {
        console.log("b");
        setTimeout(resolve,duration);
    })}console.log("a");sleep(5000).then(()=>console.log("c"));

這是一段非常常用的封裝方法,利用 Promise 把 setTimeout 封裝成可以用於非同步的函數。

我們首先來看,setTimeout 把整個代碼分割成了 2 個巨集觀任務,這裡不論是 5 秒還是 0 秒,都是一樣的。

第一個巨集觀任務中,包含了先後同步執行的 console.log(“a”); 和 console.log(“b”);。

setTimeout 後,第二個巨集觀任務執行調用了 resolve,然後 then 中的代碼非同步得到執行,所以調用了 console.log(“c”),最終輸出的順序才是: a b c。

Promise 是 JavaScript 中的一個定義,但是實際編寫代碼時,我們可以發現,它似乎並不比回調的方式書寫更簡單,但是從 ES6 開始,我們有了 async/await,這個語法改進跟 Promise 配合,能夠有效地改善代碼結構。

 新特性:async/await

async/await 是 ES2016 新加入的特性,它提供了用 for、if 等代碼結構來編寫非同步的方式。它的運行時基礎是 Promise,面對這種比較新的特性,我們先來看一下基本用法。

async 函數必定返回 Promise,我們把所有返回 Promise 的函數都可以認為是非同步函數。

async 函數是一種特殊語法,特征是在 function 關鍵字之前加上 async 關鍵字,這樣,就定義了一個 async 函數,我們可以在其中使用 await 來等待一個 Promise。

 

function sleep(duration) {
    return new Promise(function(resolve, reject) {
        setTimeout(resolve,duration);
    })}async function foo(){
    console.log("a")
    await sleep(2000)
    console.log("b")}

這段代碼利用了我們之前定義的 sleep 函數。在非同步函數 foo 中,我們調用 sleep。

async 函數強大之處在於,它是可以嵌套的。我們在定義了一批原子操作的情況下,可以利用 async 函數組合出新的 async 函數。

function sleep(duration) {
    return new Promise(function(resolve, reject) {
        setTimeout(resolve,duration);
    })}async function foo(name){
    await sleep(2000)
    console.log(name)}async function foo2(){
    await foo("a");
    await foo("b");}

這裡 foo2 用 await 調用了兩次非同步函數 foo,可以看到,如果我們把 sleep 這樣的非同步操作放入某一個框架或者庫中,使用者幾乎不需要瞭解 Promise 的概念即可進行非同步編程了。

此外,generator/iterator 也常常被跟非同步一起來講,我們必須說明 generator/iterator 並非非同步代碼,只是在缺少 async/await 的時候,一些框架(最著名的要數 co)使用這樣的特性來模擬 async/await。

但是 generator 並非被設計成實現非同步,所以有了 async/await 之後,generator/iterator 來模擬非同步的方法應該被廢棄。

更多內容請看:開發者網站 


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

-Advertisement-
Play Games
更多相關文章
  • ##### CSS 背景屬性 ##### 1. background-color(背景顏色) 頁面的背景顏色有四種屬性值表示,分別是transparent(透明),RGB十進位顏色表示,十六進位顏色表示和顏色單詞表示。 屬性使用: ``` /* background-color: transpare ...
  • ##### 8 文本屬性 font-style(字體樣式風格) ``` /* 屬性值: normal:設置字體樣式為正體。預設值。 italic:設置字體樣式為斜體。這是選擇字體庫中的斜體字。 oblique:設置字體樣式為斜體。人為的使文字傾斜,而不是去使用字體庫的斜體字。 */ ``` font ...
  • ##### 5 變數提升 看以下代碼, 或多或少會有些問題的. ```javascript function fn(){ console.log(name); var name = '大馬猴'; } fn() ``` 發現問題了麽. 這麼寫代碼, 在其他語言里. 絕對是不允許的. 但是在js里. 不 ...
  • ##### 7 選擇器優先順序 所謂CSS優先順序,即是指CSS樣式在瀏覽器中被解析的先後順序。樣式表中的特殊性描述了不同規則的相對權重。 ``` /* !important > 行內樣式>ID選擇器 > 類選擇器 > 標簽 > 通配符 > 繼承 > 瀏覽器預設屬性 1 內聯樣式表的權值最高 style ...
  • ##### 6 樣式繼承 CSS的樣式表繼承指的是,特定的CSS屬性向下傳遞到子孫元素。總的來說,一個HTML文檔就是一個家族,然後html元素有兩個子元素,相當於它的兒子,分別是head和body,然後body和head各自還會有自己的兒子,最終形成了一張以下的家族譜。 ![image](http ...
  • ##### 9 閉包 我們都知道,函數里是可以訪問函數外的全局變數,而函數外不能訪問函數內的局部變數,如下: ```js // 函數外定義a,在函數內可以訪問 var a = "測試"; function fn() { console.log(a); } fn(); ``` 執行結果: ![imag ...
  • ##### 5 偽類選擇器 anchor偽類:專用於控制鏈接的顯示效果 |More Actions[:link](https://www.w3school.com.cn/cssref/selector_link.asp) |a:link |選擇所有未被訪問的鏈接。 | | | | | |[:visi ...
  • # 通過 NVM 安裝、管理Node.js 版本(Windows) ## 介紹 NVM 可以讓我們通過指令安裝指定版本,並且可以切換當前Node.js 版本,不用花時間在解決升版和降版。 ## 安裝 鏈接: https://github.com/coreybutler/nvm-windows 選擇最 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...