【JavaScript 筆記】函數(上) - part 5
歡迎你點入本篇文章,本系列網頁程式設計,主要紀錄我個人自學的軌跡,另外也作為日後個人複習用。若你喜歡本篇文章,歡迎在文章底下點一顆愛心,或是追蹤我的個人公開頁~
基本函數語法
宣告一個函數使用關鍵字 function 宣告,如下:
1 2 3 4
| function functionName(parameter1, parameter2) { return returnValue; }
|
範例:
1 2 3 4 5 6
| function greet(name) { console.log(`哈囉,${name}!`); }
greet("LukeTseng"); greet("Amy");
|
函數宣告有完整的 Hoisting,可以在宣告之前就呼叫,JS 引擎會自動把整個函數上升(Hoisting)到最頂端:
1 2 3 4 5
| sayHi();
function sayHi() { console.log("Hi!"); }
|

呼叫帶有參數的函數
一般參數
呼叫時傳入的值叫引數(Argument),函數定義裡接收的變數叫參數(Parameter) :
1 2 3 4 5 6 7 8 9
| function add(a, b) { console.log(a + b); }
add(3, 5); add(10, 20);
add(10);
|
預設參數(Default Parameters)
ES6 新增,當呼叫時沒有傳入對應引數(或傳入 undefined),就自動使用預設值:
1 2 3 4 5 6 7 8
| function greet(name = "訪客", greeting = "哈囉") { console.log(`${greeting},${name}!`); }
greet(); greet("LukeTseng"); greet("LukeTseng", "嗨"); greet(undefined, "嗨");
|
有回傳值的函數
用 return 語句讓函數回傳一個值,呼叫後可接收這個結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function calculateBMI(height, weight) { let bmi = weight / ((height / 100) ** 2); return bmi; }
let result = calculateBMI(175, 70); console.log(result.toFixed(2));
function checkAge(age) { if (age < 0) { return "年齡不合法"; } return `你 ${age} 歲`; }
console.log(checkAge(-1)); console.log(checkAge(21));
|
須注意函數沒有 return 或 return 後面沒有值時,預設回傳 undefined。
函數的作用域(Scope)
所謂作用域亦即變數在哪個範圍內有效,函數會建立自己的區域作用域(Local Scope),內部宣告的變數在外部無法存取:
1 2 3 4 5 6 7 8 9 10 11
| let globalVar = "我是全域變數";
function myFunc() { let localVar = "我是區域變數"; console.log(globalVar); console.log(localVar); }
myFunc(); console.log(globalVar); console.log(localVar);
|
作用域鏈(Scope Chain)
JS 找變數時,會從最內層往外層一層一層找,直到找到全域為止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let x = "全域";
function outer() { let x = "外層函數";
function inner() { let x = "內層函數"; console.log(x); }
inner(); console.log(x); }
outer(); console.log(x);
|
函數種類
1. Anonymous Function(匿名函數)
亦即沒有名字的函數,通常用在不需要重複呼叫的一次性場合:
1 2 3 4 5 6 7 8 9
| setTimeout(function() { console.log("2秒後執行"); }, 2000);
[1, 2, 3].forEach(function(num) { console.log(num * 2); });
|
2. Function Expression(函數運算式)
把函數存入變數,這個變數就代表這個函數:
1 2 3 4 5 6
| const square = function(n) { return n * n; };
console.log(square(5)); console.log(typeof square);
|
與函數宣告的差別:Function Expression 不會 Hoisting,必須先定義才能呼叫。
1 2
| console.log(square(3)); const square = function(n) { return n * n; };
|

3. Arrow Function(箭頭函數)
ES6 新增,語法最簡潔,是現代 JS 最常用的函數寫法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const add = (a, b) => { return a + b; };
const double = n => { return n * 2; };
const triple = n => n * 3;
const sayHi = () => console.log("Hi!");
console.log(add(3, 4)); console.log(double(5)); console.log(triple(4)); sayHi();
const getUser = (name) => ({ name: name, role: "student" }); console.log(getUser("LukeTseng"));
|
4. IIFE(立即呼叫函數運算式)
IIFE 全名為 Immediately Invoked Function Expression。
意思就是定義完馬上執行,執行完即消失,不會污染全域作用域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| (function() { let secret = "這個變數只存在這裡"; console.log("IIFE 執行了!"); console.log(secret); })();
console.log(typeof secret);
(function(name) { console.log(`哈囉,${name}!`); })("LukeTseng");
(() => { console.log("箭頭 IIFE!"); })();
|

