JavaScript 语言概述

JavaScript 是一种多范式、动态语言,具有类型和运算符、标准内置对象和方法。它的语法基于 Java 和 C 语言 - 这些语言的许多结构也适用于 JavaScript。JavaScript 支持使用 对象原型 和类进行面向对象编程。它还支持函数式编程,因为函数是 first-class 对象,可以通过表达式轻松创建并像任何其他对象一样传递。

¥JavaScript is a multi-paradigm, dynamic language with types and operators, standard built-in objects, and methods. Its syntax is based on the Java and C languages — many structures from those languages apply to JavaScript as well. JavaScript supports object-oriented programming with object prototypes and classes. It also supports functional programming since functions are first-class objects that can be easily created via expressions and passed around like any other object.

本页面快速概述了各种 JavaScript 语言功能,专为具有其他语言(例如 C 或 Java)背景的读者编写。

¥This page serves as a quick overview of various JavaScript language features, written for readers with background in other languages, such as C or Java.

数据类型

¥Data types

让我们首先看看任何语言的构建块:类型。JavaScript 程序操作值,而这些值都属于一种类型。JavaScript 提供了七种基本类型:

¥Let's start off by looking at the building blocks of any language: the types. JavaScript programs manipulate values, and those values all belong to a type. JavaScript offers seven primitive types:

  • 数字:用于除非常大的整数之外的所有数值(整数和浮点数)。
  • BigInt:用于任意大的整数。
  • 字符串:用于存储文本。
  • 布尔值truefalse — 通常用于条件逻辑。
  • 符合:用于创建不会冲突的唯一标识符。
  • 不明确的:表明变量还没有被赋值。
  • 无效的:表明故意没有价值。

其他一切都称为 对象。常见的对象类型包括:

¥Everything else is known as an Object. Common object types include:

函数并不是 JavaScript 中的特殊数据结构 - 它们只是一种可以调用的特殊类型的对象。

¥Functions aren't special data structures in JavaScript — they are just a special type of object that can be called.

数字

¥Numbers

JavaScript 有两种内置的数字类型:数字和 BigInt。

¥JavaScript has two built-in numeric types: Number and BigInt.

Number 类型是 IEEE 754 64 位双精度浮点值,这意味着整数可以安全地表示在 -(253 − 1)253 - 1 之间而不会损失精度,浮点数可以一直存储到 1.79×10308。在数字中,JavaScript 不区分浮点数和整数。

¥The Number type is a IEEE 754 64-bit double-precision floating point value, which means integers can be safely represented between -(253 − 1) and 253 − 1 without loss of precision, and floating point numbers can be stored all the way up to 1.79 × 10308. Within numbers, JavaScript does not distinguish between floating point numbers and integers.

js
console.log(3 / 2); // 1.5, not 1

因此,表面上的整数实际上隐含着浮点数。由于 IEEE 754 编码,有时浮点运算可能不精确。

¥So an apparent integer is in fact implicitly a float. Because of IEEE 754 encoding, sometimes floating point arithmetic can be imprecise.

js
console.log(0.1 + 0.2); // 0.30000000000000004

对于需要整数的运算,例如按位运算,数字将转换为 32 位整数。

¥For operations that expect integers, such as bitwise operations, the number will be converted to a 32-bit integer.

数字字面量 还可以具有表示基数的前缀(二进制、八进制、十进制或十六进制)或指数后缀。

¥Number literals can also have prefixes to indicate the base (binary, octal, decimal, or hexadecimal), or an exponent suffix.

js
console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503

BigInt 类型是任意长度的整数。它的行为类似于 C 的整数类型(例如除法截断为零),但它可以无限增长。BigInt 是用数字文字和 n 后缀指定的。

¥The BigInt type is an arbitrary length integer. Its behavior is similar to C's integer types (e.g. division truncates to zero), except it can grow indefinitely. BigInts are specified with a number literal and an n suffix.

js
console.log(-3n / 2n); // -1n

支持标准 算术运算符,包括加法、减法、余数运算等。BigInt 和数字不能混合进行算术运算。

¥The standard arithmetic operators are supported, including addition, subtraction, remainder arithmetic, etc. BigInts and numbers cannot be mixed in arithmetic operations.

Math 对象提供标准数学函数和常量。

¥The Math object provides standard mathematical functions and constants.

js
Math.sin(3.5);
const circumference = 2 * Math.PI * r;

将字符串转换为数字有以下三种方法:

¥There are three ways to convert a string to a number:

  • parseInt(),它将字符串解析为整数。
  • parseFloat(),它将字符串解析为浮点数。
  • Number() 函数,将字符串解析为数字文字,并支持许多不同的数字表示形式。

你还可以使用 一元加 + 作为 Number() 的简写。

¥You can also use the unary plus + as a shorthand for Number().

数值还包括 NaN("不是一个数字" 的缩写)和 Infinity。许多 "无效的数学" 操作将返回 NaN — 例如,如果尝试解析非数字字符串,或对负值使用 Math.log()。除以零产生 Infinity(正或负)。

¥Number values also include NaN (short for "Not a Number") and Infinity. Many "invalid math" operations will return NaN — for example, if attempting to parse a non-numeric string, or using Math.log() on a negative value. Division by zero produces Infinity (positive or negative).

NaN 具有传染性:如果将其作为任何数学运算的操作数提供,结果也将为 NaNNaN 是 JavaScript 中唯一不等于自身的值(根据 IEEE 754 规范)。

¥NaN is contagious: if you provide it as an operand to any mathematical operation, the result will also be NaN. NaN is the only value in JavaScript that's not equal to itself (per IEEE 754 specification).

字符串

¥Strings

JavaScript 中的字符串是 Unicode 字符的序列。对于任何必须应对国际化的人来说,这应该是一个受欢迎的消息。更准确地说,他们是 UTF-16 编码

¥Strings in JavaScript are sequences of Unicode characters. This should be welcome news to anyone who has had to deal with internationalization. More accurately, they are UTF-16 encoded.

