函数

函数是 JavaScript 中的基本构建块之一。JavaScript 中的函数类似于过程 - 一组执行任务或计算值的语句,但对于符合函数资格的过程,它应该接受一些输入并返回一个输出,其中输出之间存在一些明显的关系。 输入和输出。要使用函数,你必须在要调用它的范围内的某个位置定义它。

¥Functions are one of the fundamental building blocks in JavaScript. A function in JavaScript is similar to a procedure—a set of statements that performs a task or calculates a value, but for a procedure to qualify as a function, it should take some input and return an output where there is some obvious relationship between the input and the output. To use a function, you must define it somewhere in the scope from which you wish to call it.

另请参阅 有关 JavaScript 函数的详尽参考章节 以了解详细信息。

¥See also the exhaustive reference chapter about JavaScript functions to get to know the details.

定义函数

¥Defining functions

函数声明

¥Function declarations

函数定义(也称为函数声明或函数语句)由 function 关键字和后跟:

¥A function definition (also called a function declaration, or function statement) consists of the function keyword, followed by:

  • 函数的名称。
  • 函数的参数列表,用括号括起来并用逗号分隔。
  • 定义函数的 JavaScript 语句,用大括号 { /* … */ } 括起来。

例如,以下代码定义了一个名为 square 的简单函数:

¥For example, the following code defines a simple function named square:

js
function square(number) {
  return number * number;
}

函数 square 有一个参数,称为 number。该函数由一条语句组成,该语句表示返回函数的参数(即 number)乘以自身。return 语句指定了函数的返回值,即 number * number

¥The function square takes one parameter, called number. The function consists of one statement that says to return the parameter of the function (that is, number) multiplied by itself. The return statement specifies the value returned by the function, which is number * number.

参数本质上是按值传递给函数的 - 因此,如果函数体内的代码为传递给函数的参数分配了一个全新的值,则该更改不会在全局或调用该函数的代码中反映出来。

¥Parameters are essentially passed to functions by value — so if the code within the body of a function assigns a completely new value to a parameter that was passed to the function, the change is not reflected globally or in the code which called that function.

当你将对象作为参数传递时,如果函数更改了对象的属性,则该更改在函数外部可见,如以下示例所示:

¥When you pass an object as a parameter, if the function changes the object's properties, that change is visible outside the function, as shown in the following example:

js
function myFunc(theObject) {
  theObject.make = "Toyota";
}

const mycar = {
  make: "Honda",
  model: "Accord",
  year: 1998,
};

console.log(mycar.make); // "Honda"
myFunc(mycar);
console.log(mycar.make); // "Toyota"

当你将数组作为参数传递时,如果函数更改了数组的任何值,则该更改在函数外部可见,如以下示例所示:

¥When you pass an array as a parameter, if the function changes any of the array's values, that change is visible outside the function, as shown in the following example:

js
function myFunc(theArr) {
  theArr[0] = 30;
}

const arr = [45];

console.log(arr[0]); // 45
myFunc(arr);
console.log(arr[0]); // 30

函数表达式

¥Function expressions

虽然上面的函数声明在语法上是一个语句,但函数也可以通过 函数表达式

¥While the function declaration above is syntactically a statement, functions can also be created by a function expression.

这样的函数可以是匿名的;它不必有名字。例如,函数 square 可以定义为:

¥Such a function can be anonymous; it does not have to have a name. For example, the function square could have been defined as:

js
const square = function (number) {
  return number * number;
};

console.log(square(4)); // 16

然而,名称可以与函数表达式一起提供。提供名称允许函数引用自身,并且还可以更轻松地在调试器的堆栈跟踪中识别该函数:

¥However, a name can be provided with a function expression. Providing a name allows the function to refer to itself, and also makes it easier to identify the function in a debugger's stack traces:

js
const factorial = function fac(n) {
  return n < 2 ? 1 : n * fac(n - 1);
};

console.log(factorial(3)); // 6

将函数作为参数传递给另一个函数时,函数表达式很方便。以下示例显示了 map 函数,该函数应接收函数作为第一个参数,接收数组作为第二个参数:

¥Function expressions are convenient when passing a function as an argument to another function. The following example shows a map function that should receive a function as first argument and an array as second argument:

