函数

一般来说,函数是一个 "subprogram",可以由函数外部(或内部,在递归的情况下)代码调用。与程序本身一样,函数由一系列称为函数体的语句组成。值可以作为参数传递给函数,函数将返回一个值。

¥Generally speaking, a function is a "subprogram" that can be called by code external (or internal, in the case of recursion) to the function. Like the program itself, a function is composed of a sequence of statements called the function body. Values can be passed to a function as parameters, and the function will return a value.

在 JavaScript 中,函数是 一流的对象,因为它们可以传递给其他函数、从函数返回以及分配给变量和属性。它们也可以像任何其他对象一样具有属性和方法。它们与其他对象的区别在于可以调用函数。

¥In JavaScript, functions are first-class objects, because they can be passed to other functions, returned from functions, and assigned to variables and properties. They can also have properties and methods just like any other object. What distinguishes them from other objects is that functions can be called.

有关更多示例和说明,请参阅 关于函数的 JavaScript 指南

¥For more examples and explanations, see the JavaScript guide about functions.

描述

¥Description

函数值通常是 Function 的实例。有关 Function 对象的属性和方法的信息,请参阅 Function。可调用值导致 typeof 返回 "function" 而不是 "object"

¥Function values are typically instances of Function. See Function for information on properties and methods of Function objects. Callable values cause typeof to return "function" instead of "object".

注意:并非所有可调用值都是 instanceof Function。例如,Function.prototype 对象是可调用的,但不是 Function 的实例。你还可以手动设置函数的 原型链,使其不再继承自 Function.prototype。然而,这种情况极为罕见。

¥Note: Not all callable values are instanceof Function. For example, the Function.prototype object is callable but not an instance of Function. You can also manually set the prototype chain of your function so it no longer inherits from Function.prototype. However, such cases are extremely rare.

返回值

¥Return value

默认情况下,如果函数的执行未在 return 语句处结束,或者 return 关键字后面没有表达式,则返回值为 undefinedreturn 语句允许你从函数返回任意值。一次函数调用只能返回一个值,但你可以通过返回一个对象或数组并 destructuring 结果来模拟返回多个值的效果。

¥By default, if a function's execution doesn't end at a return statement, or if the return keyword doesn't have an expression after it, then the return value is undefined. The return statement allows you to return an arbitrary value from the function. One function call can only return one value, but you can simulate the effect of returning multiple values by returning an object or array and destructuring the result.

注意:使用 new 调用的构造函数具有一组不同的逻辑来确定其返回值。

¥Note: Constructors called with new have a different set of logic to determine their return values.

传递参数

¥Passing arguments

参数和参数 的含义略有不同,但在 MDN 网络文档中,我们经常互换使用它们。快速参考:

¥Parameters and arguments have slightly different meanings, but in MDN web docs, we often use them interchangeably. For a quick reference:

js
function formatNumber(num) {
  return num.toFixed(2);
}

formatNumber(2);

在此示例中,num 变量称为函数的参数:它在函数定义的括号括起来的列表中声明。该函数期望 num 参数是一个数字 - 尽管如果不编写运行时验证代码,这在 JavaScript 中是不可强制执行的。在 formatNumber(2) 调用中,数字 2 是函数的参数:它是在函数调用中实际传递给函数的值。可以通过相应的参数名称或 arguments 对象在函数体内访问参数值。

¥In this example, the num variable is called the function's parameter: it's declared in the parenthesis-enclosed list of the function's definition. The function expects the num parameter to be a number — although this is not enforceable in JavaScript without writing runtime validation code. In the formatNumber(2) call, the number 2 is the function's argument: it's the value that is actually passed to the function in the function call. The argument value can be accessed inside the function body through the corresponding parameter name or the arguments object.

参数始终为 按值传递 并且从不通过引用传递。这意味着如果函数重新分配参数,则该值不会在函数外部更改。更准确地说,对象参数是 分享通过,这意味着如果对象的属性发生突变,该更改将影响函数的外部。例如:

¥Arguments are always passed by value and never passed by reference. This means that if a function reassigns a parameter, the value won't change outside the function. More precisely, object arguments are passed by sharing, which means if the object's properties are mutated, the change will impact the outside of the function. For example:

js
function updateBrand(obj) {
  // Mutating the object is visible outside the function
  obj.brand = "Toyota";
  // Try to reassign the parameter, but this won't affect
  // the variable's value outside the function
  obj = null;
}

const car = {
  brand: "Honda",
  model: "Accord",
  year: 1998,
};