js
console.log("Hello, world");
console.log("你好,世界!"); // Nearly all Unicode characters can be written literally in string literals

字符串可以用单引号或双引号书写 - JavaScript 不区分字符和字符串。如果要表示单个字符,只需使用由该单个字符组成的字符串即可。

¥Strings can be written with either single or double quotes — JavaScript does not have the distinction between characters and strings. If you want to represent a single character, you just use a string consisting of that single character.

js
console.log("Hello"[1] === "e"); // true

要查找字符串的长度(以 代码单元 为单位),请访问其 length 属性。

¥To find the length of a string (in code units), access its length property.

字符串有 实用方法 来操作字符串并访问有关字符串的信息。由于所有基元在设计上都是不可变的,因此这些方法返回新字符串。

¥Strings have utility methods to manipulate the string and access information about the string. Because all primitives are immutable by design, these methods return new strings.

+ 运算符对于字符串是重载的:当操作数之一是字符串时,它执行字符串连接而不是数字加法。特殊的 模板文字 语法允许你更简洁地编写带有嵌入表达式的字符串。与 Python 的 f 字符串或 C# 的内插字符串不同,模板文字使用反引号(不是单引号或双引号)。

¥The + operator is overloaded for strings: when one of the operands is a string, it performs string concatenation instead of number addition. A special template literal syntax allows you to write strings with embedded expressions more succinctly. Unlike Python's f-strings or C#'s interpolated strings, template literals use backticks (not single or double quotes).

js
const age = 25;
console.log("I am " + age + " years old."); // String concatenation
console.log(`I am ${age} years old.`); // Template literal

其他类型

¥Other types

JavaScript 区分 nullundefined,前者表示故意无值(并且只能通过 null 关键字访问),后者表示不存在值。获取 undefined 的方法有多种:

¥JavaScript distinguishes between null, which indicates a deliberate non-value (and is only accessible through the null keyword), and undefined, which indicates absence of value. There are many ways to obtain undefined:

  • 没有值 (return;) 的 return 语句隐式返回 undefined
  • 访问不存在的 object 属性 (obj.iDontExist) 将返回 undefined
  • 未初始化的变量声明 (let x;) 将隐式将变量初始化为 undefined

JavaScript 有一个布尔类型,可能的值有 truefalse — 两者都是关键字。任何值都可以根据以下规则转换为布尔值:

¥JavaScript has a Boolean type, with possible values true and false — both of which are keywords. Any value can be converted to a boolean according to the following rules:

  1. false0、空字符串("")、NaNnullundefined 全部变成 false
  2. 所有其他值都变为 true

你可以使用 Boolean() 函数显式执行此转换:

¥You can perform this conversion explicitly using the Boolean() function:

js
Boolean(""); // false
Boolean(234); // true

然而,这很少是必要的,因为当 JavaScript 需要布尔值时,例如在 if 语句中(请参阅 控制结构),它会默默地执行此转换。因此,我们有时会提到“truthy”和“falsy”,表示在布尔上下文中使用时分别变为 truefalse 的值。

¥However, this is rarely necessary, as JavaScript will silently perform this conversion when it expects a boolean, such as in an if statement (see Control structures). For this reason, we sometimes speak of "truthy" and "falsy", meaning values that become true and false, respectively, when used in boolean contexts.

支持 &&(逻辑与)、||(逻辑或)、!(逻辑非)等布尔运算;见 运算符

¥Boolean operations such as && (logical and), || (logical or), and ! (logical not) are supported; see Operators.

符号类型通常用于创建唯一标识符。使用 Symbol() 函数创建的每个符号都保证是唯一的。此外,还有注册符号(共享常量)和众所周知的符号(语言将其用作某些操作的 "protocols")。你可以在 符号参考 中阅读有关它们的更多信息。

¥The Symbol type is often used to create unique identifiers. Every symbol created with the Symbol() function is guaranteed to be unique. In addition, there are registered symbols, which are shared constants, and well-known symbols, which are utilized by the language as "protocols" for certain operations. You can read more about them in the symbol reference.

变量

¥Variables

JavaScript 中的变量是使用以下三个关键字之一声明的:letconstvar

¥Variables in JavaScript are declared using one of three keywords: let, const, or var.

let 允许你声明块级变量。声明的变量可从它所包含的块中获得。

¥let allows you to declare block-level variables. The declared variable is available from the block it is enclosed in.

js
let a;
let name = "Simon";

// myLetVariable is *not* visible out here

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable is only visible in here
}

// myLetVariable is *not* visible out here

const 允许你声明其值永远不会更改的变量。该变量可从声明它的块中获得。

¥const allows you to declare variables whose values are never intended to change. The variable is available from the block it is declared in.

js
const Pi = 3.14; // Declare variable Pi
console.log(Pi); // 3.14

const 声明的变量不能重新赋值。

¥A variable declared with const cannot be reassigned.

js
const Pi = 3.14;
Pi = 1; // will throw an error because you cannot change a constant variable.

const 声明仅防止重新分配 - 它们不会防止变量值的突变(如果它是一个对象)。

¥const declarations only prevent reassignments — they don't prevent mutations of the variable's value, if it's an object.

js
const obj = {};
obj.a = 1; // no error
console.log(obj); // { a: 1 }

var 声明可能具有令人惊讶的行为(例如,它们不是块作用域),并且在现代 JavaScript 代码中不鼓励使用它们。

¥var declarations can have surprising behaviors (for example, they are not block-scoped), and they are discouraged in modern JavaScript code.

如果声明一个变量但没有为其分配任何值,则其值为 undefined。如果没有初始化器,则无法声明 const 变量,因为以后无论如何都无法更改它。

¥If you declare a variable without assigning any value to it, its value is undefined. You can't declare a const variable without an initializer, because you can't change it later anyway.

letconst 声明的变量仍然占据它们定义的整个范围,并且位于实际声明行之前的称为 颞死区 的区域中。这与变量阴影有一些有趣的交互,这在其他语言中不会发生。