js
function map(f, a) {
  const result = new Array(a.length);
  for (let i = 0; i < a.length; i++) {
    result[i] = f(a[i]);
  }
  return result;
}

在以下代码中,该函数接收由函数表达式定义的函数,并对作为第二个参数接收的数组的每个元素执行该函数:

¥In the following code, the function receives a function defined by a function expression and executes it for every element of the array received as a second argument:

js
function map(f, a) {
  const result = new Array(a.length);
  for (let i = 0; i < a.length; i++) {
    result[i] = f(a[i]);
  }
  return result;
}

const cube = function (x) {
  return x * x * x;
};

const numbers = [0, 1, 2, 5, 10];
console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000]

在 JavaScript 中,可以根据条件定义函数。例如,以下函数定义仅当 num 等于 0 时才定义 myFunc

¥In JavaScript, a function can be defined based on a condition. For example, the following function definition defines myFunc only if num equals 0:

js
let myFunc;
if (num === 0) {
  myFunc = function (theObject) {
    theObject.make = "Toyota";
  };
}

除了按照此处所述定义函数之外,你还可以使用 Function 构造函数在运行时从字符串创建函数,就像 eval() 一样。

¥In addition to defining functions as described here, you can also use the Function constructor to create functions from a string at runtime, much like eval().

方法是一个函数,它是对象的属性。阅读 处理对象 中有关对象和方法的更多信息。

¥A method is a function that is a property of an object. Read more about objects and methods in Working with objects.

调用函数

¥Calling functions

定义函数并不执行它。定义它时会命名该函数并指定调用该函数时要执行的操作。

¥Defining a function does not execute it. Defining it names the function and specifies what to do when the function is called.

调用该函数实际上使用指示的参数执行指定的操作。例如,如果定义函数 square,则可以按如下方式调用它:

¥Calling the function actually performs the specified actions with the indicated parameters. For example, if you define the function square, you could call it as follows:

js
square(5);

前面的语句使用参数 5 调用该函数。该函数执行其语句并返回值 25

¥The preceding statement calls the function with an argument of 5. The function executes its statements and returns the value 25.

函数在调用时必须在作用域内,但函数声明可以是 hoisted(出现在代码中的调用下方)。函数声明的范围是声明它的函数(或者整个程序,如果它是在顶层声明的)。

¥Functions must be in scope when they are called, but the function declaration can be hoisted (appear below the call in the code). The scope of a function declaration is the function in which it is declared (or the entire program, if it is declared at the top level).

函数的参数不限于字符串和数字。你可以将整个对象传递给函数。showProps() 函数(在 处理对象 中定义)是将对象作为参数的函数示例。

¥The arguments of a function are not limited to strings and numbers. You can pass whole objects to a function. The showProps() function (defined in Working with objects) is an example of a function that takes an object as an argument.

函数可以调用自身。例如,下面是一个递归计算阶乘的函数:

¥A function can call itself. For example, here is a function that computes factorials recursively:

