JavaScript異步精講,讓你更加明白Js的執行流程!

clipboard.png

問題點

  • 什麼是單線程,和異步有什麼關係
  • 什麼是 event-loop
  • jQuery的Deferred
  • Promise 的基本使用和原理
  • async/await(和 Promise的區別、聯繫)

1、什麼是單線程,和異步有什麼關係

單線程- 只有一個線程,只能作一件事
緣由-避免DOM渲染的衝突
解決方案-異步
1) 單線程- 只有一個線程,只能作一件事
基礎事例
// 循環運行期間,JS執行和DOM 渲染暫時卡頓javascript

var i, sum = 0;
for (i = 0; i<100000000000; i++) {
  sum += i;
}
console.log(sum);

// alert不處理,JS執行和DOM 渲染暫時卡頓
console.log(1);
alert('hello');
console.log(2)

2) 緣由 - 避免DOM渲染的衝突
瀏覽器須要渲染DOM
JS能夠修改DOM結構
因此JS執行的時候,瀏覽器 DOM 渲染會暫停
兩段JS也不能同時執行(都修改 DOM 就衝突了)
webworker支持多線程,可是不能修改訪問 DOM
3) 解決方案 - 異步
基礎事例java

console.log(100)

setTimeout(function () {
  console.log(200); // 反正1000ms以後執行
}, 1000);           // 先無論他,先讓其它 JS 代碼運行

console.log(300);
console.log(400);

4) 異步 - 存在的問題
問題一:沒有按照書寫方式執行,可讀性差
問題二:callback中不容易模塊化web

2、什麼event-loop

文字解釋
事件輪詢,JS實現異步的具體解決方案
同步代碼,直接執行
異步函數先放在異步隊列中
待同步函數執行完畢,輪詢執行異步隊列函數
例子分析
例子一:ajax

clipboard.png

先看左下角,是咱們要執行的代碼,第一個是延遲100毫秒打印1,第二個是延遲0,打印2,按照上面講的,同步代碼先執行,異步代碼放在異步隊列中,最後執行完同步在執行異步隊列的函數,執行第一個函數的時候,setTimeout是個異步函數,那咱們是否是應該當即放在右側的隊列中呢?答案是否認的,由於它有一個延時,咱們須要100毫秒以後才能放進去,執行第二個setTimeout函數,因爲沒有延時,因此會被放進異步隊列中,最後一個是直接 在主進程,因此 直接執行打印,等同步執行完以後,馬上看異步隊列中,這時候只有第二個setTimeout,執行完以後,在去看異步隊列有沒有,由於100這毫秒對於計算機來講,是一個至關長的時間,因此js引擎會一直輪詢異步隊列中有沒有可執行函數,直接100毫秒以後第一個setTimeout被放進異隊列中,而後才執行。數組

例子一:promise

clipboard.png

上圖流程跟例子一同樣,這邊咱們疑惑的是,當咱們執行 ajax 的時候,裏面的success 是異步函數,它要何時被放進異步隊列中呢?
固然是請求成功的時候被放進異步隊列中,但咱們不知道是 大於100毫秒仍是小於,因此打印結果有兩種狀況,先打印d c b a,或者打印 d c a b。瀏覽器

3、是否用過jQuery的Deferred
jQuery 1.5的變化 -1.5以前服務器

jQuery 1.5的變化 -1.5以後多線程

clipboard.png

jQuery 1.5的變化
沒法改變JS異步和單線程的本質
只能從寫法上杜絕 callback 這種形式
它是一種語法糖形式,可是解藕了代碼
很好的體現:開放封裝原則異步

1) 什麼是deferred對象?
開發網站的過程當中,咱們常常遇到某些耗時很長的javascript操做。其中,既有異步的操做(好比ajax讀取服務器數據),也有同步的操做(好比遍歷一個大型數組),它們都不是當即能獲得結果的。
一般的作法是,爲它們指定回調函數(callback)。即事先規定,一旦它們運行結束,應該調用哪些函數。

可是,在回調函數方面,jQuery的功能很是弱。爲了改變這一點,jQuery開發團隊就設計了deferred對象。
簡單說,deferred對象就是jQuery的回調函數解決方案。

2) deferred應用
咱們來看一個具體的例子。假定有一個很耗時的操做wait:

var wait = function() {
var tasks = function() {

alert('執行完畢');

};
setTimeout(tasks,5000);
}

咱們爲它指定回調函數,應該怎麼作呢?

你可能會使用$when()

$.when(wait())
 .done(function(){
   alert('成功!')
 })
 .fail(function() {
   alert('出錯!');
 })

可是,這樣寫的話,done()方法會當即執行,起不到回調函數的做用。緣由在於$.when()的參數只能是deferred對象,因此必須對wait()進行改寫:

var dtd = $.Deferred(); //新建一個deferred

var wait = function (dtd) {
  var tasks = function () {
    console.log('執行完畢');
    dtd.resolve(); //改變deferred對象執行狀態
  };

  setTimeout(tasks, 5000);

  return dtd;
}

如今,wait()函數返回的是deferred對象,這就能夠加上鍊式操做了。

$.when(wait(dtd))

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

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

更多內容可參考這裏

4、Promise 的基本使用和原理
1) 什麼是 Promise
一人Promise 對象表明一個目前還不可用,可是在將來的某個時間點能夠被解析的值。它容許你以一種同步的方式編寫異步代碼。Promise的出現,本來是爲了解決回調地獄的問題。全部人在講解Promise時,都會以一個ajax請求爲例,此處咱們也用一個簡單的ajax的例子來帶你們看一下Promise是如何使用的。

ajax請求的傳統寫法:

getData(method, url, successFun, failFun){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open(method, url);
  xmlHttp.send();
  xmlHttp.onload = function () {
    if (this.status == 200 ) {
      successFun(this.response);
    } else {
      failFun(this.statusText);
    }
  };
  xmlHttp.onerror = function () {
    failFun(this.statusText);
  };
}

改成promise後的寫法:

getData(method, url){
var promise = new Promise(function(resolve, reject){

var xmlHttp = new XMLHttpRequest();
xmlHttp.open(method, url);
xmlHttp.send();
xmlHttp.onload = function () {
  if (this.status == 200 ) {
    resolve(this.response);
  } else {
    reject(this.statusText);
  }
};
xmlHttp.onerror = function () {
  reject(this.statusText);
};

})
return promise;
}

getData('get','www.xxx.com').then(successFun, failFun)
很顯然,咱們把異步中使用回調函數的場景改成了.then()、.catch()等函數鏈式調用的方式。基於promise咱們能夠把複雜的異步回調處理方式進行模塊化。

2)Promise的原理分析
其實 promise 原理提及來並不難,它內部有三個狀態,分別是 pending, fulfilled 和 rejected。

peding 是對象建立後的初始狀態,當對象fulfill(成功)時變爲 fulfilled, 當對象 reject(失敗)時變爲 rejected。且只能從 pending 變爲 fulfilled 或 rejected,而不能逆向或或從 fulfilled 變爲rejected,從 rejected變爲 fulfilled。如圖所示:

clipboard.png

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載

相關文章
相關標籤/搜索