¥let and const declared variables still occupy the entire scope they are defined in, and are in a region known as the temporal dead zone before the actual line of declaration. This has some interesting interactions with variable shadowing, which don't occur in other languages.

js
function foo(x, condition) {
  if (condition) {
    console.log(x);
    const x = 2;
    console.log(x);
  }
}

foo(1, true);

在大多数其他语言中,这将记录 "1" 和 "2",因为在 const x = 2 行之前,x 仍应引用上部范围中的参数 x。在 JavaScript 中,因为每个声明都占据整个作用域,所以这会在第一个 console.log 上引发错误:"初始化前无法访问 'x'"。有关详细信息,请参阅 let 的参考页。

¥In most other languages, this would log "1" and "2", because before the const x = 2 line, x should still refer to the parameter x in the upper scope. In JavaScript, because each declaration occupies the entire scope, this would throw an error on the first console.log: "Cannot access 'x' before initialization". For more information, see the reference page of let.

JavaScript 是动态类型的。类型(如 上一节 中所述)仅与值关联,但与变量无关。对于 let 声明的变量,你始终可以通过重新赋值来更改其类型。

¥JavaScript is dynamically typed. Types (as described in the previous section) are only associated with values, but not with variables. For let-declared variables, you can always change its type through reassignment.

js
let a = 1;
a = "foo";

运算符

¥Operators

JavaScript 的数字运算符包括 +-*/%(余数)和 **(求幂)。使用 = 分配值。每个二元运算符还有一个复合赋值对应项,例如 +=-=,它们延伸到 x = x operator y

¥JavaScript's numeric operators include +, -, *, /, % (remainder), and ** (exponentiation). Values are assigned using =. Each binary operator also has a compound assignment counterpart such as += and -=, which extend out to x = x operator y.

js
x += 5;
x = x + 5;

你可以使用 ++-- 分别递增和递减。这些可以用作前缀或后缀运算符。

¥You can use ++ and -- to increment and decrement respectively. These can be used as a prefix or postfix operators.

+ 运算符 还可以进行字符串连接:

¥The + operator also does string concatenation:

js
"hello" + " world"; // "hello world"

如果将字符串添加到数字(或其他值),则所有内容都会首先转换为字符串。这可能会让你绊倒:

¥If you add a string to a number (or other value) everything is converted into a string first. This might trip you up:

js
"3" + 4 + 5; // "345"
3 + 4 + "5"; // "75"

向某些内容添加空字符串是将其转换为字符串本身的有用方法。

¥Adding an empty string to something is a useful way of converting it to a string itself.

JavaScript 中的 比较 可以使用 <><=>= 来表示,它们既适用于字符串也适用于数字。为了相等,如果你给它不同的类型,双等于运算符 就会执行类型强制,有时会产生有趣的结果。另一方面,三等于运算符 不会尝试类型强制,并且通常是首选。

¥Comparisons in JavaScript can be made using <, >, <= and >=, which work for both strings and numbers. For equality, the double-equals operator performs type coercion if you give it different types, with sometimes interesting results. On the other hand, the triple-equals operator does not attempt type coercion, and is usually preferred.

js
123 == "123"; // true
1 == true; // true

123 === "123"; // false
1 === true; // false

二等号和三等号也有对应的不等式:!=!==

¥The double-equals and triple-equals also have their inequality counterparts: != and !==.

JavaScript 也有 按位运算符逻辑运算符。值得注意的是,逻辑运算符不仅仅适用于布尔值 - 它们适用于值的 "truthiness"。

¥JavaScript also has bitwise operators and logical operators. Notably, logical operators don't work with boolean values only — they work by the "truthiness" of the value.

js
const a = 0 && "Hello"; // 0 because 0 is "falsy"
const b = "Hello" || "world"; // "Hello" because both "Hello" and "world" are "truthy"

&&|| 运算符使用短路逻辑,这意味着它们是否执行第二个操作数取决于第一个操作数。这对于在访问其属性之前检查空对象非常有用:

¥The && and || operators use short-circuit logic, which means whether they will execute their second operand is dependent on the first. This is useful for checking for null objects before accessing their attributes:

js
const name = o && o.getName();

或者对于缓存值(当虚假值无效时):

¥Or for caching values (when falsy values are invalid):

js
const name = cachedName || (cachedName = getName());

有关运算符的完整列表,请参阅 引导页参考部分。你可能对 运算符优先级 特别感兴趣。

¥For a comprehensive list of operators, see the guide page or reference section. You may be especially interested in the operator precedence.

语法

¥Grammar

JavaScript 语法与 C 家族非常相似。有几点值得一提:

¥JavaScript grammar is very similar to the C family. There are a few points worth mentioning:

  • 身份标识 可以有 Unicode 字符,但它们不能是 保留字 之一。
  • 评论 通常是 ///* */,而许多其他脚本语言(如 Perl、Python 和 Bash)则使用 #
  • JavaScript 中的分号是可选的 - 需要时使用 自动插入它们 语言。然而,有一些警告需要注意,因为与 Python 不同,分号仍然是语法的一部分。

要深入了解 JavaScript 语法,请参阅 词汇语法参考页

¥For an in-depth look at the JavaScript grammar, see the reference page for lexical grammar.

控制结构

¥Control structures

JavaScript 具有与 C 家族中其他语言类似的控制结构集。ifelse 支持条件语句;你可以将它们链接在一起:

¥JavaScript has a similar set of control structures to other languages in the C family. Conditional statements are supported by if and else; you can chain them together:

js
let name = "kittens";
if (name === "puppies") {
  name += " woof";
} else if (name === "kittens") {
  name += " meow";
} else {
  name += "!";
}
name === "kittens meow";

JavaScript 没有 elifelse if 实际上只是由单个 if 语句组成的 else 分支。

¥JavaScript doesn't have elif, and else if is really just an else branch comprised of a single if statement.

