箭頭函式表示式

Baseline 已廣泛支援

此特性已非常成熟,可在多種裝置和瀏覽器版本上使用。自 ⁨2016 年 9 月⁩以來,它已在各大瀏覽器中可用。

箭頭函式表示式是傳統函式表示式的一種緊湊替代方案,它有一些語義上的差異和刻意的用法限制。

試一試

const materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

console.log(materials.map((material) => material.length));
// Expected output: Array [8, 6, 7, 9]

語法

js
() => expression

param => expression

(param) => expression

(param1, paramN) => expression

() => {
  statements
}

param => {
  statements
}

(param1, paramN) => {
  statements
}

支援剩餘引數預設引數以及引數中的解構,並且始終需要括號。

js
(a, b, ...r) => expression
(a = 400, b = 20, c) => expression
([a, b] = [10, 20]) => expression
({ a, b } = { a: 10, b: 20 }) => expression

透過在表示式前加上async關鍵字,箭頭函式可以是async函式。

js
async param => expression
async (param1, param2, ...paramN) => {
  statements
}

描述

讓我們一步步地將一個傳統的匿名函式分解為最簡單的箭頭函式。每一步都是一個有效的箭頭函式。

注意:傳統函式表示式和箭頭函式除了語法之外還有更多差異。我們將在接下來的幾個小節中更詳細地介紹它們的行為差異。

js
// Traditional anonymous function
(function (a) {
  return a + 100;
});

// 1. Remove the word "function" and place arrow between the argument and opening body brace
(a) => {
  return a + 100;
};

// 2. Remove the body braces and word "return" — the return is implied.
(a) => a + 100;

// 3. Remove the parameter parentheses
a => a + 100;

在上面的例子中,引數周圍的括號和函式體周圍的大括號都可以省略。然而,它們只能在某些情況下省略。

只有當函式只有一個簡單引數時,才能省略括號。如果它有多個引數、沒有引數,或者有預設引數、解構引數或剩餘引數,則引數列表周圍的括號是必需的。

js
// Traditional anonymous function
(function (a, b) {
  return a + b + 100;
});

// Arrow function
(a, b) => a + b + 100;

const a = 4;
const b = 2;

// Traditional anonymous function (no parameters)
(function () {
  return a + b + 100;
});

// Arrow function (no parameters)
() => a + b + 100;

只有當函式直接返回一個表示式時,才能省略大括號。如果函式體有語句,則大括號是必需的——return關鍵字也是必需的。箭頭函式無法猜測你想要返回什麼或何時返回。

js
// Traditional anonymous function
(function (a, b) {
  const chuck = 42;
  return a + b + chuck;
});

// Arrow function
(a, b) => {
  const chuck = 42;
  return a + b + chuck;
};

箭頭函式本身沒有關聯的名稱。如果箭頭函式需要呼叫自身,請使用命名函式表示式。你也可以將箭頭函式賦值給一個變數,這樣你就可以透過該變數引用它。

js
// Traditional Function
function bob(a) {
  return a + 100;
}

// Arrow Function
const bob2 = (a) => a + 100;

函式體

箭頭函式可以有表示式體或通常的塊體

在表示式體中,只指定一個表示式,它成為隱式返回值。在塊體中,你必須使用顯式的return語句。

js
const func = (x) => x * x;
// expression body syntax, implied "return"

const func2 = (x, y) => {
  return x + y;
};
// with block body, explicit "return" needed

使用表示式體語法(params) => { object: literal }返回物件字面量不起作用,因為這並不是預期行為。

js
const func = () => { foo: 1 };
// Calling func() returns undefined!

const func2 = () => { foo: function () {} };
// SyntaxError: function statement requires a name

const func3 = () => { foo() {} };
// SyntaxError: Unexpected token '{'

這是因為 JavaScript 只有在箭頭後面的 token 不是左大括號時,才將箭頭函式視為具有表示式體,因此大括號({})內的程式碼被解析為一系列語句,其中foo是一個標籤,而不是物件字面量中的鍵。

為了解決這個問題,將物件字面量用括號括起來。

js
const func = () => ({ foo: 1 });

不能作為方法使用

箭頭函式表示式只應用於非方法函式,因為它們沒有自己的this。讓我們看看當我們嘗試將它們用作方法時會發生什麼。

js
"use strict";

const obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c() {
    console.log(this.i, this);
  },
};

obj.b(); // logs undefined, Window { /* … */ } (or the global object)
obj.c(); // logs 10, Object { /* … */ }

另一個涉及Object.defineProperty()的例子。

js
"use strict";

const obj = {
  a: 10,
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this); // undefined 'undefined' Window { /* … */ } (or the global object)
    return this.a + 10; // represents global object 'Window', therefore 'this.a' returns 'undefined'
  },
});

因為的函式體有一個this上下文,所以作為類欄位的箭頭函式會關閉該類的this上下文,並且箭頭函式函式體內的this將正確指向例項(或對於靜態欄位,指向類本身)。然而,由於它是一個閉包,而不是函式自身的繫結,this的值不會根據執行上下文而改變。

js
class C {
  a = 1;
  autoBoundMethod = () => {
    console.log(this.a);
  };
}

const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1
// If it were a normal method, it should be undefined in this case