console.log(car.brand); // Honda

// Pass object reference to the function
updateBrand(car);

// updateBrand mutates car
console.log(car.brand); // Toyota

this 关键字指的是访问函数的对象 - 它不是指当前正在执行的函数,因此你必须按名称引用函数值,即使在函数体内也是如此。

¥The this keyword refers to the object that the function is accessed on — it does not refer to the currently executing function, so you must refer to the function value by name, even within the function body.

定义函数

¥Defining functions

广义上来说,JavaScript 有四种功能:

¥Broadly speaking, JavaScript has four kinds of functions:

  • 常规功能:可以返回任何东西;调用后始终运行至完成
  • 生成器功能:返回 Generator 对象;可以使用 yield 操作符暂停和恢复
  • 异步函数:返回 Promise;可以使用 await 操作符暂停和恢复
  • 异步生成器函数:返回 AsyncGenerator 对象;awaityield 运算符都可以使用

对于每种函数,都有三种定义方法:

¥For every kind of function, there are three ways to define it:

声明

function, function*, async function, async function*

表达

function, function*, async function, async function*

构造函数

Function(), GeneratorFunction(), AsyncFunction(), AsyncGeneratorFunction()

此外,还有定义 箭头函数methods 的特殊语法,为它们的使用提供了更精确的语义。 在概念上不是函数(因为它们在没有 new 的情况下调用时会抛出错误),但它们也继承自 Function.prototype 并具有 typeof MyClass === "function"

¥In addition, there are special syntaxes for defining arrow functions and methods, which provide more precise semantics for their usage. Classes are conceptually not functions (because they throw an error when called without new), but they also inherit from Function.prototype and have typeof MyClass === "function".

js
// Constructor
const multiply = new Function("x", "y", "return x * y");

// Declaration
function multiply(x, y) {
  return x * y;
} // No need for semicolon here

// Expression; the function is anonymous but assigned to a variable
const multiply = function (x, y) {
  return x * y;
};
// Expression; the function has its own name
const multiply = function funcName(x, y) {
  return x * y;
};

// Arrow function
const multiply = (x, y) => x * y;

// Method
const obj = {
  multiply(x, y) {
    return x * y;
  },
};

所有语法都做大致相同的事情,但存在一些细微的行为差异。

¥All syntaxes do approximately the same thing, but there are some subtle behavior differences.

  • Function() 构造函数、function 表达式和 function 声明语法创建成熟的函数对象,可以使用 new 构造函数对象。但是,无法构造箭头函数和方法。无论语法如何,异步函数、生成器函数和异步生成器函数都不可构造。
  • function 声明创建了 hoisted 的函数。其他语法不会提升函数,函数值仅在定义后可见。
  • 箭头函数和 Function() 构造函数总是创建匿名函数,这意味着它们不能轻松地递归调用自身。递归调用箭头函数的一种方法是将其分配给变量。
  • 箭头函数语法无法访问 argumentsthis
  • Function() 构造函数无法访问任何局部变量 - 它只能访问全局范围。
  • Function() 构造函数会导致运行时编译,并且通常比其他语法慢。

对于 function 表达式,函数名称和函数分配到的变量之间存在区别。函数名称不能更改,而函数所分配的变量可以重新分配。函数名称可以与函数分配给的变量不同 - 它们彼此没有关系。函数名称只能在函数体内使用。尝试在函数体之外使用它会导致错误(或获取另一个值,如果在其他地方声明了相同的名称)。例如:

¥For function expressions, there is a distinction between the function name and the variable the function is assigned to. The function name cannot be changed, while the variable the function is assigned to can be reassigned. The function name can be different from the variable the function is assigned to — they have no relation to each other. The function name can be used only within the function's body. Attempting to use it outside the function's body results in an error (or gets another value, if the same name is declared elsewhere). For example:

js
const y = function x() {};
console.log(x); // ReferenceError: x is not defined

另一方面,函数分配给的变量仅受其范围的限制,该范围保证包括声明函数的范围。

¥On the other hand, the variable the function is assigned to is limited only by its scope, which is guaranteed to include the scope in which the function is declared.

函数声明还会创建一个与函数名称同名的变量。因此,与函数表达式定义的函数不同,函数声明定义的函数可以在其定义范围内及其自身的主体中通过其名称进行访问。

¥A function declaration also creates a variable with the same name as the function name. Thus, unlike those defined by function expressions, functions defined by function declarations can be accessed by their name in the scope they were defined in, as well as in their own body.