JavaScript 有 while 循环和 do...while 循环。第一个适用于基本循环;第二个是 for 循环,你希望确保循环体至少执行一次:

¥JavaScript has while loops and do...while loops. The first is good for basic looping; the second is for loops where you wish to ensure that the body of the loop is executed at least once:

js
while (true) {
  // an infinite loop!
}

let input;
do {
  input = get_input();
} while (inputIsNotValid(input));

JavaScript 的 for 循环 与 C 和 Java 中的相同:它允许你在一行上提供循环的控制信息。

¥JavaScript's for loop is the same as that in C and Java: it lets you provide the control information for your loop on a single line.

js
for (let i = 0; i < 5; i++) {
  // Will execute 5 times
}

JavaScript 还包含另外两个著名的 for 循环:for...of,它迭代 iterables,尤其是数组;for...in,它访问对象的所有 enumerable 属性。

¥JavaScript also contains two other prominent for loops: for...of, which iterates over iterables, most notably arrays, and for...in, which visits all enumerable properties of an object.

js
for (const value of array) {
  // do something with value
}

for (const property in object) {
  // do something with object property
}

switch 语句可用于基于相等性检查的多个分支。

¥The switch statement can be used for multiple branches based on equality checking.

js
switch (action) {
  case "draw":
    drawIt();
    break;
  case "eat":
    eatIt();
    break;
  default:
    doNothing();
}

与 C 类似,case 子句在概念上与 labels 相同,因此如果不添加 break 语句,执行将 "跌倒" 到下一级。然而,它们实际上并不是跳转表 - 任何表达式都可以是 case 子句的一部分,而不仅仅是字符串或数字文字,并且它们将被一一求值,直到 1 等于匹配的值。使用 === 运算符在两者之间进行比较。

¥Similar to C, case clauses are conceptually the same as labels, so if you don't add a break statement, execution will "fall through" to the next level. However, they are not actually jump tables — any expression can be part of the case clause, not just string or number literals, and they would be evaluated one-by-one until one equals the value being matched. Comparison takes place between the two using the === operator.

与 Rust 等某些语言不同,控制流结构是 JavaScript 中的语句,这意味着你不能将它们分配给变量,例如 const a = if (x) { 1 } else { 2 }

¥Unlike some languages like Rust, control-flow structures are statements in JavaScript, meaning you can't assign them to a variable, like const a = if (x) { 1 } else { 2 }.

JavaScript 错误使用 try...catch 语句进行处理。

¥JavaScript errors are handled using the try...catch statement.

js
try {
  buildMySite("./website");
} catch (e) {
  console.error("Building site failed:", e);
}

可以使用 throw 语句引发错误。许多内置操作也可能会抛出异常。

¥Errors can be thrown using the throw statement. Many built-in operations may throw as well.

js
function buildMySite(siteDirectory) {
  if (!pathExists(siteDirectory)) {
    throw new Error("Site directory does not exist");
  }
}

一般来说,你无法判断刚刚捕获的错误的类型,因为 throw 语句可能会抛出任何错误。但是,你通常可以假设它是 Error 实例,如上面的示例所示。有一些内置的 Error 子类,例如 TypeErrorRangeError,你可以使用它们来提供有关错误的额外语义。JavaScript 中没有条件捕获 - 如果你只想处理一种类型的错误,则需要捕获所有内容,使用 instanceof 识别错误类型,然后重新抛出其他情况。

¥In general, you can't tell the type of the error you just caught, because anything can be thrown from a throw statement. However, you can usually assume it's an Error instance, as is the example above. There are some subclasses of Error built-in, like TypeError and RangeError, that you can use to provide extra semantics about the error. There's no conditional catch in JavaScript — if you only want to handle one type of error, you need to catch everything, identify the type of error using instanceof, and then rethrow the other cases.

js
try {
  buildMySite("./website");
} catch (e) {
  if (e instanceof RangeError) {
    console.error("Seems like a parameter is out of range:", e);
    console.log("Retrying...");
    buildMySite("./website");
  } else {
    // Don't know how to handle other error types; throw them so
    // something else up in the call stack may catch and handle it
    throw e;
  }
}

如果调用堆栈中的任何 try...catch 都未捕获错误,则程序将退出。

¥If an error is uncaught by any try...catch in the call stack, the program will exit.

有关控制流语句的完整列表,请参阅 参考部分

¥For a comprehensive list of control flow statements, see the reference section.

对象

¥Objects

JavaScript 对象可以被认为是键值对的集合。因此,它们类似于:

¥JavaScript objects can be thought of as collections of key-value pairs. As such, they are similar to:

  • Python 中的字典。
  • Perl 和 Ruby 中的哈希值。
  • C 和 C++ 中的哈希表。
  • Java 中的 HashMap。
  • PHP 中的关联数组。

JavaScript 对象是哈希值。与静态类型语言中的对象不同,JavaScript 中的对象没有固定的形状 - 可以随时添加、删除、重新排序、改变或动态查询属性。对象键始终是 stringssymbols - 即使是规范整数的数组索引,实际上也是字符串。

¥JavaScript objects are hashes. Unlike objects in statically typed languages, objects in JavaScript do not have fixed shapes — properties can be added, deleted, re-ordered, mutated, or dynamically queried at any time. Object keys are always strings or symbols — even array indices, which are canonically integers, are actually strings under the hood.

对象通常使用文字语法创建:

¥Objects are usually created using the literal syntax:

js
const obj = {
  name: "Carrot",
  for: "Max",
  details: {
    color: "orange",
    size: 12,
  },
};

对象属性可以是使用点 (.) 或方括号 ([]) 的 accessed。使用点表示法时,密钥必须是有效的 identifier。另一方面,方括号允许使用动态键值对对象进行索引。

¥Object properties can be accessed using dot (.) or square brackets ([]). When using the dot notation, the key must be a valid identifier. Square brackets, on the other hand, allow indexing the object with a dynamic key value.

js
// Dot notation
obj.name = "Simon";
const name = obj.name;

