一次搞懂-JavaScript之異步編程

JS-異步代碼maind.png

前言

異步,就是非同步....javascript

這節內容可能會有點枯燥,可是倒是 JavaScript 中很是重要的概念,很是有必要去學習。html

目的

  • 提高開發效率,編寫易維護的代碼

引子問題

  • 請求時候爲何頁面卡死??
$.ajax({
  url: "www.xx.com/api",
  async: false, // true
  success: function(result) {
    console.log(result);
  },
});
  • 爲何數據更新了,DOM 卻沒有更新??
// 異步批量更新DOM(vue-nextTick)
// <div id="app">{{num}}</div>
new Vue({
  el: "#app",
  data: {
    num: 0,
  },
  mounted() {
    let dom = document.getElementById("app");
    while (this.num !== 100) {
      this.num++;
    }
    console.log("Vue num=" + this.num, "DOM num=" + dom.innerHTML);
    // Vue num=100,DOM num=0
    // nextTick or setTimeout
  },
});

產生異步的緣由

緣由:單線程(一個時間點,只作一件事),瀏覽器的 JS 引擎是單線程致使的。vue

單線程是指在 JS 引擎中負責解釋和執行 IavaScript 代碼的線程只有一個,不妨叫它主線程。html5

所謂單線程,就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成再執行後面一個任務。java

先看看一下瀏覽器內核的線程圖:
Browser thread.jpgios

其中,渲染線程和 JS 線程互斥ajax

假設有兩個函數,一個修改一個刪除,同時操做一個 DOM 節點,假若有多個線程的話,兩個線程一塊兒執行,確定就死鎖了,就會有問題。編程

爲何 JS 要設計爲單線程,由於瀏覽器的特殊環境。axios

單線程的優缺點:segmentfault

這種模式的好處是實現起來比較簡單,執行環境相對單純; 壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段 Javascript 代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。

常見的堵塞(死循環):

while (true) {}

JS 在設計之初就以運行在瀏覽器中的腳本語言,因此也不想搞得這麼複雜,就設計成了單線程,也就是,一個時間點,只能作一件事。

爲了解決單線程堵塞這個缺點:產生了異步。

拿吃泡麪舉例:

  • 同步:買泡麪=》燒水(盯着)=》煮麪=》吃泡麪
  • 異步:買泡麪=》燒水(水開了熱水壺響-回調)=》看電視=》煮麪(面好了熱水壺響-回調)=》看電視=》熟了叫我=》吃泡麪

看電視就是異步操做,熱水壺響,就是回調函數。

異步編程

JS 中大多的代碼都是同步執行的,只有極個別的函數是異步執行的,異步執行的代碼,則須要異步編程。

異步代碼

setTimeout(() => {
  console.log("log2");
}, 0);
console.log("log1");
// ?? log1 log2

異步代碼的特色:不是當即執行,而是須要等待,在將來的某一個時間點執行。

同步代碼 異步代碼
<script>代碼 網絡請求(Ajax)
I/O 操做 定時器(setTimeout、setInterval)
渲染操做 Promise(then)
async/await

回調函數

異步代碼最多見的寫法就是使用回調函數。

  • HTTP 網絡請求(請求成功、識別後執行 xx 操做)
  • DOM 事件綁定機制(用戶觸發事件後執行 xx 操做)
  • 定時器(setTimeout、setInterval)(在達到設定時間後執行 xx 操做)
// 注意到click方法中是一個函數而不是一個變量
// 它就是回調函數
$("#btn_1").click(function() {
  alert("Btn 1 Clicked");
});
// 或者
function click() {
  // 它就是回調函數
  alert("Btn 1 Clicked");
}
$("#btn_1").click(click);

回調函數的缺點也很明顯,容易產生回調地獄:
callbackhell.png

異步編程的三種方式

  • callback
function getOneNews() {
  $.ajax({
    url: topicsUrl,
    success: function(res) {
      let id = res.data[0].id;
      $.ajax({
        url: topicOneUrl + id,
        success: function(ress) {
          console.log(ress);
          render(ress.data);
        },
      });
    },
  });
}
  • promise
function getOneNews() {
  axios
    .get(topicsUrl)
    .then(function(response) {
      let id = response.data.data[0].id;
      return axios.get(topicOneUrl + id);
    })
    .then((res) => {
      render(res.data.data);
    })
    .catch(function(error) {
      console.log(error);
    });
}
  • async/await
async function getOneNews() {
  let listData = await axios.get(topicsUrl);
  let id = listData.data.data[0].id;
  let data = await axios.get(topicOneUrl + id);
  render(data.data.data);
}

在線預覽

預覽地址:http://jsrun.net/s43Kp/embedded/all/light

問題??

若是多個異步代碼同時存在,那麼執行順序應該是怎樣的?那個先執行、那個後執行了?

宏任務和微任務

異步代碼的劃分,異步代碼分宏任務和微任務。

宏任務(不着急) 微任務(着急)
<script>總體代碼 Promise
setTimeout/setInterval

事件循環(Event loop)

EventLoopmind.png

執行順序:

  1. 執行總體代碼<script>(宏任務)
  2. 執行全部微任務
  3. 執行一個宏任務
  4. 執行渲染線程
  5. 2->3->2->3...依次循環(在 二、3 步中又建立了新的宏、微任務)

重複從宏任務和微任務隊列裏拿出任務去執行。

總結

由於瀏覽器設計的緣由,JS 線程和渲染線程互斥,因此 JS 線程被設計成了單線程。

由於單線程執行一些操做(如網絡請求)時有堵塞的問題,全部產生了異步。

由於有了異步,因此產生了異步編程,從而有了回調函數。

由於回調函數寫多了會產生回調地獄,全部又有了解決回調地獄的 Promise 寫法

自 ES7 標準後有了比 Promise 更加優雅的寫法 ———— async/await 寫法,也是異步編程的最終解決方法。

由於 JS 的代碼分爲同步和異步代碼,同步代碼的執行順序沒必要多說,自上而下的執行。

可是若是有多個異步的代碼,他的執行順序又是怎麼的呢??

爲了解決多個異步代碼的執行順序問了,有了事件循環(EventLoop),將異步任務區分爲宏任務、微任務,依據規則依次執行。

至此 完!

練習

console.log("script start");
setTimeout(function() {
  console.log("timeout1");
}, 10);
new Promise((resolve) => {
  console.log("promise1");
  resolve();
  setTimeout(() => console.log("timeout2"), 10);
}).then(function() {
  console.log("then1");
});
console.log("script end");

寫出 log 的輸出結果,並說出理由。

參考

來自九旬的 原創文章
相關文章
相關標籤/搜索