## 回調函數
1.自己定義的函數 2.回調函數沒有親自調用,但是最終應該執行(例如```settimeout```)
分為:1. 同步回調函數 2.異步回調函數
1. 同步回調函數
```
const arr = [1, 2, 3]
arr.forEach(item => { //遍歷回調函數 不會放入隊列中,一上來就要執行完
console.log(item)
)
console.log('執行函數')
//問:兩個函輸出誰先執行?
```
2. 異步回調函數
```
setTimeout(() => { //異步回調函數,會放入隊列中 將來執行
console.log('timeout cb')
}, 0)
console.log('回調函數')
```
## js中的[Error](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error)
1. 錯誤類型
Error: 所有錯誤的父類型
ReferenceError: 引用的變量不存在
TypeError: 數據類型不正確的錯誤
RangeError: 數據值不在其所允許的范圍內
SyntaxError: 語法錯誤
2. 錯誤處理
捕獲錯誤: try ... catch
拋出錯誤:throw error
3. 錯誤對象
message屬性:錯誤相關信息
stack屬性:函數調用棧記錄信息
>link to errShow code
## Promise
### WHAT?
**1.1 抽象表達 是js中進行異步編程的**新**的解決方案(舊的是啥? 就是純的異步執行的方法)**
**1.2. 具體表達:**
1)從語法上來說,Promise是一個構造函數
2) 從功能上來說Promise對象用來封裝一個異步操作并可以獲取其結果
**2.1 Promise的狀態改變**
2.1.1 pending 變為resolved
2.1.2 pending變為 rejected
說明:只有這2種,且一個promise對象只能改變一次
無論變為成功還是失敗,都共有一個結果數據
成功的結果數據一般稱為value,失敗的結果數據一般稱為reason
**3.1 Promise的流程**

