1. 程式人生 > >js同步非同步 回撥函式

js同步非同步 回撥函式

js同步非同步

同步 指的是一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。 非同步 是指每一個任務有一個或多個回撥函式,前一個任務結束後,不是執行後一個任務,而是執行回撥函式,後一個任務則是不等前一個任務結束就執行,所以程式的執行順序與排列順序不一定一致,是非同步的。

非同步模式其實就是延遲處理。在和HTML互動的過程中,會需要一些IO操作(典型的就是Ajax請求,指令碼檔案載入),如果這些操作是同步的,就會阻塞其它操作,使用者的體驗就是頁面失去了響應。

JS如何實現非同步原理?

js是單執行緒的語言,同一時間只能做一件事。這樣問題來了 非同步和單執行緒不是自相矛盾的嗎?

其實非同步和單執行緒確實不能同時成為一個語言的特徵,js選擇成為單執行緒的語言它本身是不可以非同步的。 但是js的宿主環境(瀏覽器 Node)是多執行緒的,宿主通過某種方式(事件驅動)使得js具備非同步的屬性。

瀏覽器的核心是多執行緒的,它們在核心制控下相互配合以保持同步,一個瀏覽器至少實現三個常駐執行緒: JAVASCRIPT引擎線 程,渲染執行緒,瀏覽器事件觸發執行緒。

  1. javascript引擎執行緒是基於事件驅動單執行緒執行的,JS引擎一直等待著任務佇列中任務的到來 ,然後加以處理,瀏覽器無論什麼時候都只有一個JS執行緒在執行JS程式

  2. UI喧染執行緒負責渲染瀏覽器介面,當介面需要重繪

    Repaint )或由於某種操作引發迴流(reflow)吋.該執行緒就會執行。但需要注意Ul渲染執行緒與JS引擎是互斥的,當JS引擎拋行吋UI執行緒會被掛起, UI更新會被儲存在一個佇列中等到JS引擎空閒吋立即被執行。

  3. 事件觸發執行緒,當一個事件被觸發吋該執行緒會把事件新增到待處理佇列的隊尾,等待JS引擎的處理.這些事件可來自JavaScript引擎當前執行的程式碼塊如setTimeOut、也可來自瀏覽器核心的其他執行緒如鼠棕點選、AJAX非同步清求等,但由於JS的単執行緒關係所有這些事件都得排隊等待JS引擎處理.

總的來說:當js觸發到非同步時,會將非同步任務交給瀏覽器來執行,當執行有結果時會把非同步任務的回撥函式插入待處理佇列的隊尾。

UI執行緒和JS執行緒互斥例項

來看一段簡單的程式碼:

<body>
<input type="text" name="input" value="" onkeydown="console.log(this.value);">  <!--事件會在使用者按下一個鍵盤按鍵時發生--> //輸入a 顯示空格 輸入aa 顯示a

<input type="text" name="input" value="" onkeydown="var me=this;setTimeout(function(){console.log(me.value);},0)">    //輸入a 顯示a 輸入aa 顯示aa
</body>

分析: 第一個在keydown的時候,彈出來的是input裡原來的value,(按下的時候先執行js一段程式碼,打印出value值,然後a才渲染出來)

而第二個在keydown的時候,卻能彈出更新後的value,就是因為setTimeout,雖然他的delay設定為0,幾乎是即時觸發,但還是被新增到了執行佇列後面,但就是這個過程,渲染已經完成了,當他回撥函式執行時 ,輸出來的 已經是更新後的value了。

注意:JS的工作機制是當執行緒空閒的情況下才會執行非同步程式碼的回撥
(即:當所有的同步任務執行完畢後才會執行非同步任務的回撥

常見的非同步回撥函式

1.點選事件 2.Ajax請求 3.定時器

瀏覽器處理點選事件的過程 瀏覽器開啟事件觸發執行緒,等待使用者動作,事件觸發執行緒解析為響應事件,轉移到javascript引|擎執行緒,排隊等候,等待 javascript引擎的處理。

裡面的核心內容:

(1)所有同步任務都在主執行緒上執行,形成一個執行棧。 (2)主執行緒之外,還存在一個"任務佇列"。只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件。 (3)一旦"執行棧"中的 所有同步任務執行完畢,系統就會讀取"任務佇列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。 (4)主執行緒不斷重複上面的第三步。

只要主執行緒空了,就會去讀取"任務佇列",這就是JavaScript的執行機制。這個過程會不斷重複。

js回撥函式

function foo(){
 var a = 10;
 return function(){
  a *= 2;
  return a;  
 }; 
}
var f = foo();
f(); //return 20.
f(); //return 40.

字面上的理解,回撥函式就是一個引數,將這個函式作為引數傳到另一個函式裡面,當那個函式執行完之後,再執行傳進去的這個函式。這個過程就叫做回撥。(即:主函式執行完後 再執行內部的回撥函式)

回撥函式與非同步

我們都說 非同步任務必須指定回撥函式,當主執行緒開始執行非同步任務,就是執行對應的回撥函式。

(一直以來我都在混淆 非同步和回撥函式之間的關係) 我們來看這樣一個例子:

var A = function (callback){
    callback();
    console.log("AAAAAAAA");
  }
  var B = function (){
    for (var i = 0; i < 10000; i++) {
    }
    console.log("BBBBBBBB");
  }
 A(B);
 
 //BBBBBB
 //AAAAAA

並不是說呼叫了回撥函式就會實現非同步操作,在這裡callback函式仍然在主執行緒中執行,所以,呼叫callback,當前執行緒還是會去執行callback碰到IO操作才會真的執行非同步

回撥與同步、非同步並沒有直接的聯絡,回撥只是一種實現方式,既可以有同步回撥,也可以有非同步回撥,還可以有事件處理回撥和延遲函式回撥,這些在我們工作中有很多的使用場景