匿名函數
什麼是匿名函數
匿名函數(anonymous function)就是沒有名字的函數。一般直接定義在需要它的地方,通常賦值給變數、當參數傳進去、或定義完馬上呼叫。
// 具名函數:有自己的名字 add
function add(a, b) {
return a + b;
}
// 匿名函數:沒有名字,賦值給變數
const add = function (a, b) {
return a + b;
};
// 匿名函數當參數傳進去
[1, 2, 3].map(function (n) {
return n * 2;
});
跟具名函數的差異
| 面向 | 具名函數 | 匿名函數 |
|---|---|---|
| Hoisting(提升) | function foo(){} 整個被提升,定義前就能呼叫 | 賦值給變數的匿名函數只提升變數宣告,定義前呼叫會錯 |
| 遞迴 | 可以用自己的名字呼叫自己 | 沒名字,難以自我呼叫(除非靠外層變數) |
| 除錯 stack trace | 錯誤堆疊顯示函數名稱,好追 | 常顯示 <anonymous>,較難定位 |
| 用途 | 重複使用、需要被多處呼叫 | 一次性、就地使用(callback、IIFE) |
console.log(named(2)); // OK,函數宣告被 hoist → 4
function named(n) { return n * 2; }
console.log(anon(2)); // ❌ TypeError: anon is not a function
const anon = function (n) { return n * 2; };
「賦值給變數」的匿名函數其實會被引擎推斷出 name現代 JS 引擎會把 const add = function(){} 的 add.name 推斷成 "add",stack trace 也會好讀一些。但真正當參數內聯傳進去的匿名函數(map(function(n){...}))就還是匿名的。
IIFE(立即執行函數表達式)
IIFE = Immediately Invoked Function Expression,定義完馬上呼叫的匿名函數。主要目的是建立獨立 scope,避免變數污染外層。
(function () {
const secret = 42; // 被關在這個 scope 裡,外面拿不到
console.log(secret);
})();
// 箭頭函數版
(() => {
console.log("run once");
})();
外層那對 () 把 function 變成「表達式」(expression),後面的 () 才是真正去呼叫它。
為什麼以前很常用 IIFEES6 之前只有 var(函數作用域,沒有 block scope),要隔離變數只能靠函數包一層。有了 let / const(block scope)和 ES Modules(每個模組自己一個 scope)後,IIFE 的需求大幅減少,但在打包後的 bundle、或想「跑一次就丟」的初始化邏輯裡還是看得到。
當 callback 用
匿名函數最常見的場景就是當 callback —— 傳給另一個函數,讓它在適當時機呼叫。事件處理、陣列方法、setTimeout、Promise 等到處都是。
button.addEventListener("click", function () {
console.log("clicked");
});
setTimeout(function () {
console.log("1 秒後");
}, 1000);
[1, 2, 3].filter((n) => n > 1); // 箭頭版匿名 callback
好處是邏輯就寫在使用的地方,不用為了傳一個只用一次的函數特地命名。代價是若同一段 callback 在多處重複,或需要被移除(例如 removeEventListener 需要同一個 reference),就該抽成具名函數。
跟箭頭函數的關係
箭頭函數(() => {})本身就是一種匿名函數的語法,但有兩個關鍵差異:
| 面向 | 一般匿名函數 function(){} | 箭頭函數 () => {} |
|---|---|---|
this | 自己的 this(由呼叫方式決定) | 沒有自己的 this,沿用定義時外層的 this(lexical this) |
arguments | 有 | 沒有(要用 rest ...args) |
當建構子 new | 可以 | 不行 |
const obj = {
name: "Bobo",
// 一般匿名函數:this 在 setTimeout 內會跑掉(指向 undefined / window)
greetBad() {
setTimeout(function () {
console.log(this.name); // undefined
}, 0);
},
// 箭頭函數:沿用 greetGood 的 this → obj
greetGood() {
setTimeout(() => {
console.log(this.name); // "Bobo"
}, 0);
},
};
也因為箭頭函數的 lexical this,在 React 寫 callback(像下面的 onClick={() => print(number)})很自然 —— 不用擔心 this 綁定問題。
因為sayHello是很簡單的打印 所以才可以直接
function HomePage() {
const array = [1, 2, 3];
function sayHello() {
console.log("Hello")
}
function sayHello2(number: number) {
console.log("Hello" + number)
}
function print(number: number) {
console.log("print", number);
}
function doubleV1(number: number) {
const newNumber = number * 2;
return () => console.log("double", newNumber);
}
function doubleV2(number: number) {
return () => {
const newNumber = number * 2;
console.log("double", newNumber);
};
}
console.log("render");
return (
<>
<button onClick={sayHello}>Say Hello</button>
<button onClick={() => sayHello()}>Say Hello</button>
{array.map((number) => {
return (
<button onClick={() => print(number)} className="border m-1 p-1">
Print {number}{" "}
</button>
);
})}
<br />
{array.map((number) => {
return (
<>
<button onClick={doubleV1(number)} className="border m-1 p-1">
Double {number}{" "}
</button>
<button onClick={doubleV2(number)} className="border m-1 p-1">
Double {number}{" "}
</button>
</>
);
})}
</>
)
}