// Bracket notation
obj["name"] = "Simon";
const name = obj["name"];

// Can use a variable to define a key
const userName = prompt("what is your key?");
obj[userName] = prompt("what is its value?");

属性访问可以链接在一起:

¥Property access can be chained together:

js
obj.details.color; // orange
obj["details"]["size"]; // 12

对象始终是引用,因此除非有东西显式复制对象,否则对象的突变对外部是可见的。

¥Objects are always references, so unless something is explicitly copying the object, mutations to an object would be visible to the outside.

js
const obj = {};
function doSomething(o) {
  o.x = 1;
}
doSomething(obj);
console.log(obj.x); // 1

这也意味着两个单独创建的对象永远不会相等(!==),因为它们是不同的引用。如果你持有同一对象的两个引用,则可以通过另一个来观察其中一个的变化。

¥This also means two separately created objects will never be equal (!==), because they are different references. If you hold two references of the same object, mutating one would be observable through the other.

js
const me = {};
const stillMe = me;
me.x = 1;
console.log(stillMe.x); // 1

有关对象和原型的更多信息,请参阅 Object 参考页。有关对象初始值设定项语法的更多信息,请参阅其 参考页

¥For more on objects and prototypes, see the Object reference page. For more information on the object initializer syntax, see its reference page.

本页省略了有关对象原型和继承的所有细节,因为你通常可以使用 classes 实现继承,而无需触及底层机制(你可能听说过它很深奥)。要了解它们,请参阅 继承和原型链

¥This page has omitted all details about object prototypes and inheritance because you can usually achieve inheritance with classes without touching the underlying mechanism (which you may have heard to be abstruse). To learn about them, see Inheritance and the prototype chain.

数组

¥Arrays

JavaScript 中的数组实际上是一种特殊类型的对象。它们的工作方式与常规对象非常相似(自然只能使用 [] 语法访问数字属性),但它们有一个名为 length 的神奇属性。它始终比数组中的最高索引多 1。

¥Arrays in JavaScript are actually a special type of object. They work very much like regular objects (numerical properties can naturally be accessed only using [] syntax) but they have one magic property called length. This is always one more than the highest index in the array.

数组通常使用数组文字创建:

¥Arrays are usually created with array literals:

js
const a = ["dog", "cat", "hen"];
a.length; // 3

JavaScript 数组仍然是对象 - 你可以为其分配任何属性,包括任意数字索引。唯一的 "magic" 是当你设置特定索引时,length 会自动更新。

¥JavaScript arrays are still objects — you can assign any properties to them, including arbitrary number indices. The only "magic" is that length will be automatically updated when you set a particular index.

js
const a = ["dog", "cat", "hen"];
a[100] = "fox";
console.log(a.length); // 101
console.log(a); // ['dog', 'cat', 'hen', empty × 97, 'fox']

我们上面得到的数组被称为 稀疏数组,因为中间有无人居住的槽,并且会导致引擎将其从数组去优化为哈希表。确保你的数组密集!

¥The array we got above is called a sparse array because there are uninhabited slots in the middle, and will cause the engine to deoptimize it from an array to a hash table. Make sure your array is densely populated!

越界索引不会抛出异常。如果查询不存在的数组索引,你将得到 undefined 的返回值:

¥Out-of-bounds indexing doesn't throw. If you query a non-existent array index, you'll get a value of undefined in return:

js
const a = ["dog", "cat", "hen"];
console.log(typeof a[90]); // undefined

数组可以有任何元素,并且可以任意增长或收缩。

¥Arrays can have any elements and can grow or shrink arbitrarily.

js
const arr = [1, "foo", true];
arr.push({});
// arr = [1, "foo", true, {}]

可以使用 for 循环迭代数组,就像在其他类似 C 的语言中一样:

¥Arrays can be iterated with the for loop, as you can in other C-like languages:

js
for (let i = 0; i < a.length; i++) {
  // Do something with a[i]
}

或者,由于数组是可迭代的,因此你可以使用 for...of 循环,它与 C++/Java 的 for (int x : arr) 语法同义:

¥Or, since arrays are iterable, you can use the for...of loop, which is synonymous to C++/Java's for (int x : arr) syntax:

js
for (const currentValue of a) {
  // Do something with currentValue
}

数组带有大量的 数组方法。其中许多会迭代数组 - 例如,map() 会对每个数组元素应用回调,并返回一个新数组:

¥Arrays come with a plethora of array methods. Many of them would iterate the array — for example, map() would apply a callback to every array element, and return a new array:

js
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
// babies = ['baby dog', 'baby cat', 'baby hen']

函数

¥Functions

与对象一样,函数是理解 JavaScript 的核心组件。最基本的函数声明如下所示:

¥Along with objects, functions are the core component in understanding JavaScript. The most basic function declaration looks like this:

js
function add(x, y) {
  const total = x + y;
  return total;
}

JavaScript 函数可以采用 0 个或多个参数。函数体可以包含任意数量的语句,并且可以声明自己的变量,这些变量是该函数的本地变量。return 语句可用于随时返回一个值,从而终止该函数。如果未使用 return 语句(或没有值的空返回),JavaScript 将返回 undefined

¥A JavaScript function can take 0 or more parameters. The function body can contain as many statements as you like and can declare its own variables which are local to that function. The return statement can be used to return a value at any time, terminating the function. If no return statement is used (or an empty return with no value), JavaScript returns undefined.

可以使用比指定的更多或更少的参数来调用函数。如果调用函数时未传递其所需的参数,它们将被设置为 undefined。如果传递的参数多于预期,该函数将忽略多余的参数。

¥Functions can be called with more or fewer parameters than it specifies. If you call a function without passing the parameters it expects, they will be set to undefined. If you pass more parameters than it expects, the function will ignore the extra parameters.

js
add(); // NaN
// Equivalent to add(undefined, undefined)

add(2, 3, 4); // 5
// added the first two; 4 was ignored

