非同步,就是非同步....
這節內容可能會有點枯燥,但是卻是 JavaScript 中非常重要的概念,非常有必要去學習。
$.ajax({ url: "www.xx.com/api", async: false, // true success: function(result) { console.log(result); }, });
// 非同步批次更新DOM(vue-nextTick) // <p id="app">{{num}}</p> 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 引擎是單執行緒導致的。
單執行緒是指在 JS 引擎中負責解釋和執行 IavaScript 程式碼的執行緒只有一個,不妨叫它主執行緒。
所謂單執行緒,就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成再執行後面一個任務。
先看看一下瀏覽器核心的執行緒圖:
其中,渲染執行緒和 JS 執行緒互斥。
假設有兩個函數,一個修改一個刪除,同時操作一個 DOM 節點,假如有多個執行緒的話,兩個執行緒一起執行,肯定就死鎖了,就會有問題。
為什麼 JS 要設計為單執行緒,因為瀏覽器的特殊環境。
單執行緒的優缺點:
這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。常見的瀏覽器無響應(假死),往往就是因為某一段 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 |
非同步程式碼最常見的寫法就是使用回撥函數。
// 注意到click方法中是一個函數而不是一個變數 // 它就是回撥函數 $("#btn_1").click(function() { alert("Btn 1 Clicked"); }); // 或者 function click() { // 它就是回撥函數 alert("Btn 1 Clicked"); } $("#btn_1").click(click);
回撥函數的缺點也很明顯,容易產生回撥地獄:
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); }, }); }, }); }
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 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 |
執行順序:
執行整體程式碼<script>
(宏任務)
執行所有微任務
執行一個宏任務
執行渲染執行緒
2->3->2->3...依次迴圈(在 2、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 的輸出結果,並說出理由。
更多程式設計相關知識,請存取:!!
以上就是手把手帶你弄懂JavaScript中的非同步程式設計的詳細內容,更多請關注TW511.COM其它相關文章!