js
function factorial(n) {
  if (n === 0 || n === 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

然后,你可以计算 15 的阶乘,如下所示:

¥You could then compute the factorials of 1 through 5 as follows:

js
console.log(factorial(1)); // 1
console.log(factorial(2)); // 2
console.log(factorial(3)); // 6
console.log(factorial(4)); // 24
console.log(factorial(5)); // 120

还有其他方法可以调用函数。通常情况下,需要动态调用函数,或者函数的参数数量不同,或者需要将函数调用的上下文设置为运行时确定的特定对象。

¥There are other ways to call functions. There are often cases where a function needs to be called dynamically, or the number of arguments to a function vary, or in which the context of the function call needs to be set to a specific object determined at runtime.

事实证明,函数本身就是对象 - 反过来,这些对象也有方法。(请参阅 Function 对象。)call()apply() 方法可用于实现此目标。

¥It turns out that functions are themselves objects — and in turn, these objects have methods. (See the Function object.) The call() and apply() methods can be used to achieve this goal.

函数提升

¥Function hoisting

考虑下面的例子:

¥Consider the example below:

js
console.log(square(5)); // 25

function square(n) {
  return n * n;
}

尽管 square() 函数在声明之前被调用,但该代码运行时没有任何错误。这是因为 JavaScript 解释器将整个函数声明提升到当前作用域的顶部,因此上面的代码相当于:

¥This code runs without any error, despite the square() function being called before it's declared. This is because the JavaScript interpreter hoists the entire function declaration to the top of the current scope, so the code above is equivalent to:

js
// All function declarations are effectively at the top of the scope
function square(n) {
  return n * n;
}

console.log(square(5)); // 25

函数提升仅适用于函数声明,不适用于函数表达式。以下代码将不起作用:

¥Function hoisting only works with function declarations — not with function expressions. The following code will not work:

js
console.log(square(5)); // ReferenceError: Cannot access 'square' before initialization
const square = function (n) {
  return n * n;
};

函数作用域

¥Function scope

函数内部定义的变量无法从函数外部的任何地方访问,因为变量仅在函数范围内定义。但是,函数可以访问其定义范围内定义的所有变量和函数。

¥Variables defined inside a function cannot be accessed from anywhere outside the function, because the variable is defined only in the scope of the function. However, a function can access all variables and functions defined inside the scope in which it is defined.

换句话说,在全局作用域中定义的函数可以访问在全局作用域中定义的所有变量。在另一个函数内定义的函数还可以访问其父函数中定义的所有变量,以及父函数有权访问的任何其他变量。

¥In other words, a function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables defined in its parent function, and any other variables to which the parent function has access.

js
// The following variables are defined in the global scope
const num1 = 20;
const num2 = 3;
const name = "Chamakh";

// This function is defined in the global scope
function multiply() {
  return num1 * num2;
}

console.log(multiply()); // 60

// A nested function example
function getScore() {
  const num1 = 2;
  const num2 = 3;

  function add() {
    return `${name} scored ${num1 + num2}`;
  }

  return add();
}

console.log(getScore()); // "Chamakh scored 5"

范围和函数堆栈

¥Scope and the function stack

递归

¥Recursion

函数可以引用并调用自身。函数可以通过三种方式引用自身:

¥A function can refer to and call itself. There are three ways for a function to refer to itself:

  1. 函数名称
  2. arguments.callee
  3. 引用函数的范围内变量

例如,考虑以下函数定义:

¥For example, consider the following function definition:

js
const foo = function bar() {
  // statements go here
};

在函数体内,以下内容都是等效的:

¥Within the function body, the following are all equivalent:

  1. bar()
  2. arguments.callee()
  3. foo()

调用自身的函数称为递归函数。在某些方面,递归类似于循环。两者都多次执行相同的代码,并且都需要一个条件(以避免无限循环,或者更确切地说,在这种情况下避免无限递归)。

¥A function that calls itself is called a recursive function. In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case).

例如,考虑以下循环:

¥For example, consider the following loop:

js
let x = 0;
// "x < 10" is the loop condition
while (x < 10) {
  // do stuff
  x++;
}

它可以转换为递归函数声明,然后调用该函数:

¥It can be converted into a recursive function declaration, followed by a call to that function:

js
function loop(x) {
  // "x >= 10" is the exit condition (equivalent to "!(x < 10)")
  if (x >= 10) {
    return;
  }
  // do stuff
  loop(x + 1); // the recursive call
}
loop(0);

然而,有些算法不能是简单的迭代循环。例如,通过递归获取树结构的所有节点(例如 DOM)更容易:

¥However, some algorithms cannot be simple iterative loops. For example, getting all the nodes of a tree structure (such as the DOM) is easier via recursion:

js
function walkTree(node) {
  if (node === null) {
    return;
  }
  // do something with node
  for (let i = 0; i < node.childNodes.length; i++) {
    walkTree(node.childNodes[i]);
  }
}

与函数 loop 相比,每次递归调用本身都会在这里进行多次递归调用。

¥Compared to the function loop, each recursive call itself makes many recursive calls here.

可以将任何递归算法转换为非递归算法,但逻辑通常要复杂得多,并且这样做需要使用堆栈。

¥It is possible to convert any recursive algorithm to a non-recursive one, but the logic is often much more complex, and doing so requires the use of a stack.

事实上,递归本身就使用了堆栈:函数堆栈。类似堆栈的行为可以在以下示例中看到:

¥In fact, recursion itself uses a stack: the function stack. The stack-like behavior can be seen in the following example:

js
function foo(i) {
  if (i < 0) {
    return;
  }
  console.log(`begin: ${i}`);
  foo(i - 1);
  console.log(`end: ${i}`);
}
foo(3);

// Logs:
// begin: 3
// begin: 2
// begin: 1
// begin: 0
// end: 0
// end: 1
// end: 2
// end: 3

嵌套函数和闭包

¥Nested functions and closures

你可以将一个函数嵌套在另一个函数中。嵌套(内部)函数对其包含(外部)函数是私有的。

¥You may nest a function within another function. The nested (inner) function is private to its containing (outer) function.

它还形成了一个封闭。闭包是一个表达式(最常见的是一个函数),它可以具有自由变量以及绑定这些变量的环境(即 "closes" 表达式)。

¥It also forms a closure. A closure is an expression (most commonly, a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

由于嵌套函数是一个闭包,这意味着嵌套函数可以 "inherit" 其包含函数的参数和变量。换句话说,内部函数包含外部函数的范围。

¥Since a nested function is a closure, this means that a nested function can "inherit" the arguments and variables of its containing function. In other words, the inner function contains the scope of the outer function.

总结一下:

¥To summarize:

  • 内部函数只能从外部函数中的语句访问。
  • 内部函数形成一个闭包:内部函数可以使用外部函数的参数和变量,而外部函数不能使用内部函数的参数和变量。

以下示例显示了嵌套函数:

¥The following example shows nested functions:

js
function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}

console.log(addSquares(2, 3)); // 13
console.log(addSquares(3, 4)); // 25
console.log(addSquares(4, 5)); // 41

由于内部函数形成一个闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:

¥Since the inner function forms a closure, you can call the outer function and specify arguments for both the outer and inner function:

js
function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}

