JavaScript - 非同步的前世今生

来源:https://www.cnblogs.com/wuvkcyan/archive/2018/08/16/9489271.html
-Advertisement-
Play Games

​ 在開始接觸JavaScript的時候,書上有一句話我記憶深刻, JavaScript是一門單線程語言 ,不管從什麼途徑去獲取這個消息,前端開發者都會記住,哦~~,JavaScript是一門單線程語言,所以alert()會卡住 ​ 為什麼JavaScript是一門單線程語言?因為什麼原因讓Java ...


​ 在開始接觸JavaScript的時候,書上有一句話我記憶深刻,JavaScript是一門單線程語言,不管從什麼途徑去獲取這個消息,前端開發者都會記住,哦~~,JavaScript是一門單線程語言,所以alert()會卡住

為什麼JavaScript是一門單線程語言?因為什麼原因讓JavaScript出生就是單線程語言?

​ 提出問題,必然解決問題,從JavaScript出生說起,最開始JavaScript是配合html去完成對dom的控制,優化用戶交互,於是問題來了

  • js可以修改Dom結構
  • 瀏覽器渲染Dom結構

假如同時執行,會發生什麼,假如同時操作同一個Dom怎麼辦,

同理 兩段js都修改Dom結構,假如同時操作Dom會發生什麼

js沒有執行的時候,瀏覽器Dom渲染,js執行的時候,瀏覽器Dom停止渲染,html結構樹本來是就是從上往下渲染,也就是說,瀏覽器渲染Dom本身就是單線程,js為了避免發生Dom衝突,沒有辦法只能成為單線程語言

但是單線程的硬傷出現了,單線程意味著頁面載入會卡頓,線程被占用瀏覽器卡死

  var sum = 0;
    console.log("start");
    
    for (let i = 0; i < 1000000000; i++) {  //單線程占用
      sum++
    }
  console.log(sum);
  alert("100")   //單線程占用
  console.log("我終於執行啦");

​ 例如上面的代碼,瀏覽器資源類占用,就會發生可怕的事情,瀏覽器卡死,也許這裡for迴圈不形式,但是現實開發裡面同步ajax請求慢的時候頁面卡死是正常事

同步解決方案 - 非同步

說到非同步,不得不談大名鼎鼎的event-loop(事件輪詢)

相信開發者一定看過阮一峰大神對Event loop的解釋,除了圖有點看不懂之外,其他都講的很請求

這裡要說一道比較老的面試題

 setTimeout(() => {
    console.log("我是定製器");
  });
  console.log("我是主進程");

很簡單 先執行console.log("我是主進程"); ,為什麼呢?即使因為事件輪詢

當瀏覽器從上往下解析,遇到setTimeout,setTimeout是非同步任務,瀏覽器就會將他放入非同步任務裡面,然後執行主線程的代碼,當主線程的代碼執行完畢,事件輪詢就是去查看非同步任務,於是執行了setTimeout裡面的函數

這就是事件輪詢

這裡借閱阮一峰大神的一段博客

單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。

如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網路讀取數據),不得不等著結果出來,再往下執行。

JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續執行下去。

於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個非同步任務可以執行了,該任務才會進入主線程執行。

具體來說,非同步執行的運行機制如下。(同步執行也是如此,因為它可以被視為沒有非同步任務的非同步執行。)

(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。

(2)主線程之外,還存在一個"任務隊列"(task queue)。只要非同步任務有了運行結果,就在"任務隊列"之中放置一個事件。

(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。

(4)主線程不斷重覆上面的第三步。

​ event-loop就像哨兵一樣,監視著非同步隊列,一旦有非同步代碼,就會在主線程執行完畢將非同步方法,讀取到主線程,在回去監視非同步隊列,所以event-loop也叫事件輪詢

jQuery.deferred()

無法改變 JS 非同步和單線程的本質

只能從寫法上杜絕 callback 這種形式

他是語法糖,但是解構了代碼

體現了編程思想 : 開放封閉原則

對拓展開放,對修改封閉, 越是深入模塊化,越是體現這樣的實現

 $.ajax({
    url: "test.html",
    success: function(){
      alert("哈哈,成功了!");
    },
    error:function(){
      alert("出錯啦!");
    }
  });

老版本的使用的是回調函數形式,那麼假如我們想修改代碼,勢必要對$.ajax({})裡面的所有代碼進行變成 這就成了

對拓展封閉,對修改封閉,這是對在開發上非常不友好的變成方式,牽一發而動全身

當deferred出現後,ajax變成了鏈式調用

 $.ajax("test.html")

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出錯啦!"); });

這裡明顯可以看到對修改封閉,對拓展開放

假如我們想拓展

.done(function(){ alert("第二個回調函數!");} );

完全不影響之前的代碼,代碼耦合性低

在我最開始接觸jquery的時候,ajax有三種寫法

$.ajax({
    url,
    success: function (){
        //成功回調
    },
    error: function (){
        //失敗回調
    }
})
$.ajax({url})
.done(()=> {
    //成功回調
})
.fail((0=> {
    //失敗回調
})
$.ajax({url})
.then(()=> {
    //成功回調
},
()=> {
    //失敗回調
})

剛開始不解,為什麼第一種是淘汰的寫法.再後來漸漸深入才發現回調的局限性,

  • 耦合性太高,
  • 不支持鏈式調用,
  • 不利於維護,回調里代碼過於複雜會產生風險

這讓我想探究一下,如何實現.done.fail,所以就不得不說jQuery的deferred

阮一峰大神對deferred的講解

在使用jq我們可以使用deferred幫助我們控制非同步

function waitHandle() {
      var dtd = $.Deferred()
      var wait = function (dtd) {
        var task = function () {
          console.log("執行完成");
          dtd.resolve()
        }
        setTimeout(task, 2000);
        return dtd.promise()  //返回pomise對象 防止在回調裡面執行控制回調的函數 例如reject()
      }
      return wait(dtd) //返回wait的執行結果
    }
    waitHandle()
      .then(function () {
        console.log(1);
      }, () => {
        console.log("hahah");

      })
      .then(() => {
        console.log(2);
      }, () => {
        console.log("hahah");
      })

這裡我就不贅述了,前人已經全面的總結了;這裡明顯看到,這是已經初步提出了promise的概念,對日後的標準建立的基礎

Promise

對於低版本瀏覽器,想要使用第三方庫,可以使用bluebird.js,對promise進行實現

老生長談了,開發必須要會

這是jq的deferred的進階版啊,,開發者卸磨殺驢,竟然說jq淘汰了,一葉障目

  function loadImg(src){
    return new Promise((resolve,reject)=> {
      var img = document.createElement('img')
      img.onload = ()=> {
        resolve(img)
      }
      img.onerror = ()=> {
        reject("圖片載入失敗")
      }
      img.src = src
    })
  }

 var load1 = loadImg("https://secure.gravatar.com/avatar/7337a05ac8210b3f1b522818cd31b90d?s=86")
 var load2 = loadImg("https://avatars2.githubusercontent.com/u/33681955?s=460&v=4")
 load1.then(res=> {
   console.log(res.width);
   return load2   //後面的是load2的回調  對於請求串聯 可以這麼寫 
 }).then(res=> {
   console.log(res.width);
 }).catch(res=> {
   console.log(res);
 })

Promise.all

接受一個Promise對象的數組,待全部完成後,統一執行then,失敗一個catch

Promise.all([load1,load2])
  .then(res=> {
    console.log(res);
  })
  .catch (res=> {
    console.log(res); //假如有一個失敗就會返回catch
  })

Pormise.race

接收一個包含多個Promise對象的數組,主要完成一個就執行then,失敗一個catch

Promise.race([load1, load2])
    .then(res => {
        console.log(res); //成功一個立馬返回
    })
    .catch(res => {
         console.log(res); //假如有一個失敗就會返回catch
     })

在Peomise上還要說一點就是狀態變化

三種狀態: pending(待定的) fulfilled(滿足的) rejected(拒絕)

規則: 狀態變化不可逆

在初始狀態下,Promise是pending 只有兩種情況

  • Promise執行成功 pending 成功狀態變成fulfilled
  • Promise執行成功 pending 失敗狀態變成rejected

瞭解一下

async/await

then是將callback進行拆分了

關於async/await 其實網上資料也挺多的,將非同步代碼同步進行,取消了.then這樣的寫法,回歸本源

let load = async function() {
  let result1 = await loadImg(load1)
  console.log(result1);
  let result2 = await loadImg(load2)
  console.log(result2);
}
總結一下JavaScript非同步的前世今生
  • JavaScript是單線程,兩段js不可以同時進行
  • 原因是為了避免 DOM的渲染衝突
  • 非同步就是"無奈的解決方案",但是初期有很多問題
  • 非同步是event loop實現的,事件輪詢 非同步方法 進入的非同步隊列,主線程執行完畢才會去執行非同步隊列裡面的代碼
  • jQuery Deferred,改變了非同步只能由callback操作這個問題
  • deferred和Promise 有區別的 Promise對deferred進行優化 ,讓Promise對象只能被動監聽結果,避免衝突
  • Promise的出現成為了對非同步的最佳解決方案相對於callback,極大的降低了耦合性
  • async/await 配合Promise 成為終極解決方案,但是ES7為成為正式版,未來仍需參考

非同步的解決方案

  • 回調函數 - callback
  • 初代鏈式 - deferred (我覺得這個有必要提出來,改變js非同步編程的關鍵)
  • 成熟方案 - promise
  • 終極方案 - async/await + promise
  • 迭代器 Generator(同樣實現非同步,但是被async/await替代)

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

-Advertisement-
Play Games
更多相關文章
  • 一.數組的定義(來源於Array.prototype) 1.構造函數方法: (1)var arr = new Array();//沒有參數等價於 var arr = []; (2)var arr = new Array(length);//創建指定長度的數組;預分配一個數組空間;但數組中沒有存儲值, ...
  • jQuery的優勢: 使用jQuery: jQuery對象和DOM對象的區別 一://取得jsDOM對象: 二://獲得jquery對象 三:DOM對象與jQuery對象互相轉換 可以把jQuery對象看做DOM對象的數組,因此 可以通過索引下標在jQuery中獲取DOM對象 方法一:jquery對 ...
  • MDN定義:位運算符將它的操作數視為32位元的二進位串(0和1組成)而非十進位八進位或十六進位數。 例如:十進位數字9用二進位表示為1001,位運算符就是在這個二進位表示上執行運算,但是返回結果是標準的JavaScript數值。 位運算符有7個,這裡只講一個:按位異或^ 用法: a^b, 運算規則: ...
  • 11、強制轉換 強制轉換主要指使用Number、String和Boolean三個構造函數,手動將各種類型的值,轉換成數字、字元串或者布爾值。 1>Number強制轉換 參數為原始類型值的轉換規則: 原始類型的值主要是字元串、布爾值、undefined和null,它們都能被Number轉成數值或NaN ...
  • 4-1 渲染機制:-1-,什麼是DOCTYPE及其作用?DTD(document type definition,文檔類型定義)是一系列的語法規則,用來定義XML或(X)HTML的文件類型,瀏覽器會使用DTD來判斷文檔類型,決定使用何種協議來解析,以及切換瀏覽器模式。DOCTYPE就是用來聲明文檔類 ...
  • 什麼是職責鏈模式? 重要性:4 星,在項目中能對 if-else 語句進行優化 定義:避免請求發送者與接收者耦合在一起,讓多個對象都有可能接收請求,將這些對象連接成一條鏈,並且沿著這條鏈傳遞請求,直到有對象處理它為止。 主要解決:職責鏈上的處理者負責處理請求,客戶只需要將請求發送到職責鏈上即可,無須 ...
  • 一、背景 CSS有三大特性:層疊性、繼承性、優先順序。 而我們在給CSS定義樣式的時候,經常出現兩個及以上的規則應用在同一元素上,單該元素最終在瀏覽器呈現的效果是應用的哪個規則呢?這就要考慮優先順序的問題了。 CSS優先順序是由CSS權重來作為衡量標準的,權重的計算有一套計算公式,有如下規範: 使用一個4 ...
  • 每個第一次使用jq的開發者都感到驚嘆,jq的$太神奇了,究竟是怎麼做到的使用\$控制dom 贊嘆前人之餘,探究其本源才是前端開發者應該做的事,社區常常說,不要重覆造輪子, 可是啊,連輪子都造不出來,又怎麼去瞭解在什麼環境下用什麼輪子,怎麼樣才可以造成更加優秀的輪子, 不同階段對前端有不同的理解,作為 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...