# Day 5 深入理解 JavaScript 核心:函式、物件、原型鏈(上)
---
## 本日主題
1. 再談函式
2. IIFE
3. 從 Callback 到 Promise
4. 介紹 EventQueue
---
## 一級函式(First class functions)
什麼是一級函式?
一級函式 = 函式在該程式語言中可以被當作值(value)放在變數、物件或者當作叫用另一個函式的參數
----
>JavaScript treat function as a first-class-citizens. This means that functions are simply a value and are just another type of object.
在 JavaScript 中不僅函式擁有一級函式的特性,也被當作第一級公民,所以 JavaScript 中的函式其實是一個物件,也有自己的屬性
----
```javascript=
// 所以函式的記憶體可與變數連結
const greeting = (name) => console.log(`${name} says hi`);
// const greeting = function (name) {
// console.log(`${name} says hi`);
// }
greeting('Lun');
// Lun says hi
// 函式也可以當作物件屬性的值
const obj = {
method: () => console.log(1+1)
}
obj.method();
// 2
// 也可以被函式回傳
const sayHi = (name) => () => console.log(`${name} says Hi`);
// const sayHi = function (name) {
// return function () {
// console.log(`${name} says Hi`);
// }
// }
const myFunc = sayHi('Lun');
myFunc();
// Lun says Hi
```
---
## IIFE 立即函式
顧名思義,就是宣告的同時叫用
----
如何用 `var` 在迴圈內執行一個五秒內依序印出 0, 1, 2, 3, 4 的函式?
```javascript=
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
// 5
// 5
// 5
// 5
// 5
```
----
因為在 for 不會等待 setTimeout 執行完才跑下一次迴圈,會持續地跑完,等到跑完後讀取到的 `i` 已經是 5 了
用立即函式可以建立 function scope 保存每一次 i + 1 的值(因為使用 `var` 宣告的變數的最小有效範圍為 function )
----
```javascript=
for (var i = 0; i < 5; i ++) {
((i) => {
setTimeout(() => {
console.log(i);
}, i * 1000);
})(i)
}
// 0
// 1
// 2
// 3
// 4
```
----
也可以使用 `let` / `const` 因為 `let` 有 block scope 所以在每次迴圈內新增的函式都可以妥善的保存 `i` 的值
```javascript=
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
// 0
// 1
// 2
// 3
// 4
```
----
立即函式也可以有模組化的效果
```javascript=
const pokemon = (() => {
const name = '皮卡丘';
return {
attack: () => {
console.log(`${name} 使用電光石火!`);
},
sleep: () => {
console.log(`${name} 回寶貝球休息吧!`);
}
}
})();
pokemon.attack();
// 皮卡丘 使用電光石火!
```
---
## 從 Callback 到 Promise
Callback 是什麼東東?
----
> A callback is a function passed as an argument to another function.
> This technique allows a function to call another function.
> A callback function can run after another function has finished.
*[ref. JavaScript Callbacks](https://www.w3schools.com/js/js_callback.asp)*
----
Callback 就是函式,只是被叫用的時機不一樣(通常放在等什麼做完後才做、等待觸發某個事件後才做)
----
所以之後看文件發現方法需要帶入一個 callback 參數叫用也可以自己宣告一個函式,並把該函式放入對應的位置
```javascript=
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]
const sixLetterWords = (word) => word.length > 6;
const result2 = words.filter(sixLetterWords);
console.log(result2);
// expected output: Array ["exuberant", "destruction", "present"]
// [ref. Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
```
----
處理事件觸發時的任務
```javascript=
document.body.addEventListener('click', () => conosle.log('Hi from body!'));
// or
const handleClick = () => console.log('Hi from body!');
document.body.addEventListener('click', handleClick);
```
---
既然函式可以當作叫用其他函式的參數,那麼就也可以用 callback 處理「等待完成 A 再做 B 的情況」
```javascript=
// 假設目前需要接收 API 的資料
// 接收資料的函式
const fetchData = (resource, callback) => {
// 接收資料...
// 收到資料了,假如接收正常
if (ok) {
// 就做 B 吧
// 藉由參數來控制目前的狀態為何
callback(null, b);
} else if (not ok) {
// 就做 C 吧
callback(c, null);
}
}
fetchData('some resources', (error, success) => {
// 如果有 success 的話
if (success) {
// do something....
// 再叫資料
fetchData('some new resources', (error, success) => {
if (success) {
// do something...
} else {
// handle error
}
});
} else {
// handle error
}
});
```
---
## 處理非同步事件——從 Callback 到 Promise
假設要等使用者寫完文章再渲染到頁面上
```javascript=
// 希望等到寫完新的文章再渲染
const posts = [
{ title: '1', content: 'post 1' },
{ title: '2', content: 'post 2' },
{ title: '3', content: 'post 3' },
];
// 取得文章渲染
function getPosts (posts) {
const timer = (Math.random() + 1) * 1000;
console.log(`要等待的時間:${timer / 1000}秒(getPost)`);
// 模擬等待資料讀取的時間
setTimeout(() => {
posts.forEach((post) => {
const p = document.createElement('p');
p.innerHTML = `${post.title} -- ${post.content}`;
document.body.append(p);
});
}, timer);
}
// 撰寫文章
function writeNewPost ({ title, content }, callback) {
const timer = (Math.random() + 1) * 1000;
console.log(`要等待的時間:${timer / 1000}秒(writeNewPost)`);
setTimeout(() => {
posts.push({ title, content });
// 確保文章寫完才會印出來
callback(posts);
}, timer);
}
writeNewPost({ title: 'new post', content: 'Helloooo!' }, getPosts);
```
----
需要把 callback 傳入當作叫用函式的參數
```javascript=
function fetchData (num, callback) {
if (num < 3) {
callback('error', null);
} else {
callback(null, 'success');
}
}
fetchData(5, (error, data) => {
if (data) { console.log(data) }
else { console.log(error) }
});
// success
```
---
## Promise
Promise 有分三種狀態且必然會在其中一個狀態之中:pending(等待中)、fulfilled(已實現)或 rejected(已拒絕)。
==由 pending 轉為 fulfilled / rejected==
----

[*ref. Promise*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#incumbent_settings_object_tracking)
----
## 基本的應用
then 可以放兩個 callback arguments,第一個負責處理已經實現時,第二個負責處理被拒絕時
```javascript=
const promise = new Promise(function(resolve, reject) {
// 成功時
resolve(value)
// 失敗時
reject(reason)
})
promise.then(
function(value) {
// on fulfillment(已實現時)
},
function(reason) {
// on rejection(已拒絕時)
}
)
```
----
但使用 `.catch()` 捕捉錯誤(被拒絕時)程式碼的閱讀性比較好,而且當要執行多個非同步的情況中,只需要一個 `.catch()` 就可以捕捉錯誤
```javascript=
const promise = new Promise(function(resolve, reject) {
// 成功時
resolve(value)
// 失敗時
reject(reason)
})
promise.then(function (){
// 已經實現時
}).catch(function () {
// 已經被拒絕時
});
```
----
>Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.
[*ref. Using Promises*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)
與以前的 callback 處理非同步的方式不同,使用 Promise 可以把 callback 寫在回傳的 Promise 內
----
```javascript=
function fetchData (num) {
return new Promise((resolve, reject) => {
if (num < 3) {
reject('something went wrong!');
} else {
resolve('everything went well!');
}
});
}
fetchData(2).then((result) => console.log(result)).catch((error) => console.log(error));
// something went wrong!
fetchData(5).then((result) => console.log(result)).catch((error) => console.log(error));
// everything went well!
```
----
## Promise Chaining
只要回傳的是 Promise 就可以繼續使用 `.then()` 來依循執行任務
```javascript=
function funcA(){
return new Promise(function(resolve, reject){
window.setTimeout(function(){
console.log('A');
resolve('A');
}, (Math.random() + 1) * 1000);
});
}
function funcB(){
return new Promise(function(resolve, reject){
window.setTimeout(function(){
console.log('B');
resolve('B');
}, (Math.random() + 1) * 1000);
});
}
function funcC(){
return new Promise(function(resolve, reject){
window.setTimeout(function(){
console.log('C');
resolve('C');
}, (Math.random() + 1) * 1000);
});
}
funcA().then(funcB).then(funcC);
// A
// B
// C
```
----
回到寫文章的範例,但是用 Promise
```javascript=
// 希望等到寫完新的文章再渲染
const posts = [
{ title: '1', content: 'post 1' },
{ title: '2', content: 'post 2' },
{ title: '3', content: 'post 3' },
];
// 取得文章渲染
function getPosts () {
const timer = (Math.random() + 1) * 1000;
// 模擬等待資料讀取的時間
setTimeout(() => {
posts.forEach((post) => {
const p = document.createElement('p');
p.innerHTML = `${post.title} -- ${post.content}`;
document.body.append(p);
});
}, timer);
}
// 撰寫文章
function writeNewPost ({ title, content }) {
return new Promise((resolve, reject) => {
const timer = (Math.random() + 1) * 1000;
setTimeout(() => {
posts.push({ title, content });
// 確保文章寫完才會印出來
resolve();
}, timer);
});
}
writeNewPost({ title: 'title 12345', content: '6789' }).then(getPosts);
```
----
# Promise.all([])
不在乎誰先誰後,我想要一次獲得全部的結果
==適合用在等到全部 API 都回傳資料後再做其他事情==
- 陣列的索引值 !== 執行順序
- 如果輸入的不是 Promise 物件,會被 `Promise.resolve()` 轉換為已經實現(fulfilled)的 Promise
- 只要其中的一個 Promise 被拒絕(reject)就會立即終止其他 Promise 的執行,並回傳該被拒絕的 Promise 物件
----
等到最後都沒問題,可用 `.then(callback)` 取得以陣列形式包覆的值
```javascript=
// Promise.resolve() 會直接建立一個已經實現的 promise
const promise1 = Promise.resolve('My name is Lun.');
// 會被 Promise.resolve() 自動轉為一個已經實現的 promise
const promise2 = 'Hello';
const promise3 = new Promise((resolve, reject) => { setTimeout(resolve('third promise'), 1000)});
// 錯誤就會馬上停止其他 Promise 並回傳被拒絕的 Promise
// const promise4 = Promise.reject('No!');
Promise.all([promise1, promise2, promise3]).then((value) => console.log(value));
// ['My name is Lun.', 'Hello', 'third promise']
```
----
## Promise.race([])
只要其中一個好就好,其他的慢慢來就好
所以只會得到最先完成的結果,但是剩餘的 Promise 仍會繼續執行
----
```javascript=
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1'), 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p2'), 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p3'), 500)
})
Promise.race([p1, p2, p3])
.then(value => {
console.log(value)
})
.catch(err => {
console.log(err.message)
})
// p3
```
---
## async / await
讓非同步「看起來」像是同步
----
函式前面加上 `async` 才可以在函式裡面用 `await`。`await` 讓 JavaScript 知道在這個 `async` 函式裡面要等到 `await` 函式結束才可以繼續執行 `async` 函式內的任務
----
`async` 會強制函式回傳 Promise
```javascript=
function hello() { return "Hello" };
hello();
=> 'Hello'
async function hello () { return "Hello"};
hello();
=> Promise
```
----
使用 `Promise.all()` + async / await
----
async / await 配合事件打 API 資料
```javascript=
// return Promise 並等待處理,處理完會回傳需要的資料
const fetchData = () => {
const url = 'https://livejs-api.hexschool.io/api/livejs/v1/customer/lunnnnnnn/products';
return fetch(url)
.then((response) => response.json())
.then((data) => {
const { products } = data;
console.log([products]);
return products;
});
};
const createItem = (data) => {
let item = '';
data.forEach((x) => {
item += `<p>${x.title}</p>`;
});
return item;
};
document.querySelector('button').addEventListener('click', async () => {
// async / await 讓 js 得要執行完 await 的任務才可以繼續執行 async 之後的任務
const data = await fetchData();
const ele = createItem(data);
document.body.innerHTML = ele;
});
```
---
## 介紹 EventQueue
----
http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D
----
1. JS 引擎執行到 `setTimeout` 函式
2. JS 引擎會繼續執行,此時瀏覽器會繼續倒數計時
3. 當 `setTimeout` 函式的時間到了,就會被推到 EventQueue
4. 等主要執行環境空了的時,EventQueue 的任務就會被推到主要執行環境中執行
==所以 setTimeout 設定的時間只代表最少要等多久才會執行seTimeout 的 callback==
{"metaMigratedAt":"2023-06-16T16:28:12.959Z","metaMigratedFrom":"Content","title":"Day 5 深入理解 JavaScript 核心:函式、物件、原型鏈(上)","breaks":true,"contributors":"[{\"id\":\"b3f8e95e-e370-4508-94b8-608e400399b8\",\"add\":13232,\"del\":1867}]"}