const fnInside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
console.log(fnInside(5)); // 8
console.log(outside(3)(5)); // 8

变量的保存

¥Preservation of variables

请注意返回 inside 时如何保留 x。闭包必须保留它引用的所有作用域中的参数和变量。由于每次调用都提供了可能不同的参数,因此每次调用 outside 都会创建一个新的闭包。仅当返回的 inside 不再可访问时才能释放内存。

¥Notice how x is preserved when inside is returned. A closure must preserve the arguments and variables in all scopes it references. Since each call provides potentially different arguments, a new closure is created for each call to outside. The memory can be freed only when the returned inside is no longer accessible.

这与在其他对象中存储引用没有什么不同,但通常不太明显,因为人们不直接设置引用并且无法检查它们。

¥This is not different from storing references in other objects, but is often less obvious because one does not set the references directly and cannot inspect them.

多重嵌套函数

¥Multiply-nested functions

函数可以多重嵌套。例如:

¥Functions can be multiply-nested. For example:

  • 函数 (A) 包含函数 (B),函数 (B) 本身又包含函数 (C)。
  • 函数 BC 在此形成闭包。因此,B 可以访问 AC 可以访问 B
  • 另外,由于 C 可以访问 B,而 B 可以访问 A,所以 C 也可以访问 A

因此,闭包可以包含多个作用域;它们递归地包含包含它的函数的范围。这称为作用域链。(后面会解释为什么叫 "chaining"。)

¥Thus, the closures can contain multiple scopes; they recursively contain the scope of the functions containing it. This is called scope chaining. (The reason it is called "chaining" is explained later.)

考虑以下示例:

¥Consider the following example:

js
function A(x) {
  function B(y) {
    function C(z) {
      console.log(x + y + z);
    }
    C(3);
  }
  B(2);
}
A(1); // Logs 6 (which is 1 + 2 + 3)

在此示例中,C 访问 ByAx

¥In this example, C accesses B's y and A's x.

可以这样做是因为:

¥This can be done because:

  1. B 形成一个包含 A 的闭包(即 B 可以访问 A 的参数和变量)。
  2. C 形成一个包含 B 的闭包。
  3. 因为 C 的闭包包含 BB 的闭包包含 A,所以 C 的闭包也包含 A。这意味着 C 可以访问 BA 的参数和变量。换句话说,C 按顺序链接 BA 的范围。

然而,反之则不然。A 无法访问 C,因为 A 无法访问 B 的任何参数或变量,而 CB 的变量。因此,C 仅对 B 保持私有。

¥The reverse, however, is not true. A cannot access C, because A cannot access any argument or variable of B, which C is a variable of. Thus, C remains private to only B.

名称冲突

