為何會用到函式
因為同樣的邏輯常常要重複用很多次,把它包成函式,以後只要「呼叫一下」就能重複使用,不用每次都重寫一遍。
想像一下:你家巷口有一台自動販賣機。
你投入 20 元,選了可樂,機器「動作」一下,吐出一瓶可樂給你。
這台機器做的事情,其實就是「函式」在做的事:
輸入(投入的錢、選的按鈕)→ 對應到函式的「參數」
內部處理(機器判斷、掉貨)→ 對應到函式「裡面的程式碼」
輸出(吐出可樂)→ 對應到函式的「回傳值(return)」
範例一 泡麵機
假設你要寫一個「泡泡麵」的動作。每次泡麵步驟都一樣,所以我們把它包成一個函式,以後要用就直接「呼叫」它,不用重寫一次步驟
function 泡泡麵() {
console.log("倒入熱水");
console.log("蓋上蓋子");
console.log("等三分鐘");
console.log("開動!");
}
泡泡麵(); // 呼叫函式,執行裡面的動作
//倒入熱水
//蓋上蓋子
//等三分鐘
//開動!
如果你只定義函式,不呼叫它,就像你把泡麵機買回家但沒插電、沒按開始鍵,它什麼事都不會做
範例二 有參數的函式:點餐機
自動販賣機厲害的地方是,你可以選「你要哪一款飲料」。這就是參數——讓函式可以根據不同輸入,做不同的事
// function 點餐(飲料名稱)
function a (b) {
console.log("老闆,我要一杯" + b);
}
a ("珍珠奶茶");
a ("紅茶");
// 輸出 老闆,我要一杯珍珠奶茶
// 輸出 老闆,我要一杯紅茶
飲料名稱(b) 就是參數,是一個「空位」,你呼叫函式的時候把實際的東西(叫做「引數」)塞進去,函式裡面就會用你給的東西去運作。
就像點餐機的螢幕上先寫著「請選擇飲料:____」,你選了什麼,它就處理什麼。
範例三 有回傳值的函式:自動找錢機
如果你付錢買飲料,付太多,機器不會只是「做一件事」,它還要吐錢給你。這個「吐出來的東西」就是 return(回傳值)。
//function 找零(付的錢, 飲料價格)
function a (b, c) {
return b - c;
}
let money = a(50, 35);
console.log(money); // 輸出15
這裡的重點:
return 就是機器把結果「吐出來」給你
你可以用一個變數(money)去「接住」這個吐出來的結果
有 return 的函式,才能把結果拿去做別的事(例如再拿去計算、印出來、存起來)
如果沒有 return,函式就像一台機器只會「動作」,但不會「吐東西出來」給你用——這是很多初學者會搞混的地方。
範例四 算存錢進度(存錢買遊戲機)
假設你想存錢買一台遊戲機,想知道「我還差多少錢」。這個計算每次算法都一樣,所以可以包成函式,以後不管想買什麼、存了多少,都能重複用。
//function 計算還差多少錢(目標金額, 已經存的錢)
function a (goal, money) {
let Sub = goal - money;
return Sub;
}
let total = a (3000, 1200);
console.log("還差 " + total + " 元"); // 輸出還差 1800 元
想像這個函式就是你的「記帳小幫手」:
你把「goal」和「money」餵給它(參數)
它自己在裡面算出還差多少(內部處理)
最後把結果丟還給你(return)
你把結果存進 還差 這個變數,拿去印出來或做其他事
以後不管你想存錢買球鞋、買漫畫、還是買遊戲機,都可以重複呼叫這個函式,不用每次都手動重算一次——這就是函式最大的用處:把重複的邏輯包起來,重複使用。
1.多個參數
一台機器可以同時吃好幾個輸入,不是只能一個
// function 算總金額(單價, 數量, 折扣)
function total (onePrice, qty, onSafe) {
let price = onePrice * qty;
let 折扣後金額 = price - onSafe;
return 折扣後金額;
}
let totalCost = total(35, 2, 10); // 單價35,買2份,折扣10元
console.log("總共要付 " + totalCost + " 元");
// 輸出 總共要付 60 元
2.預設參數(沒給值時的預設值)
function a(名字 = "Renee") {
console.log("哈囉," + 名字);
}
a(); // 輸出 哈囉,Renee
3.函式可以呼叫函式
一台機器裡面,可以包著另一台小機器一起運作
function calculateTotal(price, quantity) {
return price * quantity; //checkout 中的值輸入35*2後傳回
}
function calculateChange(paid, total) {
return paid - total; //輸入100-70 後傳回
}
function checkout (price, quantity, paid) {
let total = calculateTotal(price, quantity); // 呼叫第一個函式
let change = calculateChange(paid, total); // 呼叫第二個函式
console.log("Total: " + total); //輸出 Total: 70
console.log("Change: " + change);//輸出 Change: 30
}
checkout(35, 2, 100); //呼叫 checkout函式
重點:
checkout 這個函式裡面,呼叫了 calculateTotal 和 calculateChange 兩個「小機器」
就像早餐店老闆結帳時,腦中先「算總價」,再拿去「算找零」,是兩個步驟分開處理,但合起來完成一件事
這樣寫的好處:每個函式只做一件小事,程式碼比較好懂、好抓錯
4.箭頭函式(arrow function)寫法
這是跟 function 意思一樣,但寫法比較精簡的另一種語法
// 傳統 function 寫法
function add(a, b) {
return a + b;
}
// 箭頭函式寫法(意思完全一樣)
const addArrow = (a, b) => {
return a + b;
};
// 箭頭函式還可以再省略,如果只有一行 return,可以省略 {} 和 return
const addShort = (a, b) => a + b;
console.log(add(3, 5)); // 這是傳統 function 寫法,輸出 8
console.log(addArrow(3, 5));// 這是箭頭函式寫法,輸出 8
console.log(addShort(3, 5));// 這是箭頭函式在省略寫法,輸出 8
重點:
三種寫法,結果完全一樣,只是外型不同
箭頭函式用 const 名稱 = (參數) => { 內容 } 這種寫法
如果函式內容只有一行、而且是 return,可以直接省略大括號和 return,寫成 (a, b) => a + b
5.作用域(scope)——函式裡面的變數,外面拿不到
想像函式就像一個密室。密室裡面發生的事情、放的東西,密室外面的人看不到、拿不到。除非你把東西「拿出來」(用 return),外面才看得到
外面看不到裡面的變數
function makeDrink() {
let secretRecipe = "sugar + tea + ice";
console.log(secretRecipe);// 輸出 sugar + tea + ice
}
makeDrink();
console.log(secretRecipe);
// Uncaught ReferenceError: secretRecipe is not defined
外面的變數,裡面看得到(單向的)
密室是單向透明的——密室裡面看得到外面的東西,但外面看不到密室裡面的東西
let allowance = 100; // 外面的變數(全域變數)
function buySnack() {
console.log("I have " + allowance + " dollars"); // 裡面可以看到外面的 allowance
}
buySnack();//輸出 I have 100 dollars
同名變數,裡面外面互不干擾
let name = "Renee"; // 外面的 name
function greet() {
let name = "Guest"; // 這是密室裡面自己的 name,跟外面的是兩個不同的東西
console.log("Hello, " + name);
}
greet(); //輸出 Hello, Guest
console.log(name); // 輸出 Renee,外面的 name 完全沒被改變
重點: 密室裡面宣告的 name,只在密室裡面有效。密室外面的 name 完全不受影響,兩個是獨立的變數,只是剛好取了一樣的名字
兩個密室,互相看不到彼此
兩台不同的機器,也不會知道對方裡面放了什麼
function roomA() {
let item = "book";
console.log("Room A has: " + item);
}
function roomB() {
console.log("Room B trying to access: " + item); // 這裡錯誤!
}
roomA();//輸出 Room A has: book
roomB();//輸出 Uncaught ReferenceError: item is not defined
roomA 密室裡面的 item,roomB 密室完全不知道它存在,就算兩個密室是「隔壁」也一樣互相看不到
想把密室裡的東西「拿出來」怎麼辦?— 用 return
function packLunch() {
let food = "sandwich";
return food; // 把密室裡的東西「遞出來」
}
let myLunch = packLunch(); // 外面用一個變數接住它
console.log(myLunch); // 輸出 sandwich
重點: 這就是為什麼 return 很重要——它是密室唯一能把東西「合法遞出來」的方式。沒有 return,外面永遠拿不到密室裡面的東西
函式中使用 ${} (樣板字面值 Template Literals)
範例一 早餐店點餐
function orderDrink(drinkName, price) {
console.log(`I ordered a ${drinkName}, it costs ${price} dollars.`);
}
orderDrink("milk tea", 45);
//輸出 I ordered a milk tea, it costs 45 dollars.
數字加總
${} 裡面可以放運算式,不是只能放變數
function add(num1,num2){
console.log(`加總數字為${num1+num2}`)
}
add(5,10); //輸出 加總數字為15
function checkout(price, quantity) {
console.log(`Total: ${price * quantity} dollars`);
}
checkout(35, 3); //輸出 Total: 105 dollars
搭配 return,組合出一整句話再送出去
function makeReceipt(item, price) {
return `You bought ${item} for ${price} dollars.`;
}
let receipt = makeReceipt("bread", 30);
console.log(receipt); //輸出 You bought bread for 30 dollars.
多個變數、多行文字一起組合
function introduce(name, age, hobby) {
return `My name is ${name}.
I am ${age} years old.
I like ${hobby}.`;
}
console.log(introduce("Renee", 20, "coding"));
//輸出 My name is Renee.
// I am 20 years old.
// I like coding.
重點: 反引號 還有一個好處——可以直接換行寫,不用像一般字串那樣還要加 \n
搭配條件判斷,依情況顯示不同文字
function checkAllowance(money) {
return `You have ${money} dollars. You have ${money >= 100 ? "enough" : "not enough"} money to buy the toy.`;
}
console.log(checkAllowance(150));// 輸出 You have 150 dollars. You have enough money to buy the toy.
console.log(checkAllowance(50));// 輸出 You have 50 dollars. You have not enough money to buy the toy.