new Function 定义的函数将动态地组装其源代码,当你序列化它时可以观察到这一点。例如,console.log(new Function().toString()) 给出:

¥A function defined by new Function will dynamically have its source assembled, which is observable when you serialize it. For example, console.log(new Function().toString()) gives:

js
function anonymous(
) {

}

这是用于编译该函数的实际源代码。然而,尽管 Function() 构造函数将创建名为 anonymous 的函数,但该名称不会添加到函数体的作用域中。主体只能访问全局变量。例如,以下内容将导致错误:

¥This is the actual source used to compile the function. However, although the Function() constructor will create the function with name anonymous, this name is not added to the scope of the body. The body only ever has access to global variables. For example, the following would result in an error:

js
new Function("alert(anonymous);")();

由函数表达式或函数声明定义的函数继承当前作用域。也就是说,该函数形成了一个闭包。另一方面,由 Function 构造函数定义的函数不会继承除全局作用域(所有函数都继承)之外的任何作用域。

¥A function defined by a function expression or by a function declaration inherits the current scope. That is, the function forms a closure. On the other hand, a function defined by a Function constructor does not inherit any scope other than the global scope (which all functions inherit).

js
// p is a global variable
globalThis.p = 5;
function myFunc() {
  // p is a local variable
  const p = 9;

  function decl() {
    console.log(p);
  }
  const expr = function () {
    console.log(p);
  };
  const cons = new Function("\tconsole.log(p);");

  decl();
  expr();
  cons();
}
myFunc();

// Logs:
// 9 (for 'decl' by function declaration (current scope))
// 9 (for 'expr' by function expression (current scope))
// 5 (for 'cons' by Function constructor (global scope))

由函数表达式和函数声明定义的函数仅解析一次,而由 Function 构造函数定义的函数会在每次调用构造函数时解析传递给它的字符串。虽然函数表达式每次都会创建一个闭包,但函数体不会被重新解析,因此函数表达式仍然比 new Function(...) 更快。因此,通常应尽可能避免使用 Function 构造函数。

¥Functions defined by function expressions and function declarations are parsed only once, while a function defined by the Function constructor parses the string passed to it each and every time the constructor is called. Although a function expression creates a closure every time, the function body is not reparsed, so function expressions are still faster than new Function(...). Therefore the Function constructor should generally be avoided whenever possible.

当函数声明出现在表达式上下文中时,它可能会无意中变成函数表达式。

¥A function declaration may be unintentionally turned into a function expression when it appears in an expression context.

js
// A function declaration
function foo() {
  console.log("FOO!");
}

doSomething(
  // A function expression passed as an argument
  function foo() {
    console.log("FOO!");
  },
);

另一方面,函数表达式也可以变成函数声明。表达式语句 不能以 functionasync function 关键字开头,这是实现 IIFEs(立即调用函数表达式)时的常见错误。

¥On the other hand, a function expression may also be turned into a function declaration. An expression statement cannot begin with the function or async function keywords, which is a common mistake when implementing IIFEs (Immediately Invoked Function Expressions).

js
function () { // SyntaxError: Function statements require a function name
  console.log("FOO!");
}();

function foo() {
  console.log("FOO!");
}(); // SyntaxError: Unexpected token ')'

相反,应以其他内容开始表达式语句,以便 function 关键字明确地开始函数表达式。常见选项包括 grouping 和使用 void

¥Instead, start the expression statement with something else, so that the function keyword unambiguously starts a function expression. Common options include grouping and using void.

js
(function () {
  console.log("FOO!");
})();

void function () {
  console.log("FOO!");
}();

函数参数

¥Function parameters

每个函数参数都是一个可以在本地范围内访问的简单标识符。

¥Each function parameter is a simple identifier that you can access in the local scope.

js
function myFunc(a, b, c) {
  // You can access the values of a, b, and c here
}

共有三种特殊的参数语法:

¥There are three special parameter syntaxes:

  • 如果没有传递任何值或 undefined默认参数 允许使用默认值初始化形式参数。
  • 剩余参数 允许将不定数量的参数表示为数组。
  • 解构 允许将数组中的元素或对象中的属性解包到不同的变量中。
js
function myFunc({ a, b }, c = 1, ...rest) {
  // You can access the values of a, b, c, and rest here
}

如果使用上述非简单参数语法之一,则会产生一些后果:

¥There are some consequences if one of the above non-simple parameter syntaxes is used:

参数对象

¥The arguments object

你可以使用 arguments 对象在函数内引用函数的参数。

¥You can refer to a function's arguments within the function by using the arguments object.

