# 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== ---- ![](https://i.imgur.com/oqdyVuM.png) [*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}]"}
    380 views