还有许多其他可用的参数语法。例如,剩余参数语法 允许将调用者传递的所有额外参数收集到一个数组中,类似于 Python 的 *args。(由于 JS 在语言级别上没有命名参数,因此没有 **kwargs。)

¥There are a number of other parameter syntaxes available. For example, the rest parameter syntax allows collecting all the extra parameters passed by the caller into an array, similar to Python's *args. (Since JS doesn't have named parameters on the language level, there's no **kwargs.)

js
function avg(...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5

在上面的代码中,变量 args 保存传递给函数的所有值。

¥In the above code, the variable args holds all the values that were passed into the function.

剩余参数将存储声明后的所有参数,但不存储声明之前的所有参数。换句话说,function avg(firstValue, ...args) 将把传入函数的第一个值存储在 firstValue 变量中,并将其余参数存储在 args 中。

¥The rest parameter will store all arguments after where it's declared, but not before. In other words, function avg(firstValue, ...args) will store the first value passed into the function in the firstValue variable and the remaining arguments in args.

如果函数接受参数列表并且你已将它们保存在数组中,则可以在函数调用中使用 扩展语法 将数组展开为元素列表。例如:avg(...numbers)

¥If a function accepts a list of arguments and you already hold them in an array, you can use the spread syntax in the function call to spread the array as a list of elements. For instance: avg(...numbers).

我们提到过 JavaScript 没有命名参数。不过,可以使用 对象解构 来实现它们,这样可以方便地打包和解包对象。

¥We mentioned that JavaScript doesn't have named parameters. It's possible, though, to implement them using object destructuring, which allows objects to be conveniently packed and unpacked.

js
// Note the { } braces: this is destructuring an object
function area({ width, height }) {
  return width * height;
}

// The { } braces here create a new object
console.log(area({ width: 2, height: 3 }));

还有 默认参数 语法,它允许省略的参数(或作为 undefined 传递的参数)具有默认值。

¥There's also the default parameter syntax, which allows omitted parameters (or those passed as undefined) to have a default value.

js
function avg(firstValue, secondValue, thirdValue = 0) {
  return (firstValue + secondValue + thirdValue) / 3;
}

avg(1, 2); // 1, instead of NaN

匿名函数

¥Anonymous functions

JavaScript 允许你创建匿名函数 - 即没有名称的函数。在实践中,匿名函数通常用作其他函数的参数,立即分配给可用于调用该函数的变量,或从另一个函数返回。

¥JavaScript lets you create anonymous functions — that is, functions without names. In practice, anonymous functions are typically used as arguments to other functions, immediately assigned to a variable that can be used to invoke the function, or returned from another function.

js
// Note that there's no function name before the parentheses
const avg = function (...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

这使得匿名函数可以通过使用一些参数调用 avg() 来调用 - 也就是说,它在语义上等同于使用 function avg() {} 声明语法来声明该函数。

¥That makes the anonymous function invocable by calling avg() with some arguments — that is, it's semantically equivalent to declaring the function using the function avg() {} declaration syntax.

还有另一种定义匿名函数的方法 - 使用 箭头函数表达式

¥There's another way to define anonymous functions — using an arrow function expression.

js
// Note that there's no function name before the parentheses
const avg = (...args) => {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

// You can omit the `return` when simply returning an expression
const sum = (a, b, c) => a + b + c;

箭头函数在语义上并不等同于函数表达式 - 有关更多信息,请参阅其 参考页

¥Arrow functions are not semantically equivalent to function expressions — for more information, see its reference page.

匿名函数还有另一种用途:它可以在单个表达式中同时声明和调用,称为 立即调用函数表达式 (IIFE)

¥There's another way that anonymous functions can be useful: it can be simultaneously declared and invoked in a single expression, called an Immediately invoked function expression (IIFE):

js
(function () {
  // …
})();

对于 IIFE 的用例,你可以阅读 使用闭包模拟私有方法

¥For use-cases of IIFEs, you can read emulating private methods with closures.

递归函数

¥Recursive functions

JavaScript 允许你递归调用函数。这对于处理树结构(例如浏览器 DOM 中的树结构)特别有用。

¥JavaScript allows you to call functions recursively. This is particularly useful for dealing with tree structures, such as those found in the browser DOM.

js
function countChars(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += countChars(child);
  }
  return count;
}

函数表达式也可以命名,这使得它们可以递归。

¥Function expressions can be named as well, which allows them to be recursive.

js
const charsInBody = (function counter(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += counter(child);
  }
  return count;
})(document.body);

如上所述提供给函数表达式的名称仅适用于该函数自己的作用域。这允许引擎进行更多优化并产生更具可读性的代码。该名称还会显示在调试器和一些堆栈跟踪中,这可以节省调试时间。

¥The name provided to a function expression as above is only available to the function's own scope. This allows more optimizations to be done by the engine and results in more readable code. The name also shows up in the debugger and some stack traces, which can save you time when debugging.

如果你习惯于函数式编程,请注意 JavaScript 中递归对性能的影响。尽管语言规范指定了 尾调用优化,但由于恢复堆栈跟踪和可调试性的困难,只有 JavaScriptCore(Safari 使用)实现了它。对于深度递归,请考虑使用迭代来避免堆栈溢出。

¥If you are used to functional programming, beware of the performance implications of recursion in JavaScript. Although the language specification specifies tail-call optimization, only JavaScriptCore (used by Safari) has implemented it, due to the difficulty of recovering stack traces and debuggability. For deep recursion, consider using iteration instead to avoid stack overflow.

函数是第一类对象

¥Functions are first-class objects

JavaScript 函数是一等对象。这意味着它们可以分配给变量,作为参数传递给其他函数,并从其他函数返回。此外,JavaScript 支持开箱即用的 closures,无需显式捕获,让你可以方便地应用函数式编程风格。

¥JavaScript functions are first-class objects. This means that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. In addition, JavaScript supports closures out-of-the-box without explicit capturing, allowing you to conveniently apply functional programming styles.

js
// Function returning function
const add = (x) => (y) => x + y;
// Function accepting function
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);