arguments

一个类似数组的对象,包含传递给当前执行函数的参数。

arguments.callee

当前正在执行的函数。

arguments.length

传递给函数的参数数量。

Getter 和 Setter 函数

¥Getter and setter functions

你可以在任何支持添加新属性的标准内置对象或用户定义对象上定义访问器属性。在 对象字面量classes 中,你可以使用特殊语法来定义访问器属性的 getter 和 setter。

¥You can define accessor properties on any standard built-in object or user-defined object that supports the addition of new properties. Within object literals and classes, you can use special syntaxes to define the getter and setter of an accessor property.

get

将对象属性绑定到查找该属性时将调用的函数。

set

将对象属性绑定到要在尝试设置该属性时调用的函数。

请注意,这些语法创建对象属性,而不是方法。getter 和 setter 函数本身只能使用反射 API(例如 Object.getOwnPropertyDescriptor())来访问。

¥Note that these syntaxes create an object property, not a method. The getter and setter functions themselves can only be accessed using reflective APIs such as Object.getOwnPropertyDescriptor().

块级函数

¥Block-level functions

严格模式 中,块内的函数的作用域为该块。在 ES2015 之前,严格模式下禁止使用块级函数。

¥In strict mode, functions inside blocks are scoped to that block. Prior to ES2015, block-level functions were forbidden in strict mode.

js
"use strict";

function f() {
  return 1;
}

{
  function f() {
    return 2;
  }
}

f() === 1; // true

// f() === 2 in non-strict mode

非严格代码中的块级函数

¥Block-level functions in non-strict code

一句话:不。

¥In a word: Don't.

在非严格代码中,块内的函数声明行为很奇怪。例如:

¥In non-strict code, function declarations inside blocks behave strangely. For example:

js
if (shouldDefineZero) {
  function zero() {
    // DANGER: compatibility risk
    console.log("This is zero.");
  }
}

严格模式下的语义是明确指定的 - zero 仅存在于 if 块的范围内。如果 shouldDefineZero 为 false,则永远不应定义 zero,因为该块永远不会执行。然而,从历史上看,这是未指定的,因此不同的浏览器在非严格模式下以不同的方式实现它。有关详细信息,请参阅 function 声明 参考。

¥The semantics of this in strict mode are well-specified — zero only ever exists within that scope of the if block. If shouldDefineZero is false, then zero should never be defined, since the block never executes. However, historically, this was left unspecified, so different browsers implemented it differently in non-strict mode. For more information, see the function declaration reference.

有条件地定义函数的一种更安全的方法是将函数表达式分配给变量:

¥A safer way to define functions conditionally is to assign a function expression to a variable:

js
// Using a var makes it available as a global variable,
// with closer behavior to a top-level function declaration
var zero;
if (shouldDefineZero) {
  zero = function () {
    console.log("This is zero.");
  };
}

示例

¥Examples

返回格式化的数字

¥Returning a formatted number

以下函数返回一个字符串,其中包含用前导零填充的数字的格式化表示形式。

¥The following function returns a string containing the formatted representation of a number padded with leading zeros.

js
// This function returns a string padded with leading zeros
function padZeros(num, totalLen) {
  let numStr = num.toString(); // Initialize return value as string
  const numZeros = totalLen - numStr.length; // Calculate no. of zeros
  for (let i = 1; i <= numZeros; i++) {
    numStr = `0${numStr}`;
  }
  return numStr;
}

以下语句调用 padZeros 函数。

¥The following statements call the padZeros function.

js
let result;
result = padZeros(42, 4); // returns "0042"
result = padZeros(42, 2); // returns "42"
result = padZeros(5, 4); // returns "0005"

判断函数是否存在

¥Determining whether a function exists

你可以使用 typeof 运算符来确定函数是否存在。在以下示例中,执行测试以确定 window 对象是否具有名为 noFunc 的函数属性。如果是,则使用;否则,将采取一些其他行动。

¥You can determine whether a function exists by using the typeof operator. In the following example, a test is performed to determine if the window object has a property called noFunc that is a function. If so, it is used; otherwise, some other action is taken.

js
if (typeof window.noFunc === "function") {
  // use noFunc()
} else {
  // do something else
}

请注意,在 if 测试中,使用了对 noFunc 的引用 - 函数名称后面没有括号 (),因此不会调用实际函数。

¥Note that in the if test, a reference to noFunc is used — there are no parentheses () after the function name so the actual function is not called.

规范

Specification
ECMAScript Language Specification
# sec-function-definitions

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看