
打工人!打工魂!前端才是人上人!此系列總結于大前端進擊之路程序中的學習,如果文章中有不對的地方,希望大家能進行批評改正,互相進步,
經典面試題
我們先來看一道經典的面試題,讓我們的小腦袋瓜子思考起來~如果你對這道題有清晰的思路并且了解背后的原因,那么請直接點贊評論加關注!!!!!
//請寫出輸出內容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案!你答對了嗎?沒對的不要跑,睜大你的小眼睛仔細看以下的內容
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
JS采用單執行緒模式作業的原因
為了回答這個問題我們首先需要知道JS的執行環境是單執行緒的,是因為JS語言最早是運行在瀏覽器端的語言,目的是為了實作頁面上的動態互動,實作動態互動的核心就是DOM操作,因此決定了JS必須是單執行緒模式作業,我們來假設一下如果JS是多執行緒一起作業的,其中一個執行緒修改了一個DOM元素,另外的一個執行緒同時又要洗掉這個DOM元素,那么此時瀏覽器就懵逼了,無法明確以哪個作業執行緒為準,所以為了避免執行緒同步的問題,JS就被設計成了單執行緒的作業模式,
注意,我們這里說的單執行緒是JS的執行環境是單執行緒,瀏覽器中是多執行緒的,
單執行緒的優勢和弊端
采用單執行緒的作業模式可以節省記憶體,節約背景關系切換時間,沒有鎖的問題,但弊端也很明顯,如果中間有一個任務需要花費大量的時間,那么后面的任務就需要等待這個任務完成后才能執行,就會出現假死的情況,對用戶很不友好,為了解決這個問題JS給出了兩種執行模式:同步模式(Synchronous)和異步模式(Asynchronous),
同步模式和異步模式
同步模式
同步模式其實很好理解,舉個栗子:
我們如果按照同步模式煮面的話,首先先將鍋里裝上水,打開火開始燒水,等待水燒開,再將面、雞蛋、火腿腸等材料拿出,材料準備好后放入鍋中進行煮,煮好后開始干飯,
在這里其實我們已經能夠看出來問題,我們必須等到水燒開后才去準備要煮的材料,回到概念里就是在同步模式下我們的代碼是依次執行,后一個任務必須等待前一個任務結束才能開始執行,程式執行的順序和代碼撰寫的順序是完全一致的,在單執行緒模式下,大多數任務都是以同步模式執行,
異步模式
上個例子中我們在等待水燒開的程序中什么都沒干,很浪費時間,我們可以在燒水的程序中將食材都準備好,等到水燒開后直接放入,
我們在燒水的程序中去干了別的事情,就屬于異步模式,異步模式中不會等待異步任務的結束才開始執行下一個同步的任務,都是開啟過后就立即執行下一個任務,
異步模式對于JS很重要,沒有異步模式的話我們就無法同時處理大量的耗時任務,就會給用戶帶來卡頓和假死的體驗,對于我們開發者來說,會給我們打開代碼執行的順序混亂的問題,
EventLoop事件回圈和訊息佇列
- EventLoop是一種回圈機制,主執行緒從訊息佇列中讀取任務并按照順序執行,這個程序是回圈不間斷的,
- 訊息佇列是存放異步任務的地方,當我們的同步任務都執行完畢后,EventLoop會從訊息佇列中依次取出異步任務放到呼叫堆疊中進行執行,
宏任務和微任務
-
宏任務可以理解為每次執行堆疊執行的代碼就是一個宏任務
瀏覽器為了讓JS內部宏任務與DOM操作能夠有序的執行,會在一個宏任務執行結束后,下一個宏任務執行開始前,對頁面進行重新渲染,
宏任務包括:script整體代碼、setTimeout、setInterval、I/O、UI互動事件、MessageChannel等,
-
微任務可以理解為每個宏任務執行結束后立即執行的任務,發生在宏任務后,渲染之前,執行微任務,
所以微任務的回應速度相比宏任務會更快,因為無需等待UI渲染
微任務包括:Promise.then、MutaionObserver、process.nextTick(Node.js環境下)等,