>執行操作 使用一下 link to usePromise code
### Why?
用它自然是有其**優點**才會使用
1. 指定回調函數的方式更加靈活:
純回調:必須在啟動異步任務前指定
promise:啟動異步任務 => 返回promise對象 => 給promise對象綁定回調函數(甚至可以在異步執行結束后再指定回調也可以)
2. 支持鏈式調用,可以解決回調地獄問題
回調地獄:回調函數嵌套調用,外部回調函數異步執行的結果是嵌套的回調函數執行的條件
回調地獄的缺點?不便于閱讀/不便于異常處理
解決方案: promise鏈式調用
終極解決方式:async/await
>link to whyToUse code
### HOW?
#### promise的API
[API](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#%E6%96%B9%E6%B3%95)
#### promise的幾個關鍵問題
1. 如何改變promise的狀態?
(1)resolve(value) :如果當前是pendding狀態就會變為resolved
(2)reject(reason) :如果當前是pendding狀態就會變為rejected
(3)拋出異常:如果當前是pendding就會變為rejected
2. 一個promise指定多個成功/失敗回調函數,都會調用嗎?
當promise改變為對應狀態時都會調用
3. 改變promise狀態和指定回調函數誰先誰后?
(1)都有可能,正常情況下是先指定回調再改變狀態,但也可以先改變狀態再指定回調
(2)如何先改變狀態再指定回調?
①在執行器中直接調用resolve() / reject()
②延遲更長時間才調用then()
(3)什么時候才能得到數據?
①如果先指定的回調,那當狀態發生改變時,回調函數就會調用,得到數據
②如果先改變的狀態,那當指定回調時,回到函數就會調用,得到數據
4. promise.then()返回的新的promise的結果狀態由什么決定?
(1)簡單表達:由then()指定的回調函數執行的結果決定
(2)詳細表達:
如果拋出異常,新的proimise變為rejected,reason為拋出的異常
如果返回的是非promise的任意值,新promise變為resolved, value為返回的值
如果返回的是另一個新的promise,此promise的結果就會成為新的promise的結果
5. promise如何串聯多個操作任務?
(1)promise的then() 返回一個新的promise,可以看成then()的鏈式調用
(2)通過then的鏈式調用串聯多個同步/異步任務
6. promise異常傳透?
(1)當使用promise的then鏈式調用時,可以再最后指定失敗的回調
(2)面前任務操作出了異常,都會傳到最后失敗的回調中處理
7. 中斷promise鏈
(1)當使用promise的then鏈式調用時,在中間中斷,不再調用后面的回調函數
(2)辦法:在回調函數中返回一個pendding狀態的promise對象
### **自定義Promise**
1. 定義整體結構
2. Promise構造函數的實現
3. promise.then() /catch()的實現
4. Promise.resolve() / reject() 的實現 //產生立即成功 立即失敗的結果的
5. Promise.all / race()的實現
6. Promise.resolveDelay() / rejectDelay() //自定義延遲時間成功或失敗的內容
7. ES5 fucntion版本
8. ES6 class版本
* [ 1. 基礎架子出來 ]

*****
*****
*****
*****
## async和await
[async函數](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function)
[await表達式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await)
1. async函數
函數的返回值為promise對象
promise對象的結果由async函數執行的返回值覺得
2. await表達式
await右側的表達式一般為promise對象,但也可以是其他的值
如果表達式是promise對象,await返回的是promise成功的值
如果表達式是其他的值,直接將此值作為await的返回值
3. 注意
await必須在async函數中,但async函數中可以沒有await
如果await的promise失敗了,就回拋出異常,需要通過try...catch來捕獲處理
*****
## Js運行機制

Heap(堆)、Stack(棧)、Queue(隊列)、Event Loop(事件輪詢)
### 程序中的堆棧隊列
**Heap(堆)**
堆, 是一種動態存儲結構,是利用完全二叉樹維護的一組數據,堆分為兩種,一種為**最大堆**,一種為**最小堆**,將根節點最大的堆叫做**最大堆**或**大根堆**,根節點最小的堆叫做**最小堆**或**小根堆**。堆是**線性數據結構**,相當于**一維數組**,有唯一后繼。

**棧(Stack)**
棧在程序中的設定是限定僅在表尾進行插入或刪除操作的線性表。棧是一種數據結構,它按照**后進先出**(`LIFO: last-in-first-out`)的原則存儲數據,先進入的數據被壓入棧底,最后的數據在棧頂,需要讀數據的時候從棧頂開始彈出數據。棧是只能在某一端插入和刪除的特殊線性表。

**隊列(Queue**)
隊列特殊之處在于它只允許在表的前端(`front`)進行刪除操作,而在表的后端(`rear`)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。?隊列中沒有元素時,稱為**空隊列**。
隊列的數據元素又稱為隊列元素。在隊列中插入一個隊列元素稱為入隊,從隊列中刪除一個隊列元素稱為出隊。因為隊列只允許在一端插入,在另一端刪除,所以只有最早進入隊列的元素才能最先從隊列中刪除,故隊列又稱為**先進先出**(`FIFO: first-in-first-out`)

### js中的堆棧隊列
下面我解釋下**JavaScript語言**中的堆、棧、隊列。
**堆**
堆, 動態分配的內存,大小不定也不會自動釋放,存放**引用類型**,指那些可能由多個值構成的對象,保存在堆內存中,包含引用類型的變量,實際上保存的不是變量本身,而是指向該對象的指針。可以簡單理解為存儲代碼塊。
堆的作用:存儲引用類型值的數據
```
let?obj?=?{
name:?'北歌',
????puslic:?'前端自學驛站'
}
let?func?=?()?=>?{
console.log('hello?world')
}
```
**棧**
js中的棧準確來將應該叫調用棧(EC Stack),會自動分配內存空間,會自動釋放,存放**基本類型**,簡單的數據段,占據固定大小的空間。
棧的作用:存儲基本類型值,還有一個很要的作用。**提供代碼執行的環境**
**隊列**
js中的隊列可以叫做**任務隊列**或**異步隊列**,任務隊列里存放各種異步操作所注冊的回調,里面分為兩種任務類型,宏任務(`macroTask`)和微任務(`microTask`)。
好,下面可以回到正題上來了。
### 為什么會出現Event Loop
總所周知JS是一門單線程的非阻塞腳本語言,Event Loop就是為了解決JS異步編程的一種解決方案。

### JS為什么是單線程語言,那它是怎么實現異步編程(非阻塞)運行的
第一個問題:JavaScript的誕生就是為了處理瀏覽器網頁的交互(DOM操作的處理、UI動畫等), ?設計成單線程的原因就是不想讓瀏覽器變得太復雜,因為多線程需要共享資源、且有可能修改彼此的運行結果(兩個線程修改了同一個DOM節點就會產生不必要的麻煩),這對于一種網頁腳本語言來說這就太復雜了。
第二個問題:JavaScript是單線程的但它所運行的宿主環境—瀏覽器是多線程,瀏覽器提供了各種線程供Event Loop調度來協調JS單線程運行時不會阻塞。
>代碼執行開啟一個全局調用棧(主棧)提供代碼運行的環境,在執行過程中同步任務的代碼立即執行,遇到異步任務將異步的回調注冊到任務隊列中,等待同步代碼執行完畢查看異步是否完成,如果完成將當前異步任務的回調拿到主棧中執行
******
## 進程和線程
進程:進程是 CPU 資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
線程:線程是 CPU 調度的最小單位(線程是建立在進程的基礎上的一次程序運行單位)
對于進程和線程并沒有確切統一的描述,可以簡單的理解
>比如一個應用程序: 如QQ、瀏覽器啟動時會開啟一個進程,而該進程可以有多個線程來進行資源調度和分配,達到運行程序的作用。
更通俗的話講:打開QQ應用程序開啟了進程來運行程序(QQ), 有多個線程來進行資源調度和分配(多個線程來分配打開QQ所占用的運存),達到運行程序(QQ)的作用.
用操作系統來作個例子:

>線程依賴進程,一個進程可以有一個或者多個線程,但是線程只能是屬于一個進程。
### JS的單線程
js的單線程指的是javaScript引擎只有一個線程
單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。js 引擎執行異步代碼而不用等待,是因有為有任務隊列和事件輪詢。
* 任務隊列:任務隊列是一個先進先出的隊列,它里面存放著各種任務回調。
* 事件輪詢:事件輪詢是指主線程重復從任務隊列中取任務、執行任務的過程。
### 瀏覽器的多線程
1. GUI 渲染線程
* 繪制頁面,解析 HTML、CSS,構建 DOM 樹,布局和繪制等
* 頁面重繪和回流
* 與 JS 引擎線程互斥,也就是所謂的 JS 執行阻塞頁面更新
3. JS 引擎線程
* 負責 JS 腳本代碼的執行
* 負責準執行準備好待執行的事件,即定時器計數結束,或異步請求成功并正確返回的事件
* 與 GUI 渲染線程互斥,執行時間過長將阻塞頁面的渲染
5. 事件觸發線程
* 負責將準備好的事件交給 JS 引擎線程執行
* 多個事件加入任務隊列的時候需要排隊等待(JS 的單線程)
7. 定時器觸發線程
* 負責執行異步的定時器類的事件,如 setTimeout、setInterval
* 定時器到時間之后把注冊的回調加到任務隊列的隊尾
9. HTTP 請求線程
* 負責執行異步請求
* 主線程執行代碼遇到異步請求的時候會把函數交給該線程處理,當監聽到狀態變更事件,如果有回調函數,該線程會把回調函數加入到任務隊列的隊尾等待執行
******
## Event Loop
>事件輪詢就是解決javaScript單線程對于異步操作的一些缺陷,讓 javaScript做到既是**單線程**,又絕對**不會阻塞**的核心機制,是用來協調各種事件、用戶交互、腳本執行、UI 渲染、網絡請求等的一種機制。
### 瀏覽器中的Eveent Loop執行順序
Processing model\[1\]規范定義了`Eveent Loop`的循環過程:
一個Eveent Loop只要存在,就會不斷執行下邊的步驟:
* 1.在tasks(任務)隊列中選擇最老的一個task,用戶代理可以選擇任何task隊列,如果沒有可選的任務,則跳到下邊的microtasks步驟。
* 2.將上邊選擇的task設置為正在運行的task\[2\]。
* 3.Run: 運行被選擇的task。
* 4.將Eveent Loop的currently running task\[3\]變為null。
* 5.從task隊列里移除前邊運行的task。
* 6.Microtasks: 執行microtasks任務檢查點\[4\]。(也就是執行microtasks隊列里的任務)
* 7.更新渲染(Update the rendering):可以簡單理解為瀏覽器渲染...
* 8.如果這是一個worker event loop,但是沒有任務在task隊列中,并且WorkerGlobalScope\[5\]對象的closing標識為true,則銷毀Eveent Loop,中止這些步驟,然后進行定義在Web workers\[6\]章節的run a worker\[7\]。
* 9.返回到第一步。
Eveent Loopp會不斷循環上面的步驟,概括說來:
* `Eveent Loop`會不斷循環的去取`tasks`隊列的中最老的一個task(可以理解為宏任務)推入棧中執行,并在當次循環里依次執行并清空`microtask`隊列里的任務。
* 執行完`microtask`隊列里的任務,有**可能**會渲染更新。(瀏覽器很聰明,在一幀以內的多次dom變動瀏覽器不會立即響應,而是會積攢變動以最高60HZ(大約16.7ms每幀)的頻率更新視圖)
### 宏任務和微任務優先問題
>在任務對列(queue)中注冊的異步回調又分為兩種類型,宏任務和微任務。我們為了方便理解可以認為在任務隊列中有宏任務隊列和微任務隊列。宏任務隊列有多個,微任務只有一個
* 宏任務(macro Task)
* script(整體代碼)
* setTimeout/setInterval
* setImmediate(Node環境)
* UI 渲染
* requestAnimationFrame
* ....
* 微任務(micro Task)
* Promise的then()、catch()、finally()里面的回調
* process.nextTick(Node 環境)
* ...
> 個人理解的執行順序:
1. 代碼從開始執行調用一個全局執行棧,script標簽作為宏任務執行
2. 執行過程中同步代碼立即執行,異步代碼放到任務隊列中,任務隊列存放有兩種類型的異步任務,宏任務隊列,微任務隊列。
3. 同步代碼執行完畢也就意味著第一個宏任務執行完畢(script)
* 1、先查看任務隊列中的微任務隊列是否存在宏任務執行過程中所產生的微任務
1-1、有的話就將微任務隊列中的所有微任務清空
2-2、微任務執行過程中所產生的微任務放到微任務隊列中,在此次執行中一并清空
* 2、如果沒有再看看宏任務隊列中有沒有宏任務,有的話執行,沒有的話事件輪詢第一波結束
2-1、執行過程中所產生的微任務放到微任務隊列
2-2、完成宏任務之后執行清空微任務隊列的代碼

>所以是宏任務優先,在宏任務執行完畢之后才會來一次性清空任務隊列中的所有微任務。
*****
## 宏隊列和微隊列

圖片中的mutation 監視dom 標簽屬性的改變
[mutation](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver/MutationObserver)
js是單線程執行的 先執行完同步代碼,再執行隊列中的
1. js中用來存儲待執行回調函數的隊列包含2個不同特定的隊列
2. 宏隊列:用來保存執行的宏任務(回調), 比如:定時器回調/DOM時間回調/ajax回調
3. 微隊列:用來保存執行的微任務(回調),比如:promise以及MutationObserver回調
4. js執行時,會區別2個隊列
(1)js引擎首先必須先執行所有的初始化同步任務代碼
(2)每次準備取出第一個宏任務前,都要將所有的微任務一個一個取出來執行
1.遇見await左側先執行,右側時候要跳出函數,等外部任務執行完再跳回
2.awati可以看成.then是一個微任務,放入微任務隊列,和其他微任務一樣,順序執行
3.執行微任務時,產生微任務順序放入當前微任務隊列,順序執行
4.遇見函數前面有async,awiat返回時,放入到Promise.then微任務隊列中,然后執行微任務隊列,如果前面沒有async,await返回時候直接執行,下次遇見await時候,再跳出執行微任務隊列
1.微任務中nextTick隊列在Promise.next前
******
## 作業
```
console.log('script?start');
setTimeout(()?=>?{
console.log('北歌');
},?1?*?2000);
Promise.resolve()
.then(function()?{
console.log('promise1');
}).then(function()?{
console.log('promise2');
});
async function?foo()?{
await?bar()
console.log('async1?end')
}
foo()
asyncfunction?errorFunc?()?{
try?{
awaitPromise.reject('error!!!')
??}?catch(e)?{
console.log(e)
??}
console.log('async1');
returnPromise.resolve('async1?success')
}
errorFunc().then(res?=>console.log(res))
function?bar()?{
console.log('async2?end')?
}
console.log('script?end');
```