跳到主要內容
← 回到學習

學習 · 面試考題

匿名函數

目錄 · 5 個段落
  1. 01什麼是匿名函數
  2. 02跟具名函數的差異
  3. 03IIFE(立即執行函數表達式)
  4. 04當 callback 用
  5. 05跟箭頭函數的關係

什麼是匿名函數

匿名函數(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;
});
js

跟具名函數的差異

面向具名函數匿名函數
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; };
js
「賦值給變數」的匿名函數其實會被引擎推斷出 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");
})();
js

外層那對 () 把 function 變成「表達式」(expression),後面的 () 才是真正去呼叫它。

為什麼以前很常用 IIFE
ES6 之前只有 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
js

好處是邏輯就寫在使用的地方,不用為了傳一個只用一次的函數特地命名。代價是若同一段 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);
  },
};
js

也因為箭頭函數的 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>
                    </>
                );
            })}
        </>
    )
}
typescript

目錄