¥Name conflicts

当闭包作用域中的两个参数或变量具有相同的名称时,就会出现名称冲突。更多嵌套范围优先。因此,最内层的作用域具有最高的优先级,而最外层的作用域具有最低的优先级。这就是作用域链。链上的第一个是最里面的范围,最后一个是最外面的范围。考虑以下:

¥When two arguments or variables in the scopes of a closure have the same name, there is a name conflict. More nested scopes take precedence. So, the innermost scope takes the highest precedence, while the outermost scope takes the lowest. This is the scope chain. The first on the chain is the innermost scope, and the last is the outermost scope. Consider the following:

js
function outside() {
  const x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

console.log(outside()(10)); // 20 (instead of 10)

名称冲突发生在语句 return x * 2 处,并且是在 inside 的参数 xoutside 的变量 x 之间。这里的作用域链是{inside,outside,全局对象}。因此,insidex 优先于 outsidex,并且返回 20insidex)而不是 10outsidex)。

¥The name conflict happens at the statement return x * 2 and is between inside's parameter x and outside's variable x. The scope chain here is {inside, outside, global object}. Therefore, inside's x takes precedences over outside's x, and 20 (inside's x) is returned instead of 10 (outside's x).

闭包

¥Closures

闭包是 JavaScript 最强大的功能之一。JavaScript 允许函数嵌套,并授予内部函数对外部函数内部定义的所有变量和函数(以及外部函数有权访问的所有其他变量和函数)的完全访问权限。

¥Closures are one of the most powerful features of JavaScript. JavaScript allows for the nesting of functions and grants the inner function full access to all the variables and functions defined inside the outer function (and all other variables and functions that the outer function has access to).

但是,外部函数无法访问内部函数内部定义的变量和函数。这为内部函数的变量提供了一种封装。

¥However, the outer function does not have access to the variables and functions defined inside the inner function. This provides a sort of encapsulation for the variables of the inner function.

此外,由于内部函数可以访问外部函数的作用域,因此如果内部函数设法在外部函数的生命周期之外生存,则外部函数中定义的变量和函数的生存时间将比外部函数执行的持续时间长。 功能。当内部函数以某种方式可用于外部函数之外的任何作用域时,就会创建闭包。

¥Also, since the inner function has access to the scope of the outer function, the variables and functions defined in the outer function will live longer than the duration of the outer function execution, if the inner function manages to survive beyond the life of the outer function. A closure is created when the inner function is somehow made available to any scope outside the outer function.

js
// The outer function defines a variable called "name"
const pet = function (name) {
  const getName = function () {
    // The inner function has access to the "name" variable of the outer function
    return name;
  };
  return getName; // Return the inner function, thereby exposing it to outer scopes
};
const myPet = pet("Vivie");

console.log(myPet()); // "Vivie"

它可能比上面的代码复杂得多。可以返回一个包含用于操作外部函数的内部变量的方法的对象。

¥It can be much more complex than the code above. An object containing methods for manipulating the inner variables of the outer function can be returned.

