--- title: JavaScript核心篇 - 函式 tags: description: --- JavaScript核心篇 - 函式 === ### 函式本身 function - 可以執行片段程式碼。 - 可以重複呼叫。 - 可以傳入不同的參數,改變執行的結果。 ```javaScript= function callSomeone(name) { console.log(`嘿,這是${name}`); }; callSomeone('小明'); // 嘿,這是小明 ``` - 函式本身也是一個物件。 ```javaScript= callSomeone.myName = '我是Ron'; console.dir(callSomeone); ``` ![](https://i.imgur.com/8GQK4P8.png =50%x) - 限制作用域。 - 函式可以回傳值(**return**)。 --- ### 立即函式 - 限制作用域。 - 立即執行。 - 可以回傳一個結果。 :::success **函式陳述式**一定要有函式名稱(具名函式)。 立即函式內沒有函式名稱(匿名函式),所以是**函式表達式**。 ::: --- ### 一級函式 :::success **函式**在該語言可**被視為與其他變數一樣時**,就可以稱該語言具有**一級函式**。 ::: - 函式可作為**變數**(函式表達式)。 ```javascript= const fn = function (name){ return `hello ${name}`; }; console.log(fn('joe')); // hello joe // 賦予給其他變數 let str = fn('joe'); console.log(str); // hello joe ``` - 函式可做為**參數**使用 - 函式當參數傳遞就是**callback Function** - 第2行`callbackFunction()`,就是第5行`fn()`代入的匿名函式。 ```javascript= const fn = function (callbackFunction){ callbackFunction(); }; // 把匿名函式代到fn裡面去執行 fn(function() { console.log(`這是callback`); }); ``` - 進階的**callback Function** - 匿名函式代入fn()。 - `callbackFunction(a, b)`取得`const a`、`const b`組成字串。 ```javascript= function fn(callbackFunction) { const a = 'joe'; const b = 'jojo'; callbackFunction(a, b); }; fn(function (x, y) { console.log(`${x},${y}是好朋友`); }); ``` - 函式可以作為回傳值(函式裡還能回傳函式) ```javascript= function fn() { return function () { console.log(`這是回傳的函式`); } }; console.log(fn()); ``` 第7行回傳的結果只有函式。 ![](https://i.imgur.com/HNqqj01.png =50%x) 再把**return**的函式執行 ```javascript=7 fn()(); // 這是回傳的函式 ``` --- ### 高階函式(技法) :::success 特性跟一級函式相同,高階函式是指用的寫法、技法(概念)。 ::: - 商品與商品折扣寫法 - 第8行,把`itemPrice()`帶入價格100,賦予給變數 `brandPrice`。 - 第10行,`brandPrice(0.5)`會呼叫`return`的函式,並且把折扣代入。 - `itemPrice()`內的匿名函式,會從外層取得`originPrice`,跟value計算取得折扣後價格,並回傳。 ```javascript= function itemPrice(price) { const originPrice = price; return function(value) { return originPrice * value; }; }; const brandPrice = itemPrice(100) console.log(brandPrice(0.5)); // 50 console.log(brandPrice(0.8)); // 80 ``` --- ### 閉包(closure) :::success 1. 閉包就是**內層函式**可以**取得外層函式作用域內**的變數。反過來說,外層函式**無法存取內層函式**作用域內的變數。 2. 閉包**作用域內**的變數,若有被**內層匿名函式調用**。則閉包作用域內變數的記憶體**不會被釋放掉**。 3. 閉包作用域內**反覆調用**的變數,也稱為**私有**(private )變數。 ::: - 內層函式調用外層的變數`str` ```javascript= function fn() { let str = `內層函式訪問外層作用域,稱為閉包。`; return function() { debugger; console.log(str); }; }; const x = fn(); x(); // 內層函式訪問外層作用域,稱為閉包。 ``` - 透過高階函式特性(回傳函式),將作用域內的變數**不被釋放**。 ```javascript= function sellEgg(price) { const eggPrice = price; let count = 0; return function (value) { // 會調用外層變數count count += value; console.log(`一共賣出${count}個蛋,賺了${eggPrice * count}元`); }; }; // 把函式sellEgg()代入雞蛋價格15元,並賦予給變數todaySell const todaySell = sellEgg(15); // 再把雞蛋數量10,賦予到sellEgg()內層函式 todaySell(10); // 假設下週雞蛋變便宜,代入雞蛋價格13元,並賦予給變數nextWeekSell const nextWeekSell = sellEgg(13); nextWeekSell(10); ``` 從上面範例可以知道**閉包的特性**:point_down: - **可重複調用** - 能夠賦予給不同變數使用。 - **獨立變數** - 在第6、17行,閉包賦予給不同變數之後,閉包與閉包之間,作用域內的資料都**完全獨立**。 - **隱藏變數** - 上面範例第2行,eggPrice是**被封裝**在`sellEgg()`作用域內,**全域**或**其他函式**都無法調用。 --- ### 閉包私有方法 在下面範例中 - 創造一個wallet的函式(物件),並用**3種方法**存取內層變數`money`。 - 這裡的變數`money`就相當於物件導向中的**私有成員變數** (private member)。 ```javascript= function wallet(initValue) { // wallet()不帶參數的話,預設值就是0 let money = initValue || 0; return{ increase: (value) => { money += value; console.log(`錢包增加了${value}元,現在剩${money}元`); }, decrease: (value) => { money -= value; console.log(`錢包減少了${value}元,現在剩${money}元`); }, watch: () => { console.log(`現在錢包一共有${money}元`); }, }; }; // wallet(1000)初始給錢包1000元,賦予給變數fatherWallet const fatherWallet = wallet(1000); // 實際是去操作fatherWallet裡return的物件 fatherWallet.increase(100); fatherWallet.decrease(500); fatherWallet.watch(); ``` - const `fatherWallet`實際上是==物件==,因為`wallet(1000)`等同定義一個作用域裡面的物件**return**給`fatherWallet`。這個物件裡面**又包含了多個函式**(方法)。 ![](https://i.imgur.com/dT4AYh4.png) --- ### 使用`立即函式`創造獨立的環境(Immediately Invoked Function Expression, IIFE) 這段程式碼運行時,會印出什麼? ```javascript= for (var i = 0; i <= 5; ++i) { setTimeout(function() { console.log(i) }, 1000 * i) } ``` 直覺想到會印出1、2、3、4、5。 但實際上會每一秒印出連續五個`5`, **為什麼?** 1. 在`for`的**作用域內**的`setTimeout()`還未執行時,`for`迴圈已經跑完5次,會產生5個`setTimeout()`。 2. `var i` 是全域變數,`for`迴圈跑完五次時,`i = 5`, 3. 每隔**1秒**`setTimeout()`內的**callback function**調用`i`時,取到的值是`5`。 <br> **如果要每隔一秒印出1、2、3、4、5**該怎麼做? 重點是要讓每個**callback function**有自己的變數`i` :::success - 利用閉包的特性,利用**立即函式**將`setTimeout()`變成獨立的環境。 ```javascript= ((x) => { setTimeout(() => { console.log(x); }, 1000 * x); })(i) ``` - 執行立即函式參數`i`傳入後,複製到箭頭函式參數`x`,讓`setTimeout()`內的**callback function**調用參數`x`,因此參數`x`在**IIFE作用域**不會被釋放掉。 - 因而`setTimeout()`的**callback function**可以取得各自的變數`x`。 <br> 完整程式碼 ```javascript= for (var i = 1; i <= 5; i++) { ((x) => { setTimeout(() => { console.log(x); }, 1000 * x); })(i) }; ``` ::: 另一種解決方式就是把`var i`改成`let i` 改成`let i`之後`i`的作用域就被限制在`for`迴圈的**作用域內** 每次執行`setTimeout()`的**callback function**便能在**外層**取得各自的變數`i`。 ```javascript= for (let i = 1; i <= 5; i++) { setTimeout(() => { console.log(i); }, 1000 * i); }; ```