箭头函数表达式

箭头函数表达式是传统 函数表达式 的简洁替代方案,在语义上存在一些差异,并且在使用上存在一些有意限制

试一试

语法

js
() => expression

param => expression

(param) => expression

(param1, paramN) => expression

() => {
  statements
}

param => {
  statements
}

(param1, paramN) => {
  statements
}

rest 参数默认参数参数解构 受支持,并且始终需要括号

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;

在上面的示例中,参数周围的括号和函数体周围的花括号都可以省略。但是,它们只能在某些情况下省略。

只有当函数只有一个简单参数时,才能省略括号。如果它有多个参数、没有参数或默认参数、解构参数或 rest 参数,则需要参数列表周围的括号。

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 只有在箭头后面的标记不是左括号时才会将箭头函数视为具有表达式体,因此花括号 ({}) 内的代码被解析为一系列语句,其中 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 的变量,因此上面的代码将是语法错误。这使得 arguments 的作用域效果更容易理解。

在大多数情况下,使用 rest 参数 是使用 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) => {
    // …
  });

// Parameterless arrow functions that are visually easier to parse
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 simple traditional function to operate on "this"
const add = function (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 语言规范
# sec-arrow-function-definitions

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