请注意,JavaScript 函数本身就是对象 - 就像 JavaScript 中的其他所有内容一样 - 你可以添加或更改它们的属性,就像我们前面在对象部分中看到的那样。

¥Note that JavaScript functions are themselves objects — like everything else in JavaScript — and you can add or change properties on them just like we've seen earlier in the Objects section.

内部函数

¥Inner functions

JavaScript 函数声明允许在其他函数内进行。JavaScript 中嵌套函数的一个重要细节是它们可以访问其父函数作用域中的变量:

¥JavaScript function declarations are allowed inside other functions. An important detail of nested functions in JavaScript is that they can access variables in their parent function's scope:

js
function parentFunc() {
  const a = 1;

  function nestedFunc() {
    const b = 4; // parentFunc can't use this
    return a + b;
  }
  return nestedFunc(); // 5
}

这为编写更易于维护的代码提供了很大的实用性。如果被调用函数依赖于一两个对代码的任何其他部分都没有用的其他函数,则可以将这些实用函数嵌套在其中。这可以减少全局范围内的函数数量。

¥This provides a great deal of utility in writing more maintainable code. If a called function relies on one or two other functions that are not useful to any other part of your code, you can nest those utility functions inside it. This keeps the number of functions that are in the global scope down.

这也是对抗全局变量诱惑的一个很好的方法。在编写复杂代码时,通常很容易使用全局变量在多个函数之间共享值,这会导致代码难以维护。嵌套函数可以共享其父函数中的变量,因此你可以使用该机制将函数耦合在一起,而不会污染全局名称空间。

¥This is also a great counter to the lure of global variables. When writing complex code, it is often tempting to use global variables to share values between multiple functions, which leads to code that is hard to maintain. Nested functions can share variables in their parent, so you can use that mechanism to couple functions together without polluting your global namespace.

¥Classes

JavaScript 提供了与 Java 等语言非常相似的 class 语法。

¥JavaScript offers the class syntax that's very similar to languages like Java.

js
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return `Hello, I'm ${this.name}!`;
  }
}

const p = new Person("Maria");
console.log(p.sayHello());

JavaScript 类只是必须使用 new 运算符实例化的函数。每次实例化一个类时,它都会返回一个包含该类指定的方法和属性的对象。类不强制执行任何代码组织 - 例如,你可以让函数返回类,或者每个文件可以有多个类。下面是一个如何临时创建类的示例:它只是从箭头函数返回的表达式。这种模式称为 mixin

¥JavaScript classes are just functions that must be instantiated with the new operator. Every time a class is instantiated, it returns an object containing the methods and properties that the class specified. Classes don't enforce any code organization — for example, you can have functions returning classes, or you can have multiple classes per file. Here's an example of how ad hoc the creation of a class can be: it's just an expression returned from an arrow function. This pattern is called a mixin.

js
const withAuthentication = (cls) =>
  class extends cls {
    authenticate() {
      // …
    }
  };

class Admin extends withAuthentication(Person) {
  // …
}

静态属性是通过在前面添加 static 创建的。私有属性是通过在前面添加哈希值 #(不是 private)来创建的。哈希值是属性名称的组成部分。(在 Python 中将 # 想象为 _。)与大多数其他语言不同,绝对无法读取类主体之外的私有属性 - 即使在派生类中也是如此。

¥Static properties are created by prepending static. Private properties are created by prepending a hash # (not private). The hash is an integral part of the property name. (Think about # as _ in Python.) Unlike most other languages, there's absolutely no way to read a private property outside the class body — not even in derived classes.

有关各种类别功能的详细指南,你可以阅读 引导页

¥For a detailed guide on various class features, you can read the guide page.

异步编程

¥Asynchronous programming

JavaScript 本质上是单线程的。没有 paralleling;只有 concurrency。异步编程由 事件循环 提供支持,它允许对一组任务进行排队和轮询以完成。

¥JavaScript is single-threaded by nature. There's no paralleling; only concurrency. Asynchronous programming is powered by an event loop, which allows a set of tasks to be queued and polled for completion.

在 JavaScript 中编写异步代码有三种惯用的方法:

¥There are three idiomatic ways to write asynchronous code in JavaScript:

例如,文件读取操作在 JavaScript 中可能如下所示:

¥For example, here's what a file-read operation might look like in JavaScript:

js
// Callback-based
fs.readFile(filename, (err, content) => {
  // This callback is invoked when the file is read, which could be after a while
  if (err) {
    throw err;
  }
  console.log(content);
});
// Code here will be executed while the file is waiting to be read

// Promise-based
fs.readFile(filename)
  .then((content) => {
    // What to do when the file is read
    console.log(content);
  })
  .catch((err) => {
    throw err;
  });
// Code here will be executed while the file is waiting to be read

// Async/await
async function readFile(filename) {
  const content = await fs.readFile(filename);
  console.log(content);
}

核心语言没有指定任何异步编程功能,但在与外部环境交互时(从 询问用户权限、到 获取数据、到 读取文件)至关重要。保持可能长时间运行的操作异步可确保其他进程在该进程等待时仍然可以运行 - 例如,浏览器在等待用户单击按钮授予权限时不会冻结。

¥The core language doesn't specify any asynchronous programming features, but it's crucial when interacting with the external environment — from asking user permissions, to fetching data, to reading files. Keeping the potentially long-running operations async ensures that other processes can still run while this one waits — for example, the browser will not freeze while waiting for the user to click a button to grant permission.

如果你有异步值,则无法同步获取其值。例如,如果你有一个承诺,则只能通过 then() 方法访问最终结果。同样,await 只能在异步上下文中使用,通常是异步函数或模块。Promise 永远不会阻塞 - 只有依赖于 Promise 结果的逻辑才会被推迟;与此同时,其他一切都继续执行。如果你是一名函数式程序员,你可能会将 Promise 识别为 monads,它可以与 then() 映射(但是,它们不是正确的 monad,因为它们会自动展平;即你不能拥有 Promise<Promise<T>>)。