圖片取自掘金,侵即刪
異步編程方案的本質—回呼函式
回呼函式:由呼叫者定制,交給執行者執行的函式,
我們通過 callback 回呼函式、事件發布/訂閱、Promise 等來組織代碼,本質都是通過回呼函式來實作異步代碼的存放與執行,
// callback就是回呼函式
// 就是把函式作為引數傳遞,缺點是不利于閱讀,執行順序混亂,
function foo(callback) {
setTimeout(function(){
callback()
}, 3000)
}
foo(function() {
console.log('這是回呼函式')
console.log('呼叫者定義這個函式,執行者執行這個函式')
console.log('其實就是呼叫者告訴執行者異步任務結束后應該做什么')
})
更優異步編程統一方案——Promise
Promise概述
Promise概念MDN傳送門
關于Promise概念性內容就不在贅述了,可直接點擊傳送門前往MDN查看,簡單來說如果我們是用傳統的回呼函式方式來完成復雜的異步流程,就會無法避免大量的回呼函式嵌套,產生回呼地獄的問題,為了避免回呼地獄讓我們開始愉快的Promise的學習時光吧!
// 我們想要執行完第一個再執行第二個再執行第三個
// 雖然我們使用同步的方式將異步的代碼學出來了,但是這樣的回呼是不是讓我們的小腦袋瓜子嗡嗡的?
setTimeout(() => {
console.log('執行第一個');
setTimeout(() => {
console.log('執行第二個');
setTimeout(() => {
console.log('執行第三個');
setTimeout(() => {
console.log('執行第四個');
setTimeout(() => {
console.log('執行第五個');
}, 2000);
}, 2000);
}, 2000);
}, 2000);
}, 2000);
回呼地獄圖示,取自網路,侵即刪,
Promise基本用法
// Promise 基本示例
// promise的英文意思是承諾
// 在JS中Promise是一個物件,接收一個函式作為引數
const promise = new Promise(function (resolve, reject) {
// 這里用于“兌現”承諾
// resolve(100) // 承諾達成
reject(new Error('promise rejected')) // 承諾失敗
})
promise.then(function (value) {
// 即便沒有異步操作,then 方法中傳入的回呼仍然會被放入佇列,等待下一輪執行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
Promise案例
我們用Promise來封裝一個AJax
function ajax (url) {
return new Promise((resolve, rejects) => {
// 創建一個XMLHttpRequest物件去發送一個請求
const xhr = new XMLHttpRequest()
// 先設定一下xhr物件的請求方式是GET,請求的地址就是引數傳遞的url
xhr.open('GET', url)
// 設定回傳的型別是json,是HTML5的新特性
// 我們在請求之后拿到的是json物件,而不是字串
xhr.responseType = 'json'
// html5中提供的新事件,請求完成之后(readyState為4)才會執行
xhr.onload = () => {
if(this.status === 200) {
// 請求成功將請求結果回傳
resolve(this.response)
} else {
// 請求失敗,創建一個錯誤物件,回傳錯誤文本
rejects(new Error(this.statusText))
}
}
// 開始執行異步請求
xhr.send()
})
}
ajax('/api/user.json').then((res) => {
console.log(res)
}, (error) => {
console.log(error)
})
Promise的鏈式呼叫
誤區
- 嵌套使用的方式是使用Promise最常見的誤區,我們要使用promise的鏈式呼叫的方法盡可能保證異步任務的扁平化,
// 嵌套使用 Promise 是最常見的誤區
ajax('/api/urls.json').then(function (urls) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
})
})
})
})
})
鏈式呼叫的理解
- promise物件then方法,回傳了全新的promise物件,可以再繼續呼叫then方法,如果return的不是promise物件,而是一個值,那么這個值會作為resolve的值傳遞,如果沒有值,默認是undefined
- 后面的then方法就是在為上一個then回傳的Promise注冊回呼
- 前面then方法中回呼函式的回傳值會作為后面then方法回呼的引數
- 如果回呼中回傳的是Promise,那后面then方法的回呼會等待它的結束
Promise的例外處理
- then中回呼的onRejected方法
- .catch()
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.catch(function onRejected(error) {
console.log('onRejected', error)
})
// 相當于
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.then(undefined, function onRejected(error) {
console.log('onRejected', error)
})
簡單來說.catch是給整個promise鏈條注冊的一個失敗回呼,推薦使用,
參考
拉勾大前端訓練營
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/244306.html
標籤:其他
上一篇:自定義指令
下一篇:vue自定義指令
