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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...