¥If you have an async value, it's not possible to get its value synchronously. For example, if you have a promise, you can only access the eventual result via the then() method. Similarly, await can only be used in an async context, which is usually an async function or a module. Promises are never blocking — only the logic depending on the promise's result will be deferred; everything else continues to execute in the meantime. If you are a functional programmer, you may recognize promises as monads which can be mapped with then() (however, they are not proper monads because they auto-flatten; i.e. you can't have a Promise<Promise<T>>).

事实上,单线程模型使 Node.js 成为服务器端编程的流行选择,因为它具有非阻塞 IO,使得处理大量数据库或文件系统请求的性能非常好。然而,纯 JavaScript 的 CPU 密集型(计算密集型)任务仍然会阻塞主线程。要实现真正的并行,你可能需要使用 workers

¥In fact, the single-threaded model has made Node.js a popular choice for server-side programming due to its non-blocking IO, making handling a large number of database or file-system requests very performant. However, CPU-bound (computationally intensive) tasks that are pure JavaScript will still block the main thread. To achieve real paralleling, you may need to use workers.

要了解有关异步编程的更多信息,你可以阅读 使用承诺 或遵循 异步 JavaScript 教程。

¥To learn more about asynchronous programming, you can read about using promises or follow the asynchronous JavaScript tutorial.

模块

¥Modules

JavaScript 还指定了大多数运行时支持的模块系统。模块通常是一个文件,由其文件路径或 URL 标识。你可以使用 importexport 语句在模块之间交换数据:

¥JavaScript also specifies a module system supported by most runtimes. A module is usually a file, identified by its file path or URL. You can use the import and export statements to exchange data between modules:

js
import { foo } from "./foo.js";

// Unexported variables are local to the module
const b = 2;

export const a = 1;

与 Haskell、Python、Java 等不同,JavaScript 模块解析完全是主机定义的 - 它通常基于 URL 或文件路径,因此相对文件路径 "只是工作" 和 相对于当前模块的路径而不是某个项目根路径。

¥Unlike Haskell, Python, Java, etc., JavaScript module resolution is entirely host-defined — it's usually based on URLs or file paths, so relative file paths "just work" and are relative to the current module's path instead of some project root path.

然而,JavaScript 语言不提供标准库模块 - 所有核心功能都由 MathIntl 等全局变量提供支持。这是由于 JavaScript 长期以来缺乏模块系统,而且选择模块系统涉及到运行时设置的一些更改。

¥However, the JavaScript language doesn't offer standard library modules — all core functionalities are powered by global variables like Math and Intl instead. This is due to the long history of JavaScript lacking a module system, and the fact that opting into the module system involves some changes to the runtime setup.

不同的运行时可能使用不同的模块系统。例如,Node.js 使用包管理器 npm,并且主要基于文件系统,而 Deno 和浏览器完全基于 URL,并且可以从 HTTP URL 解析模块。

¥Different runtimes may use different module systems. For example, Node.js uses the package manager npm and is mostly file-system based, while Deno and browsers are fully URL-based and modules can be resolved from HTTP URLs.

有关详细信息,请参阅 模块指南页面

¥For more information, see the modules guide page.

语言和运行时

¥Language and runtime

在本页中,我们不断提到某些功能是语言级的,而其他功能是运行时级的。

¥Throughout this page, we've constantly mentioned that certain features are language-level while others are runtime-level.

JavaScript 是一种通用脚本语言。核心语言规范 专注于纯计算逻辑。它不处理任何输入/输出 - 事实上,如果没有额外的运行时级 API(尤其是 console.log()),JavaScript 程序的行为是完全不可观察的。

¥JavaScript is a general-purpose scripting language. The core language specification focuses on pure computational logic. It doesn't deal with any input/output — in fact, without extra runtime-level APIs (most notably console.log()), a JavaScript program's behavior is entirely unobservable.

运行时或主机是向 JavaScript 引擎(解释器)提供数据、提供额外的全局属性并为引擎提供与外界交互的钩子的东西。模块解析、读取数据、打印消息、发送网络请求等都是运行时级别的操作。JavaScript 自诞生以来,已经在各种环境中得到采用,比如浏览器(提供了 DOM 之类的 API)、Node.js(提供了 文件系统访问 之类的 API)等。JavaScript 已经成功地集成到了 Web 中(这是它的主要目的) 、移动应用、桌面应用、服务器端应用、无服务器、嵌入式系统等。当你学习 JavaScript 核心功能时,了解主机提供的功能也很重要,以便运用知识。例如,你可以阅读所有 网络平台 API,它们是由浏览器实现的,有时是非浏览器实现的。

¥A runtime, or a host, is something that feeds data to the JavaScript engine (the interpreter), provides extra global properties, and provides hooks for the engine to interact with the outside world. Module resolution, reading data, printing messages, sending network requests, etc. are all runtime-level operations. Since its inception, JavaScript has been adopted in various environments, such as browsers (which provide APIs like DOM), Node.js (which provides APIs like file system access), etc. JavaScript has been successfully integrated in web (which was its primary purpose), mobile apps, desktop apps, server-side apps, serverless, embedded systems, and more. While you learn about JavaScript core features, it's also important to understand host-provided features in order to put the knowledge to use. For example, you can read about all web platform APIs, which are implemented by browsers, and sometimes non-browsers.

进一步探索

¥Further exploration

此页面提供了有关各种 JavaScript 功能与其他语言的比较的非常基本的见解。如果你想了解更多关于语言本身以及每个功能的细微差别,你可以阅读 JavaScript 指南JavaScript 参考

¥This page offers a very basic insight into how various JavaScript features compare with other languages. If you want to learn more about the language itself and the nuances of each feature, you can read the JavaScript guide and the JavaScript reference.

由于篇幅和复杂性,我们省略了该语言的一些重要部分,但你可以自行探索:

¥There are some essential parts of the language that we have omitted due to space and complexity, but you can explore on your own: