JS單執行緒和非同步

查小小飛發表於2020-02-15

因為疫情的原因,這是最長的一個假期了,人也變懶惰了些。

單執行緒

一個執行緒就是一個處理任務的一個基本過程,可以這樣理解:排隊取號的過程就是一個執行緒,而單執行緒的意思就是,你必須等你前面的人完成了自己的事兒,才能輪到你。

Task1 --> Task2 --> Task3
複製程式碼

單執行緒特點是:任務依次執行,後面的必須等前面的任務完成後,才能執行下一個任務。

JS 單執行緒

JS 程式碼被JS引擎執行的時候是單執行緒的,同一時刻有且僅有一處程式碼正在執行,同一個時間只能做一件事,並阻塞其它的程式碼。

如下程式碼

function Task1() {
    //一個耗時5s的任務
    console.log("任務1完成了")
}
function Task2() {
    console.log("任務2完成了")
}
function Task3() {
    console.log("任務3完成了")
}
Task1()
Task2()
Task3()
複製程式碼

結果會按照 Task1,Task2,Task3的順序列印到控制檯。 單執行緒就意味著,Task2必須等Task1完成後,才會被執行,哪怕Task1需要很長的時間才會執行完成。

同步和非同步

因為JS在被執行的時候是單執行緒的,像前面一樣,如果Task1需要很久才能出結果,這樣後面的任務只能一直等,這樣就會導致後面的任務都不會被執行,導致頁面卡住不懂。那如何解決這樣的問題呢?

答案就是非同步。

先看一段程式碼:

function Task1() {
    setTimeout(function(){
         console.log("任務1完成了")
    },0)
}
function Task2() {
    console.log("任務2完成了")
}
function Task3() {
    console.log("任務3完成了")
}
Task1()
Task2()
Task3()
複製程式碼

這段程式碼的執行結果是 Task2,Task3,Task1的順序,因為Task1 是非同步任務,後面的任務不會等Task1的結果。

再看一個例子 我們使用AJAX 發請求的時候,一個請求也可能很快就有結果,也很有可能需要很久才能得到結果。

  • 同步的AJAX
// 同步AJAX
function taskB(){
  var result = $.ajax({
    url:"/data.json",
    async: false // 同步
  })
  return result 
}
taskA()
taskB()  // 可能要很久才能完成
taskC()
複製程式碼

我們將AJAX 設定成同步的,這就意味者,後面的任務必須等這個AJAX的結果到來後,才能夠繼續向下執行。這就導致頁面會卡住。

如何解決呢?就是使用非同步的AJAX

  • 非同步的AJAX
// 同步AJAX
function taskB(){
  var result = $.ajax({
    url:"/data.json",
    async: true // 非同步
  })
  return result 
}
taskA()
taskB()  // 可能要很久才能完成
taskC()  // 不等TaskB 的執行結果,直接執行TaskC

複製程式碼

現在,TaskC不需要等TaskA完成後才會被執行,這樣頁面就不會被卡住。

這似乎有點問題,因為前面說了JS在被執行的時候,是單執行緒的,同一個時刻只有只有一處程式碼被執行,那是怎麼做到了,直接跳過TaskB,而直接執行後面的呢?TaskB 任務又是誰在執行呢?答案就是,這些非同步任務是瀏覽器的其他模組完成的,比如AJAX的請求就是由瀏覽器的網路模組去處理。

也就是說非同步的任務,是由瀏覽器的其他執行緒去完成的。

回撥

因為非同步的結果需要一段時間才能夠獲得,那麼你怎麼知道它什麼時候有結果呢?怎麼獲取到這個非同步的結果呢? 一般獲取非同步的結果有兩種方式:

  1. 輪詢
  2. 回撥

先不說什麼是回撥,看程式碼

// 非同步任務
function buyFruit(f) {
    setTimeout(function(){
        f("買到的蘋果") 
    },3000)
}
// 處理非同步結果的函式
function eat(fruit) {
    console.log(fruit)
}

// 非同步任務是買水果,任務的結果用回撥函式去處理
buyFruit(eat)
複製程式碼

上面的程式碼中,有一個非同步任務buyFruit,那我如何在他買到水果後再進行其他操作呢,比如我需要吃買到的水果,可以這樣做,將eat函式當作buyFruit函式的一個引數傳進去即可,這樣當buFruit有結果的時候,eat就可以對這個非同步結果進行處理了,這裡,eat函式就回撥。

回撥是什麼?也許你看過各種概念,被搞的暈頭轉向,還有舉的各種形象的例子。其實,我覺得很多東西不要去理解概念,你知道它是什麼,它的作用是什麼,這些概念其實沒什麼作用,如果你非要問什麼是回撥,首先,回撥是函式,這個函式被當作一個引數傳給另一個函式。就這麼簡單。

用回撥函式獲取非同步的結果,存在一些問題,比如,當你還有更多的非同步操作後,會很難處理,這就出現了一些其他的方法解決,比如Promise。

小結

簡單溫習了一下JS的單執行緒和非同步:

  1. 單執行緒的概念:任務依次順序執行。
  2. 單執行緒的缺點:會阻塞後面的任務執行。
  3. 非同步解決阻塞,非同步由瀏覽器其他執行緒處理。
  4. 回撥的概念和用法。

疫情快快結束啊,這個假也太長了一點吧。。

相關文章