IIFE 常用於初始化設定、模組封裝等需要立刻執行但不想暴露內部變數的場合。
5. Callback Function(回呼函數)
把函數當作引數傳給另一個函數,讓那個函數在適當時機呼叫它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function doTask(task, callback) { console.log(`正在執行:${task}`); callback(); }
function onComplete() { console.log("任務完成!"); }
doTask("洗碗", onComplete);
[3, 1, 4, 1, 5].sort((a, b) => a - b); [1, 2, 3].map(n => n * 10); [1, 2, 3, 4].filter(n => n % 2 === 0);
console.log("開始"); setTimeout(() => { console.log("2秒後執行"); }, 2000); console.log("結束");
|
6. Rest Parameter Function(其餘參數函數)
用 ... 把不定數量的引數全部收集成一個陣列,讓函數接受任意多個參數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function sum(...numbers) { let total = 0; for (let n of numbers) { total += n; } return total; }
console.log(sum(1, 2)); console.log(sum(1, 2, 3, 4)); console.log(sum(10, 20, 30, 40));
function introduce(greeting, ...names) { names.forEach(name => console.log(`${greeting},${name}!`)); }
introduce("哈囉", "LukeTseng", "Amy", "Bob");
|
函式種類比較
| 種類 | 名稱有無 | Hoisting | 主要用途 |
|---|
| 函數宣告 | Y | 完整提升 | 一般功能函數 |
| Function Expression | 可有可無 | N | 存入變數、有條件建立 |
| Anonymous Function | N | N | 一次性使用,傳入 Callback |
| Arrow Function | 可有可無 | N | 現代主流,簡潔語法 |
| IIFE | 可有可無 | N(立即執行) | 初始化、避免污染全域 |
| Callback Function | 可有可無 | 視宣告方式 | 事件處理、非同步操作 |
| Rest Parameter | 通常有 | 視宣告方式 | 不定數量引數的函數 |
函數綁定(Function Binding)
Function Binding(函數綁定)是決定函數執行時,this 關鍵字指向哪個物件的機制。
為什麼需要 Function Binding?
在 JS 中,函數是獨立存在的物件,同一個函數可以被不同的物件呼叫,此時就會有一個問題:函數內的 this 到底是誰?
1 2 3 4 5 6 7 8 9 10 11 12 13
| const luke = { name: "Luke" }; const amy = { name: "Amy" };
function greet() { console.log(`Hello, I'm ${this.name}`); }
luke.greet = greet; amy.greet = greet;
luke.greet(); amy.greet();
|
Function Binding 要解決的問題
當函數被 “轉手傳遞” 後,this 的指向很容易意外脫落,變成 window 或 undefined:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const student = { name: "Luke", greet() { console.log(`我是 ${this.name}`); } };
student.greet();
const fn = student.greet; fn();
setTimeout(student.greet, 1000);
|
Function Binding 就是為了解決 this 脫落的問題,明確控制函數執行時 this 要指向誰。
this Binding(this 綁定)
this 是一個在函數執行當下才決定值的關鍵字,它不是在定義時決定,而是在呼叫時決定,根據呼叫方式不同,this 有四種綁定規則:
規則一:預設綁定(Default Binding)
函數被直接呼叫(不附屬於任何物件),this 指向全域物件(瀏覽器中是 window),嚴格模式下為 undefined:
1 2 3 4 5
| function showThis() { console.log(this); }
showThis();
|
規則二:隱含式綁定(Implicit Binding)
函數被當作物件的方法呼叫,this 指向呼叫它的那個物件:
1 2 3 4 5 6 7 8 9 10 11 12
| const student = { name: "Luke", greet() { console.log(`我是 ${this.name}`); } };
student.greet();
const fn = student.greet; fn();
|
規則三:顯式綁定(Explicit Binding)
用 call、apply、bind 強制指定 this 的目標,接下來會詳細說明。
規則四:new 關鍵字綁定
用 new 呼叫函數時,this 指向新建立的物件:
1 2 3 4 5 6 7 8 9
| function Person(name) { this.name = name; this.greet = function() { console.log(`我是 ${this.name}`); }; }
const luke = new Person("Luke"); luke.greet();
|
四種規則的優先順序
當多個規則同時存在時,優先順序由高到低為:
new 綁定 > 顯式綁定(call/apply/bind)> 隱含式綁定 > 預設綁定
Permanent Binding(永久綁定)
bind() 建立的新函數,其 this 被永久固定,無論之後怎麼呼叫、再次 call/apply 都無法改變:
1 2 3 4 5 6 7 8 9 10 11 12
| const user = { name: "Luke" }; const admin = { name: "Admin" };
function sayName() { console.log(this.name); }
const boundFn = sayName.bind(user);
boundFn(); boundFn.call(admin); boundFn.apply(admin);
|
Partial Application(部分應用)
bind() 除了綁定 this,還可以預先填入部分參數,產生一個新的、已預填某些引數的函數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2); const triple = multiply.bind(null, 3);
console.log(double(5)); console.log(double(10)); console.log(triple(5));
function calculateTax(rate, price) { return price * rate; }
const vat5 = calculateTax.bind(null, 0.05); const vat10 = calculateTax.bind(null, 0.10);
console.log(vat5(1000)); console.log(vat10(1000));
|
Methods of Function Binding
1. bind()
建立一個新的函數,this 永久綁定,不立即執行 :
語法:
1
| const newFn = 原函數.bind(thisArg, arg1, arg2, ...)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const person = { name: "Luke", school: "國立XX大學" };
function introduce(age, job) { console.log(`我是 ${this.name},${age} 歲,在 ${this.school} 上學,職業:${job}`); }
const lukeSelfIntro = introduce.bind(person, 21);
lukeSelfIntro("學生");
const timer = { name: "計時器", start() { setTimeout(function() { console.log(`${this.name} 啟動`); }, 1000);
setTimeout(function() { console.log(`${this.name} 啟動`); }.bind(this), 1000); } };
|
2. call()
立即執行函數,同時指定 this 與參數,參數逐一傳入:
語法:
1
| 函數.call(thisArg, arg1, arg2, ...)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const player1 = { name: "Luke", score: 95 }; const player2 = { name: "Amy", score: 87 };
function showInfo(level, server) { console.log(`玩家:${this.name},分數:${this.score},等級:${level},伺服器:${server}`); }
showInfo.call(player1, "鑽石", "台灣");
showInfo.call(player2, "黃金", "馬來西亞");
function collectArgs() { const arr = Array.prototype.slice.call(arguments); console.log(arr); } collectArgs(1, 2, 3);
|
3. apply()
與 call() 幾乎相同,差別在於參數要包在陣列裡傳入:
語法:
1
| 函數.apply(thisArg, [arg1, arg2, ...])
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const player1 = { name: "Luke", score: 95 };
function showInfo(level, server) { console.log(`玩家:${this.name},等級:${level},伺服器:${server}`); }
showInfo.apply(player1, ["鑽石", "台灣"]);
const scores = [87, 45, 99, 72, 63];
console.log(Math.max.apply(null, scores));
console.log(Math.max(...scores));
|
bind / call / apply 三者比較
| 特性 | bind() | call() | apply() |
|---|
| 是否立即執行 | 回傳新函數 | 立即執行 | 立即執行 |
| 傳入參數方式 | 逐一傳入 | 逐一傳入 | 陣列傳入 |
this 是否永久固定 | 永久固定 | 僅此次 | 僅此次 |
| 應用 | 事件監聽、計時器修正 | 函數借用 | 展開陣列參數 |
Arrow Functions and this Binding
箭頭函數沒有自己的 this,它的 this 是在定義當下從外層作用域繼承進來的,稱為詞法綁定(Lexical Binding,亦稱靜態綁定)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const student = { name: "Luke",
greetNormal: function() { console.log(`一般函數:${this.name}`); },
greetArrow: () => { console.log(`箭頭函數:${this.name}`); } };
student.greetNormal(); student.greetArrow();
|
解決巢狀的 this 問題
這是箭頭函數設計出來最主要要解決的問題:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const team = { name: "資工系", members: ["Luke", "Amy", "Bob"],
listMembersNormal: function() { this.members.forEach(function(member) { console.log(`${this.name} 的成員:${member}`); }); },
listMembersArrow: function() { this.members.forEach((member) => { console.log(`${this.name} 的成員:${member}`); }); } };
team.listMembersNormal();
team.listMembersArrow();
|
call/apply/bind 對箭頭函數無效
由於箭頭函數的 this 在定義時就固定了,強制綁定完全無效:
1 2 3 4 5 6 7 8
| const obj = { num: 100 }; window.num = 2026;
const arrowFn = (a) => this.num + a;
console.log(arrowFn.call(obj, 1));
|
原本預期是輸出 100 + 1,也就是我們傳入的 obj 物件,但 this 用在箭頭函數只會是定義時外層的 window。
何時用箭頭函數、何時用一般函數
| 情境 | 建議 | 原因 |
|---|
| 物件的方法(method) | 一般函數 | 需要 this 指向物件本身 |
forEach、map、filter 的 Callback | 箭頭函數 | 繼承外層 this,避免錯誤 |
setTimeout / setInterval 內部 | 箭頭函數 | 同上,否則需要 .bind(this) |
| 需要 arguments 物件的函數 | 一般函數 | 箭頭函數沒有 arguments |
建構子(搭配 new) | 一般函數 | 箭頭函數不能用 new 呼叫 |
總整理
參考資料
Functions in JavaScript - GeeksforGeeks
JavaScript Function Binding - GeeksforGeeks
JavaScript 函数 | 菜鸟教程
披薩筆記 - JS 底層觀念 - this 物件 下(this 綁定控制)
使用 bind、call、apply 改變 this 指向的對象 - 一顆藍莓
重新認識 JavaScript: Day 20 What’s “THIS” in JavaScript (鐵人精華版) - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天