1. 程式人生 > >javascript 非同步解析

javascript 非同步解析

js 非同步解析

  一 、js單執行緒分析

    我們都知道js的一大特點是單執行緒,也就是同一時間點,只能處理一件事,一句js程式碼。那為什麼js要設計成單執行緒而不是多執行緒呢?這主要和js的用途有關,js作為瀏覽器端的指令碼語言,主要的用途為使用者與服務端的互動與操作dom。而操作dom就註定了js只能是單執行緒語言。假如js才取多執行緒將會出現,多個執行緒同時對一個dom進行操作的情況,瀏覽器將無法判斷如何渲染。不僅js是單執行緒,瀏覽器渲染dom也是單執行緒的,js的執行和瀏覽器渲染dom共用的一個執行緒,這就導致了在html程式碼中書寫js程式碼會造成瀏覽器端渲染的阻塞。例如:在html某個位置,寫一個段帶有alert(‘稍等’),alert 之前html已經被渲染出來,而alert之後的html被這段js阻塞了。為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript指令碼建立多個執行緒,但是子執行緒完 全受主執行緒控制,且不可進行DOM操作。所以,這個新標準並沒有改變JavaScript單執行緒的本質。

    所謂的js單執行緒,是指在瀏覽器中JS引擎負責解釋和執行JavaScript程式碼的執行緒只有一個。不妨叫它主執行緒。但是實際上瀏覽器處理js還存在其他的執行緒。例如:處理AJAX請求的執行緒、處理DOM事件的執行緒、定時器執行緒、讀寫檔案的執行緒(例如在Node.js中)等等。這些執行緒可能存在於JS引擎之內,也可能存在於JS引擎之外,在此我們不做區分。不妨叫它們工作執行緒

    總結一下:js之所以才取單執行緒模式是為了避免DOM渲染衝突。而瀏覽器中執行js執行緒是單執行緒我們稱它為主執行緒,同時還存在其它處理js的執行緒,我們稱它為工作執行緒。js是單執行緒,但瀏覽器是多執行緒的。

  二 、同步與非同步

    單執行緒就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。這就是同步程式碼阻塞。如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閒著的,因為IO裝置(輸入輸出裝置)很慢(比如Ajax操作從網路讀取資料),不得不等著結果出來,再往下執行。JavaScript語言的設計者意識到,這時主執行緒完全可以不管IO裝置,掛起處於等待中的任務,先執行排在後面的任務。等到IO裝置返回了結果,再回過頭,把掛起的任務繼續執行下去。

    簡單的說,同步就是會阻塞程式碼的執行,而非同步不會。同樣拿alert('稍等') 來舉例,在一段js程式碼中加入一段alert,如果沒有點選確認,此時程式碼的執行就被阻塞了,大多數js程式碼都是同步執行的。非同步則相反。那為什麼js中要引入非同步的概念呢,很簡單,由於js的單執行緒,當遇到耗時的操作時如果採用同步的執行,那麼我們就不可能看到如今這麼流暢的web應用了。再舉個簡單的例子:在一條單行道上行駛著很多汽車,假如其中某一輛車出現機械故障,將會導致後面的車也無法通過,此時應該將故障的車拉入旁邊的應急車道進行修復,待它修好之後再重新駛入主幹道中,不會影響主幹道其它行駛的汽車。所以,非同步是js單執行緒下解決耗時問題的一種“無可奈何”的解決方案。也是一種近乎完美的解決方案。

  三、js非同步與事件輪詢

    事件輪詢(event loop)是js非同步的實現方式。簡而言之,在js單執行緒中分為兩種任務,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主執行緒、而進入”任務佇列”(task queue)的任務,只有當主程序中所有同步任務執行完畢,且”任務佇列”通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。事件輪詢就是將主執行緒中的非同步任務掛載到任務佇列中,再待到合的時機,將任務佇列中的非同步函式拉到主程序來執行的這麼一個流程。

    js中非同步操作主要有:1、定時任務(setTimeout、setInterval)

              2、網路請求(ajax、動態<img>載入)

              3、事件繫結(click,focus,change等)

   js非同步具體執行流程分析

  

    大家看一下左邊程式碼,兩個console.log操作,兩個setTitmeout 操作。按照我們對非同步的理解,在主執行緒中 console.log 為同步任務從上到下依次執行,所以在最先列印的是3,當執行到第一setTimeout時,瀏覽器js引擎會自動將setTimeout放入工作執行緒中。ps:在工作執行緒中,待0.1s後將setTimeout 的回撥函式放入非同步佇列中;主程序中下一個setTimeout ,但是它的延遲時間為0,這並不意味著它能同步執行,它依舊經歷如上兩個過程,從工作程序中,0s後放入任務佇列。接下來是執行console.log(3);當主程序中任務已經執行完畢。任務佇列中有一個監視器,隨時監視著主程序和任務佇列中的非同步函式情況,當主程序執行完畢,就判斷任務佇列中是否有需要執行的函式,如果有就按照佇列現後順序依次放入主程序中,以此往復。

    所以上面程式碼,依次列印為:3,3,2,1。也就是先將非非同步執行完,再回過頭來執行非同步函式,非同步函式執行順序為佇列規則,先進先出,也就是先進入佇列的非同步函式將優先執行。

    思考:如果一段程式碼中現後存在一個ajax 和一個1s的定時函式,那麼他們誰先執行呢? 答案是:不確定。因為不確定ajax請求完畢進入佇列的時間。小夥伴們可別被面試管套路了哦。哈哈。

  四、前端非同步的寫法

    1、回撥函式,也就是在setTimeout或者ajax中添加回調函式,待到指定時間後或者請求到資料後再執行回撥。

    2、ES6標準:Promise,ES7:async await   這兩種都只是js事件輪詢實現非同步的一種優雅的 方式,將非同步變為同步的寫法,但都並未改變js非同步本質。