js
const createPet = function (name) {
  let sex;

  const pet = {
    // setName(newName) is equivalent to setName: function (newName)
    // in this context
    setName(newName) {
      name = newName;
    },

    getName() {
      return name;
    },

    getSex() {
      return sex;
    },

    setSex(newSex) {
      if (
        typeof newSex === "string" &&
        (newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")
      ) {
        sex = newSex;
      }
    },
  };

  return pet;
};

const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie

pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver

在上面的代码中,外部函数的 name 变量可以被内部函数访问,并且除了通过内部函数之外没有其他方法可以访问内部变量。内部函数的内部变量充当外部参数和变量的安全存储。它们保存 "persistent" 和 "encapsulated" 数据供内部函数使用。这些函数甚至不必分配给变量或有名称。

¥In the code above, the name variable of the outer function is accessible to the inner functions, and there is no other way to access the inner variables except through the inner functions. The inner variables of the inner functions act as safe stores for the outer arguments and variables. They hold "persistent" and "encapsulated" data for the inner functions to work with. The functions do not even have to be assigned to a variable, or have a name.

js
const getCode = (function () {
  const apiCode = "0]Eal(eh&2"; // A code we do not want outsiders to be able to modify…

  return function () {
    return apiCode;
  };
})();

console.log(getCode()); // "0]Eal(eh&2"

注意:使用闭包时有许多陷阱需要注意!

¥Note: There are a number of pitfalls to watch out for when using closures!

如果封闭函数定义了与外部作用域中的变量同名的变量,则无法再次引用外部作用域中的变量。(内部作用域变量 "overrides" 是外部作用域变量,直到程序退出内部作用域。可以将其视为 名称冲突。)

¥If an enclosed function defines a variable with the same name as a variable in the outer scope, then there is no way to refer to the variable in the outer scope again. (The inner scope variable "overrides" the outer one, until the program exits the inner scope. It can be thought of as a name conflict.)

js
const createPet = function (name) {
  // The outer function defines a variable called "name".
  return {
    setName(name) {
      // The enclosed function also defines a variable called "name".
      name = name; // How do we access the "name" defined by the outer function?
    },
  };
};

使用参数对象

¥Using the arguments object

函数的参数保存在类似数组的对象中。在函数内,你可以按如下方式处理传递给它的参数:

¥The arguments of a function are maintained in an array-like object. Within a function, you can address the arguments passed to it as follows:

js
arguments[i];

其中 i 是参数的序号,从 0 开始。因此,传递给函数的第一个参数将是 arguments[0]。参数总数由 arguments.length 指示。

¥where i is the ordinal number of the argument, starting at 0. So, the first argument passed to a function would be arguments[0]. The total number of arguments is indicated by arguments.length.

使用 arguments 对象,你可以使用比正式声明接受的参数更多的参数来调用函数。如果你事先不知道将传递给函数的参数数量,这通常很有用。你可以使用 arguments.length 来确定实际传递给函数的参数数量,然后使用 arguments 对象访问每个参数。

¥Using the arguments object, you can call a function with more arguments than it is formally declared to accept. This is often useful if you don't know in advance how many arguments will be passed to the function. You can use arguments.length to determine the number of arguments actually passed to the function, and then access each argument using the arguments object.

例如,考虑一个连接多个字符串的函数。该函数的唯一正式参数是一个字符串,它指定分隔要连接的项的字符。该函数定义如下:

¥For example, consider a function that concatenates several strings. The only formal argument for the function is a string that specifies the characters that separate the items to concatenate. The function is defined as follows:

js
function myConcat(separator) {
  let result = ""; // initialize list
  // iterate through arguments
  for (let i = 1; i < arguments.length; i++) {
    result += arguments[i] + separator;
  }
  return result;
}

你可以向此函数传递任意数量的参数,它将每个参数连接成字符串 "list":

¥You can pass any number of arguments to this function, and it concatenates each argument into a string "list":

js
console.log(myConcat(", ", "red", "orange", "blue"));
// "red, orange, blue, "

console.log(myConcat("; ", "elephant", "giraffe", "lion", "cheetah"));
// "elephant; giraffe; lion; cheetah; "

console.log(myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley"));
// "sage. basil. oregano. pepper. parsley. "

注意:arguments 变量是 "array-like",但不是数组。它与数组类似,具有编号索引和 length 属性。然而,它并不具备所有的数组操作方法。

¥Note: The arguments variable is "array-like", but not an array. It is array-like in that it has a numbered index and a length property. However, it does not possess all of the array-manipulation methods.

有关详细信息,请参阅 JavaScript 参考中的 Function 对象。

¥See the Function object in the JavaScript reference for more information.

函数参数

¥Function parameters

有两种特殊的参数语法:默认参数和其余参数。

¥There are two special kinds of parameter syntax: default parameters and rest parameters.

默认参数

¥Default parameters

在 JavaScript 中,函数的参数默认为 undefined。但是,在某些情况下,设置不同的默认值可能会很有用。这正是默认参数的作用。

¥In JavaScript, parameters of functions default to undefined. However, in some situations it might be useful to set a different default value. This is exactly what default parameters do.

过去,设置默认值的一般策略是测试函数体中的参数值,如果是 undefined 则赋值。

¥In the past, the general strategy for setting defaults was to test parameter values in the body of the function and assign a value if they are undefined.

在以下示例中,如果没有为 b 提供任何值,则在计算 a*b 时其值为 undefined,并且对 multiply 的调用通常会返回 NaN。但是,本示例中的第二行阻止了这种情况:

¥In the following example, if no value is provided for b, its value would be undefined when evaluating a*b, and a call to multiply would normally have returned NaN. However, this is prevented by the second line in this example:

js
function multiply(a, b) {
  b = typeof b !== "undefined" ? b : 1;
  return a * b;
}

console.log(multiply(5)); // 5

使用默认参数,不再需要在函数体中手动检查。你可以在函数头中将 1 作为 b 的默认值:

¥With default parameters, a manual check in the function body is no longer necessary. You can put 1 as the default value for b in the function head:

js
function multiply(a, b = 1) {
  return a * b;
}

console.log(multiply(5)); // 5

有关更多详细信息,请参阅参考资料中的 默认参数

¥For more details, see default parameters in the reference.

其余参数

¥Rest parameters

剩余参数 语法允许我们将不定数量的参数表示为数组。

¥The rest parameter syntax allows us to represent an indefinite number of arguments as an array.

在以下示例中,函数 multiply 使用剩余参数来收集从第二个到末尾的参数。然后该函数将它们乘以第一个参数。

¥In the following example, the function multiply uses rest parameters to collect arguments from the second one to the end. The function then multiplies these by the first argument.

js
function multiply(multiplier, ...theArgs) {
  return theArgs.map((x) => multiplier * x);
}

const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

箭头函数

¥Arrow functions

与函数表达式相比,箭头函数表达式(也称为粗箭头,以区别于未来 JavaScript 中假设的 -> 语法)的语法较短,并且没有自己的 thisargumentssupernew.target。箭头函数始终是匿名的。

¥An arrow function expression (also called a fat arrow to distinguish from a hypothetical -> syntax in future JavaScript) has a shorter syntax compared to function expressions and does not have its own this, arguments, super, or new.target. Arrow functions are always anonymous.

有两个因素影响了箭头函数的引入:this 的功能更短且无约束力。

¥Two factors influenced the introduction of arrow functions: shorter functions and non-binding of this.

较短的函数

¥Shorter functions

在某些功能模式中,较短的功能是受欢迎的。比较:

¥In some functional patterns, shorter functions are welcome. Compare:

js
const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

const a2 = a.map(function (s) {
  return s.length;
});

console.log(a2); // [8, 6, 7, 9]

const a3 = a.map((s) => s.length);

console.log(a3); // [8, 6, 7, 9]

没有单独这个

¥No separate this

在箭头函数出现之前,每个新函数都定义了自己的 this 值(在构造函数的情况下为新对象,在 严格模式 函数调用中未定义,如果函数作为 "对象方法" 调用则为基对象,等等)。事实证明,对于面向对象的编程风格来说,这并不理想。

¥Until arrow functions, every new function defined its own this value (a new object in the case of a constructor, undefined in strict mode function calls, the base object if the function is called as an "object method", etc.). This proved to be less than ideal with an object-oriented style of programming.

js
function Person() {
  // The Person() constructor defines `this` as itself.
  this.age = 0;

  setInterval(function growUp() {
    // In nonstrict mode, the growUp() function defines `this`
    // as the global object, which is different from the `this`
    // defined by the Person() constructor.
    this.age++;
  }, 1000);
}

const p = new Person();

在 ECMAScript 3/5 中,通过将 this 中的值分配给可以关闭的变量来解决此问题。

¥In ECMAScript 3/5, this issue was fixed by assigning the value in this to a variable that could be closed over.

js
function Person() {
  // Some choose `that` instead of `self`.
  // Choose one and be consistent.
  const self = this;
  self.age = 0;

  setInterval(function growUp() {
    // The callback refers to the `self` variable of which
    // the value is the expected object.
    self.age++;
  }, 1000);
}

或者,可以创建 绑定函数,以便将正确的 this 值传递给 growUp() 函数。

¥Alternatively, a bound function could be created so that the proper this value would be passed to the growUp() function.

箭头函数没有自己的 this;使用封闭执行上下文的 this 值。因此,在以下代码中,传递给 setInterval 的函数内的 this 与封闭函数中的 this 具有相同的值:

¥An arrow function does not have its own this; the this value of the enclosing execution context is used. Thus, in the following code, the this within the function that is passed to setInterval has the same value as this in the enclosing function:

js
function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // `this` properly refers to the person object
  }, 1000);
}

const p = new Person();