箭頭函式屬性通常被稱為“自動繫結方法”,因為與普通方法等價的是:

js
class C {
  a = 1;
  constructor() {
    this.method = this.method.bind(this);
  }
  method() {
    console.log(this.a);
  }
}

注意:類欄位定義在例項上,而不是原型上,因此每次建立例項都會建立一個新的函式引用並分配一個新的閉包,這可能導致比普通非繫結方法更多的記憶體使用。

由於類似的原因,當在箭頭函式上呼叫call()apply()bind()方法時,它們並不那麼有用,因為箭頭函式會根據定義它的作用域來建立this,並且this的值不會根據函式的呼叫方式而改變。

沒有arguments繫結

箭頭函式沒有自己的arguments物件。因此,在這個例子中,arguments是對封閉作用域的引數的引用。

js
function foo(n) {
  const f = () => arguments[0] + n; // foo's implicit arguments binding. arguments[0] is n
  return f();
}

foo(3); // 3 + 3 = 6

在大多數情況下,使用剩餘引數是使用arguments物件的一個很好的替代方案。

js
function foo(n) {
  const f = (...args) => args[0] + n;
  return f(10);
}

foo(1); // 11

不能作為建構函式使用

箭頭函式不能作為建構函式使用,並且在使用new呼叫時會丟擲錯誤。它們也沒有prototype屬性。

js
const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
console.log("prototype" in Foo); // false

不能作為生成器使用

在箭頭函式的函式體中不能使用yield關鍵字(除非在箭頭函式中進一步巢狀的生成器函式中使用)。因此,箭頭函式不能用作生成器。

箭頭前的換行符

箭頭函式不能在其引數和箭頭之間包含換行符。

js
const func = (a, b, c)
  => 1;
// SyntaxError: Unexpected token '=>'

為了格式化,你可以在箭頭後面放置換行符,或者在函式體周圍使用括號/大括號,如下所示。你也可以在引數之間放置換行符。

js
const func = (a, b, c) =>
  1;

const func2 = (a, b, c) => (
  1
);

const func3 = (a, b, c) => {
  return 1;
};

const func4 = (
  a,
  b,
  c,
) => 1;

箭頭的優先順序

雖然箭頭函式中的箭頭不是運算子,但箭頭函式有特殊的解析規則,與常規函式相比,它與運算子優先順序的互動方式不同。

js
let callback;

callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments

因為=>的優先順序低於大多數運算子,所以需要括號來避免callback || ()被解析為箭頭函式的引數列表。

js
callback = callback || (() => {});

示例

使用箭頭函式

js
// An empty arrow function returns undefined
const empty = () => {};

(() => "foobar")();
// Returns "foobar"
// (this is an Immediately Invoked Function Expression)

const simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10

const max = (a, b) => (a > b ? a : b);

// Easy array filtering, mapping, etc.
const arr = [5, 6, 13, 0, 1, 18, 23];

const sum = arr.reduce((a, b) => a + b);
// 66

const even = arr.filter((v) => v % 2 === 0);
// [6, 0, 18]

const double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]

// More concise promise chains
promise
  .then((a) => {
    // …
  })
  .then((b) => {
    // …
  });

// Arrow functions without parameters
setTimeout(() => {
  console.log("I happen sooner");
  setTimeout(() => {
    // deeper code
    console.log("I happen later");
  }, 1);
}, 1);

使用 call、bind 和 apply

在傳統函式中,call()apply()bind()方法按預期工作,因為我們為每個方法建立了作用域。

js
const obj = {
  num: 100,
};

// Setting "num" on globalThis to show how it is NOT used.
globalThis.num = 42;

// A traditional function to operate on "this"
function add(a, b, c) {
  return this.num + a + b + c;
}

console.log(add.call(obj, 1, 2, 3)); // 106
console.log(add.apply(obj, [1, 2, 3])); // 106
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 106

對於箭頭函式,由於我們的add函式基本上是在globalThis(全域性)作用域中建立的,它將假定thisglobalThis

js
const obj = {
  num: 100,
};

// Setting "num" on globalThis to show how it gets picked up.
globalThis.num = 42;

// Arrow function
const add = (a, b, c) => this.num + a + b + c;

console.log(add.call(obj, 1, 2, 3)); // 48
console.log(add.apply(obj, [1, 2, 3])); // 48
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 48

也許使用箭頭函式的最大好處是與setTimeout()EventTarget.prototype.addEventListener()等方法一起使用,這些方法通常需要某種閉包、call()apply()bind()來確保函式在正確的範圍中執行。

使用傳統函式表示式,這樣的程式碼無法按預期工作:

js
const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(function () {
      // the function executes on the window scope
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs "NaN", because the property "count" is not in the window scope.

使用箭頭函式,this作用域更容易保留:

js
const obj = {
  count: 10,
  doSomethingLater() {
    // The method syntax binds "this" to the "obj" context.
    setTimeout(() => {
      // Since the arrow function doesn't have its own binding and
      // setTimeout (as a function call) doesn't create a binding
      // itself, the "obj" context of the outer method is used.
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs 11

規範

規範
ECMAScript® 2026 語言規範
# sec-arrow-function-definitions

瀏覽器相容性

另見