JavaScript 核心:函式的七種寫法與使用時機

2026-01-26 07:49 | By justin | JavaScript
(Updated: 2026-01-26 07:49)

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 留言

目前沒有留言

發表留言
回覆