JavaScript 核心:函式的七種寫法與使用時機
各位好!
這篇是 JavaScript 系列的第四單元,我們要深入學習函式(Function)。函式是程式設計的核心概念,讓我們可以組織可重複使用的程式碼。
什麼是函式?
函式就是一段可以重複使用的程式碼,你可以: - 給它一個名字 - 傳入參數(輸入) - 得到回傳值(輸出)
類比: 函式就像一台機器,你放入原料(參數),它處理後給你產品(回傳值)。
函式宣告(Function Declaration)
最傳統的函式寫法。
// 基本語法
function sayHello() {
console.log("Hello, World!");
}
// 呼叫函式
sayHello(); // "Hello, World!"
// 帶參數的函式
function greet(name) {
console.log(`你好,${name}!`);
}
greet("小明"); // "你好,小明!"
greet("小華"); // "你好,小華!"
// 多個參數
function add(a, b) {
return a + b;
}
const result = add(5, 3);
console.log(result); // 8
// 有回傳值的函式
function multiply(x, y) {
return x * y;
}
console.log(multiply(4, 5)); // 20
特性: - 會被「提升」(Hoisting):可以在宣告前呼叫 - 適合需要被提升的函式
函式表達式(Function Expression)
將函式賦值給變數。
const sayHello = function() {
console.log("Hello!");
};
sayHello(); // "Hello!"
// 帶參數
const greet = function(name) {
return `你好,${name}!`;
};
console.log(greet("小明")); // "你好,小明!"
特性: - 不會被提升:必須先宣告後使用 - 可以是匿名函式(沒有函式名稱)
箭頭函式(Arrow Function)
ES6 引入的現代寫法,更簡潔。
// 基本語法
const sayHello = () => {
console.log("Hello!");
};
// 單一參數可以省略括號
const greet = name => {
return `你好,${name}!`;
};
// 單行回傳可以省略 return 和大括號
const add = (a, b) => a + b;
console.log(add(5, 3)); // 8
// 回傳物件時要加括號
const createUser = (name, age) => ({ name, age });
console.log(createUser("小明", 25)); // { name: "小明", age: 25 }
// 多行程式碼
const calculateTotal = (price, quantity) => {
const subtotal = price * quantity;
const tax = subtotal * 0.05;
return subtotal + tax;
};
console.log(calculateTotal(100, 3)); // 315
特性:
- 語法更簡潔
- 沒有自己的 this(後續會詳細說明)
- 不能用作建構函式
- 現代 JavaScript 中最常用
參數的進階用法
預設參數
function greet(name = "訪客") {
return `你好,${name}!`;
}
console.log(greet()); // "你好,訪客!"
console.log(greet("小明")); // "你好,小明!"
// 箭頭函式也支援
const multiply = (a, b = 1) => a * b;
console.log(multiply(5)); // 5(b 使用預設值 1)
console.log(multiply(5, 3)); // 15
其餘參數(Rest Parameters)
// 接收不定數量的參數
function sum(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// 結合一般參數
function introduce(greeting, ...names) {
return `${greeting} ${names.join(", ")}`;
}
console.log(introduce("歡迎", "小明", "小華", "小美"));
// "歡迎 小明, 小華, 小美"
// 箭頭函式版本
const max = (...numbers) => Math.max(...numbers);
console.log(max(1, 5, 3, 9, 2)); // 9
解構參數
// 物件解構
function printUser({ name, age, city }) {
console.log(`${name},${age} 歲,來自 ${city}`);
}
const user = { name: "小明", age: 25, city: "台北" };
printUser(user); // "小明,25 歲,來自 台北"
// 陣列解構
function getFirstTwo([first, second]) {
return { first, second };
}
console.log(getFirstTwo([1, 2, 3, 4])); // { first: 1, second: 2 }
// 帶預設值的解構
function createCard({ title, content = "無內容", author = "匿名" }) {
return `標題:${title}\n內容:${content}\n作者:${author}`;
}
console.log(createCard({ title: "測試文章" }));
// 標題:測試文章
// 內容:無內容
// 作者:匿名
回傳值
單一回傳值
function add(a, b) {
return a + b;
console.log("這行不會執行"); // return 後的程式碼不會執行
}
// 沒有 return 時,回傳 undefined
function noReturn() {
console.log("Hello");
}
console.log(noReturn()); // 印出 "Hello",然後回傳 undefined
回傳物件
function createUser(name, age) {
return {
name: name,
age: age,
greet: function() {
return `我是 ${this.name}`;
}
};
}
const user = createUser("小明", 25);
console.log(user.name); // "小明"
console.log(user.greet()); // "我是 小明"
// ES6 簡寫(屬性名稱與變數名稱相同)
function createUser2(name, age) {
return { name, age }; // 等同於 { name: name, age: age }
}
提早返回
function checkAge(age) {
if (age < 0) {
return "年齡不可為負數";
}
if (age < 18) {
return "未成年";
}
return "成年人";
}
console.log(checkAge(-5)); // "年齡不可為負數"
console.log(checkAge(15)); // "未成年"
console.log(checkAge(25)); // "成年人"
立即執行函式(IIFE)
定義後立即執行的函式,用於建立獨立的作用域。
// 基本語法
(function() {
console.log("立即執行");
})();
// 帶參數
(function(name) {
console.log(`Hello, ${name}!`);
})("小明");
// 箭頭函式版本
(() => {
console.log("這也是 IIFE");
})();
// 實際應用:避免污染全域變數
(function() {
const privateVar = "這是私有變數";
console.log(privateVar); // 可以存取
})();
// console.log(privateVar); // 錯誤:privateVar 未定義
回呼函式(Callback)
將函式當作參數傳遞給另一個函式。
// 基本範例
function processArray(arr, callback) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
// 使用匿名函式
const doubled = processArray(numbers, function(n) {
return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// 使用箭頭函式
const squared = processArray(numbers, n => n ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
// 實際應用:陣列方法
const filtered = numbers.filter(n => n > 3);
console.log(filtered); // [4, 5]
const mapped = numbers.map(n => n * 10);
console.log(mapped); // [10, 20, 30, 40, 50]
高階函式(Higher-Order Function)
接收函式作為參數,或回傳函式的函式。
// 回傳函式
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 箭頭函式版本
const createAdder = amount => num => num + amount;
const add5 = createAdder(5);
const add10 = createAdder(10);
console.log(add5(3)); // 8
console.log(add10(3)); // 13
// 實用範例:記錄執行時間
function withTimer(fn) {
return function(...args) {
const start = Date.now();
const result = fn(...args);
const end = Date.now();
console.log(`執行時間:${end - start}ms`);
return result;
};
}
function slowFunction() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
const timedFunction = withTimer(slowFunction);
timedFunction(); // 印出執行時間
實戰練習
練習 1:溫度轉換器
const celsiusToFahrenheit = celsius => (celsius * 9/5) + 32;
const fahrenheitToCelsius = fahrenheit => (fahrenheit - 32) * 5/9;
console.log(celsiusToFahrenheit(25)); // 77
console.log(fahrenheitToCelsius(77)); // 25
// 通用轉換函式
function createConverter(fromUnit, toUnit) {
if (fromUnit === "C" && toUnit === "F") {
return celsiusToFahrenheit;
}
if (fromUnit === "F" && toUnit === "C") {
return fahrenheitToCelsius;
}
return temp => temp; // 相同單位不轉換
}
const cToF = createConverter("C", "F");
console.log(cToF(30)); // 86
練習 2:計算機函式集
const calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => b !== 0 ? a / b : "不能除以 0",
power: (base, exp) => base ** exp,
percentage: (num, percent) => (num * percent) / 100
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.divide(10, 2)); // 5
console.log(calculator.percentage(200, 15)); // 30
練習 3:資料驗證
function validateEmail(email) {
const hasAt = email.includes("@");
const hasDot = email.includes(".");
return hasAt && hasDot && email.length > 5;
}
function validatePassword(password) {
return password.length >= 8;
}
function validateForm(data) {
const errors = [];
if (!data.email || !validateEmail(data.email)) {
errors.push("Email 格式不正確");
}
if (!data.password || !validatePassword(data.password)) {
errors.push("密碼至少需要 8 個字元");
}
if (!data.name || data.name.trim() === "") {
errors.push("姓名為必填");
}
return {
isValid: errors.length === 0,
errors: errors
};
}
const result = validateForm({
email: "[email protected]",
password: "12345",
name: "小明"
});
console.log(result);
// { isValid: false, errors: ["密碼至少需要 8 個字元"] }
練習 4:陣列工具函式
// 找出最大值
const findMax = (...numbers) => Math.max(...numbers);
// 計算平均值
const average = (...numbers) => {
const sum = numbers.reduce((acc, n) => acc + n, 0);
return sum / numbers.length;
};
// 移除重複值
const unique = arr => [...new Set(arr)];
// 打亂陣列
const shuffle = arr => {
const copy = [...arr];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
};
console.log(findMax(1, 5, 3, 9, 2)); // 9
console.log(average(1, 2, 3, 4, 5)); // 3
console.log(unique([1, 2, 2, 3, 3, 3, 4])); // [1, 2, 3, 4]
console.log(shuffle([1, 2, 3, 4, 5])); // 隨機順序
函式命名最佳實踐
// 好的命名:清楚表達功能
function calculateTotalPrice(price, quantity) { }
function isUserLoggedIn() { }
function getUserById(id) { }
function validateForm(data) { }
// 不好的命名:不清楚、太短
function calc(p, q) { }
function check() { }
function get(x) { }
function do() { }
// 命名慣例
// - 動詞開頭:get, set, calculate, validate, is, has
// - 小駝峰式:firstName, calculateTotal
// - 具描述性:能看出函式在做什麼
常見錯誤與注意事項
1. 忘記回傳值
// 錯誤
function add(a, b) {
a + b; // 沒有 return
}
console.log(add(5, 3)); // undefined
// 正確
function add(a, b) {
return a + b;
}
2. 箭頭函式回傳物件
// 錯誤:會被誤認為函式主體
const createUser = (name) => { name: name };
// 正確:用括號包起來
const createUser = (name) => ({ name: name });
3. 參數數量
function greet(firstName, lastName) {
return `Hello, ${firstName} ${lastName}`;
}
console.log(greet("John")); // "Hello, John undefined"
// 解決:使用預設參數
function greet(firstName, lastName = "") {
return `Hello, ${firstName} ${lastName}`.trim();
}
小結
這篇我們學習了:
- 函式宣告與函式表達式
- 箭頭函式的簡潔語法
- 參數的各種用法(預設參數、其餘參數、解構)
- 回呼函式與高階函式
- 實用的函式設計模式
下一步:
完成這篇後,你已經能夠: - 撰寫各種形式的函式 - 組織可重複使用的程式碼 - 使用現代 JavaScript 語法
前往下一篇:單元五:陣列操作
在那裡,我們會學習如何處理資料集合。
0 留言
發表留言