function* - JavaScript

function\*
==========

**`function*`** 这种声明方式 (`function`关键字后跟一个星号）会定义一个**生成器函数** (generator function)，它返回一个 `Generator` 对象。

尝试一下
----

你也可以使用构造函数 `GeneratorFunction` 或 `function* expression` 定义\*\**生成器函数* \*\*。

语法
--
```

function* name([param[, param[, ... param]]]) { statements }
```

`name`

函数名

`param`

要传递给函数的一个参数的名称，一个函数最多可以有 255 个参数。

`statements`

普通 JS 语句。

描述
--

**生成器函数**在执行时能暂停，后面又能从暂停处继续执行。

调用一个**生成器函数**并不会马上执行它里面的语句，而是返回一个这个生成器的 **迭代器** **（ iterator ）对象**。当这个迭代器的 `next()` 方法被首次（后续）调用时，其内的语句会执行到第一个（后续）出现`yield`的位置为止，`yield` 后紧跟迭代器要返回的值。或者如果用的是 `yield*`（多了个星号），则表示将执行权移交给另一个生成器函数（当前生成器暂停执行）。

`next()`方法返回一个对象，这个对象包含两个属性：value 和 done，value 属性表示本次 `yield` 表达式的返回值，done 属性为布尔类型，表示生成器后续是否还有 `yield` 语句，即生成器函数是否已经执行完毕并返回。

调用 `next()`方法时，如果传入了参数，那么这个参数会传给**上一条执行的 yield 语句左边的变量**，例如下面例子中的`x`：

```js
function\* gen() {
  yield 10;
  x = yield "foo";
  yield x;
}

var gen_obj = gen();
console.log(gen_obj.next()); // 执行 yield 10，返回 10
console.log(gen_obj.next()); // 执行 yield 'foo'，返回 'foo'
console.log(gen_obj.next(100)); // 将 100 赋给上一条 yield 'foo' 的左值，即执行 x=100，返回 100
console.log(gen_obj.next()); // 执行完毕，value 为 undefined，done 为 true
```

当在生成器函数中显式 `return` 时，会导致生成器立即变为完成状态，即调用 `next()` 方法返回的对象的 `done` 为 `true`。如果 `return` 后面跟了一个值，那么这个值会作为**当前**调用 `next()` 方法返回的 value 值。

示例
--

### 简单示例

```js
function\* idMaker() {
  var index = 0;
  while (index < 3) yield index++;
}

var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
```

### 生成器也可以接收参数：

```js
function\* idMaker() {
  var index = arguments[0] || 0;
  while (true) yield index++;
}

var gen = idMaker(5);
console.log(gen.next().value); // 5
console.log(gen.next().value); // 6
```

### yield\* 的示例

```js
function\* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function\* generator(i) {
  yield i;
  yield\* anotherGenerator(i); // 移交执行权
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
```

### 传递参数

```js
function\* createIterator() {
  let first = yield 1;
  let second = yield first + 2; // 4 + 2
  // first =4 是 next(4) 将参数赋给上一条的
  yield second + 3; // 5 + 3
}

let iterator = createIterator();

console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
```

### 显式返回

```js
function\* yieldAndReturn() {
  yield "Y";
  return "R"; //显式返回处，可以观察到 done 也立即变为了 true
  yield "unreachable"; // 不会被执行了
}

var gen = yieldAndReturn();
console.log(gen.next()); // { value: "Y", done: false }
console.log(gen.next()); // { value: "R", done: true }
console.log(gen.next()); // { value: undefined, done: true }
```

### 生成器函数不能当构造器使用

```js
function\* f() {}
var obj = new f(); // throws "TypeError: f is not a constructor"
```

### 使用迭代器遍历二维数组并转换成一维数组：

```js
function\* iterArr(arr) {
  //迭代器返回一个迭代器对象
  if (Array.isArray(arr)) {
    // 内节点
    for (let i = 0; i < arr.length; i++) {
      yield\* iterArr(arr[i]); // (\*) 递归
    }
  } else {
    // 离开
    yield arr;
  }
}
// 使用 for-of 遍历：
var arr = ["a", ["b", "c"], ["d", "e"]];
for (var x of iterArr(arr)) {
  console.log(x); // a b c d e
}
// 或者直接将迭代器展开：
var arr = ["a", ["b", ["c", ["d", "e"]]]];
var gen = iterArr(arr);
arr = [...gen]; // ["a", "b", "c", "d", "e"]
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-generator-function-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 函数指南
* 迭代器与生成器指南
* Function
* `GeneratorFunction`
* `function*` 表达式
* `function`
* `async function`
* `async function*`
* 迭代器协议
* `yield`
* `yield*`
* `Generator`
* GitHub 上的 Regenerator
* Promise 和生成器：控制流的乌托邦——Forbes Lindesay 在 JSConf 上的演讲（2013）
* GitHub 上的 Task.js
* 你不知道的 JS：异步和性能，第 4 章：生成器——Kyle Simpson


throw - JavaScript

throw
=====

**`throw`** **语句**用来抛出一个用户自定义的异常。当前函数的执行将被停止（`throw` 之后的语句将不会执行），并且控制将被传递到调用堆栈中的第一个 `catch` 块。如果调用者函数中没有 `catch` 块，程序将会终止。

尝试一下
----

语法
--
```

throw expression;
```

`expression`

要抛出的表达式。

描述
--

使用`throw`语句来抛出一个异常。当你抛出异常时，`expression` 指定了异常的内容。下面的每行都抛出了一个异常：

```js
throw "Error2"; // 抛出了一个值为字符串的异常
throw 42; // 抛出了一个值为整数 42 的异常
throw true; // 抛出了一个值为 true 的异常
```

注意`throw`语句同样受到自动分号插入（ASI）机制的控制，在`throw`关键字和值之间不允许有行终止符。

示例
--

### 抛出一个对象

你可以在抛出异常时指定一个对象。然后可以在`catch`块中引用对象的属性。以下示例创建一个类型为`UserException`的对象，并在`throw`语句中使用它。

```js
function UserException(message) {
  this.message = message;
  this.name = "UserException";
}
function getMonthName(mo) {
  mo = mo - 1; // 调整月份数字到数组索引 (1=Jan, 12=Dec)
  var months = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
  ];
  if (months[mo] !== undefined) {
    return months[mo];
  } else {
    throw new UserException("InvalidMonthNo");
  }
}

try {
  // statements to try
  var myMonth = 15; // 15 超出边界并引发异常
  var monthName = getMonthName(myMonth);
} catch (e) {
  var monthName = "unknown";
  console.log(e.message, e.name); // 传递异常对象到错误处理
}
```

### 另一个抛出异常对象的示例

下面的示例中测试一个字符串是否是美国邮政编码。如果邮政编码是无效的，那么`throw`语句将会抛出一个类型为 `ZipCodeFormatException`的异常对象实例。

```js
/\*
 \* 创建 ZipCode 示例。
 \*
 \* 可被接受的邮政编码格式：
 \* 12345
 \* 12345-6789
 \* 123456789
 \* 12345 6789
 \*
 \* 如果构造函数参数传入的格式不符合以上任何一个格式，将会抛出异常。
 \*/

function ZipCode(zip) {
  zip = new String(zip);
  pattern = /[0-9]{5}([- ]?[0-9]{4})?/;
  if (pattern.test(zip)) {
    // zip code value will be the first match in the string
    this.value = zip.match(pattern)[0];
    this.valueOf = function () {
      return this.value;
    };
    this.toString = function () {
      return String(this.value);
    };
  } else {
    throw new ZipCodeFormatException(zip);
  }
}

function ZipCodeFormatException(value) {
  this.value = value;
  this.message = "不是正确的邮政编码";
  this.toString = function () {
    return this.value + this.message;
  };
}

/\*
 \* 这可能是一个验证美国地区中的脚本
 \*/

const ZIPCODE\_INVALID = -1;
const ZIPCODE\_UNKNOWN\_ERROR = -2;

function verifyZipCode(z) {
  try {
    z = new ZipCode(z);
  } catch (e) {
    if (e instanceof ZipCodeFormatException) {
      return ZIPCODE\_INVALID;
    } else {
      return ZIPCODE\_UNKNOWN\_ERROR;
    }
  }
  return z;
}

a = verifyZipCode(95060); // 返回 95060
b = verifyZipCode(9560); // 返回 -1
c = verifyZipCode("a"); // 返回 -1
d = verifyZipCode("95060"); // 返回 95060
e = verifyZipCode("95060 1234"); // 返回 95060 1234
```

### 重新抛出异常

你可以使用`throw`来抛出异常。下面的例子捕捉了一个异常值为数字的异常，并在其值大于 50 后重新抛出异常。重新抛出的异常传播到闭包函数或顶层，以便用户看到它。

```js
try {
  throw n; // 抛出一个数值异常
} catch (e) {
  if (e <= 50) {
    // 异常在 1-50 之间时，直接处理
  } else {
    // 异常无法处理，重新抛出
    throw e;
  }
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-throw-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `try...catch`
* `Error`


表达式语句 - JavaScript

表达式语句
=====

**表达式语句**是指在需要语句的地方使用的表达式。该表达式被执行后，它的结果将被丢弃——因此，它仅对有着副作用的表达式有意义，例如执行函数或者更新变量。

语法
--

```js
expression;
```

`expression`

要计算的任意表达式。有些表达式可能与其他语句产生歧义，因此被禁用。

描述
--

除了专用的语句语法外，你也可以将几乎任何表达式用作单独的语句。表达式语句语法需要在表达式的结尾添加一个分号，但是如果缺少分号导致语法无效，自动分号补全的过程可能会为你插入一个分号。

因为表达式执行之后就会被丢弃，表达式的结果不再可用。因此，表达式必须有一些副作用才能有用。表达式语句通常是：

* 函数调用（`console.log("Hello");`、`[1, 2, 3].forEach((i) => console.log(i));`）
* 带标签的模版字符串
* 赋值表达式，包括复合赋值
* 自增和自减
* `delete`
* `import()` (en-US)
* `yield` 和 `yield*`

如果表达式调用 getter 或者触发强制类型转换，它们也可能有副作用。

### 被禁用的表达式

为了将表达式用作语句，它禁止与其他的语句语法有歧义。因此，表达式禁止以以下任何标记开头：

* `function`：这将是 `function` 声明或者 `function*` 声明，而不是 `function` 表达式或者 `function*` 表达式
* `async function`：这将是 `async function` 声明或者 `async function*` 声明，而不是 `async function` 表达式或者 `async function*` 表达式
* `class`：这将是 `class` 声明，而不是 `class` 表达式
* `let[`：这将是带有数组解构的 `let` 声明，而不是称为 `let` 变量的属性访问器（`let` 仅可以是在非严格模式下的标识符）
* `{`：这将是一个块语句，而不是对象字面量

因此，以下所有内容均无效：

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

// 出于某种原因，你有一个叫做 `let` 的变量
var let = [1, 2, 3];
let[0] = 4; // SyntaxError: Invalid destructuring assignment target

{
  foo: 1,
  bar: 2, // SyntaxError: Unexpected token ':'
};
```

更危险的是，有时候代码碰巧是有效的语法，但是这并不是你想要的。

```js
// 出于某种原因，你有一个叫做 `let` 的变量
var let = [1, 2, 3];

function setIndex(index, value) {
  if (index >= 0) {
    // 打算分配给数组 `let`，但是却创建一个额外的变量！
    let[index] = value;
  }
}

setIndex(0, [1, 2]);
console.log(let); // [1, 2, 3]

// 这并不是一个对象字面量，而是一个块语句。
// 其中 `foo` 是标签，`1` 是表达式语句。
// 这通常在控制台中发生
{ foo: 1 };
```

为了避免这些问题，你可以使用圆括号，使语句明确地作为一个表达式。

```js
(function foo() {
  console.log("foo");
})();
```

示例
--

### 避免控制流语句

你可以使用表达式语句来避免几乎所有控制流语句的使用。例如，可以使用三元运算符和逻辑运算符替换 `if...else`。像 `for` 或者 `for...of` 可以使用数组方法来替换。

```js
// 使用控制流语句
function range(start, end) {
  if (start > end) {
    [start, end] = [end, start];
  }
  const result = [];
  for (let i = start; i < end; i++) {
    result.push(i);
  }
  return result;
}

// 使用表达式语句
function range2(start, end) {
  start > end && ([start, end] = [end, start]);
  return Array.from({ length: end - start }, (\_, i) => start + i);
}
```

**警告：** 这仅演示了该语言的功能。过度的使用表达式语句来替代控制流语句会大大降低代码的可读性。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-expression-statement |

参见
--

* 语句和声明
* 表达式和运算符


for await...of - JavaScript

for await...of
==============

**`for await...of` 语句**创建一个循环，该循环遍历异步可迭代对象以及同步可迭代对象，包括：内置的 `String`, `Array`，类似数组对象 (例如 `arguments` 或 `NodeList`)，`TypedArray`, `Map`, `Set` 和用户定义的异步/同步迭代器。它使用对象的每个不同属性的值调用要执行的语句来调用自定义迭代钩子。

类似于 `await` 运算符一样，该语句只能在一个async function 内部使用。

**备注：** `for await...of` 不适用于不是异步可迭代的异步迭代器。

语法
--
```

for await (variable of iterable) {
  statement
}
```

`variable`

在每次迭代中，将不同属性的值分配给变量。变量有可能以`const`, `let`, 或者 `var`来声明。

`iterable`

被迭代枚举其属性的对象。与 for...of 相比，这里的对象可以返回 `Promise`，如果是这样，那么 `variable` 将是 `Promise` 所包含的值，否则是值本身。

示例
--

### 迭代异步可迭代对象

你还可以迭代一个明确实现异步迭代协议的对象：

```js
var asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return Promise.resolve({ value: this.i++, done: false });
        }

        return Promise.resolve({ done: true });
      },
    };
  },
};

(async function () {
  for await (num of asyncIterable) {
    console.log(num);
  }
})();

// 0
// 1
// 2
```

### 迭代异步生成器

异步生成器已经实现了异步迭代器协议，所以可以用 `for await...of`循环。

```js
async function\* asyncGenerator() {
  var i = 0;
  while (i < 3) {
    yield i++;
  }
}

(async function () {
  for await (num of asyncGenerator()) {
    console.log(num);
  }
})();
// 0
// 1
// 2
```

有关使用`for await... of`考虑迭代 API 中获取数据的异步 generator 更具体的例子。这个例子首先为一个数据流创建了一个异步 generator，然后使用它来获得这个 API 的响应值的大小。

```js
async function\* streamAsyncIterator(stream) {
  const reader = stream.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        return;
      }
      yield value;
    }
  } finally {
    reader.releaseLock();
  }
}
// 从 url 获取数据并使用异步 generator 来计算响应值的大小
async function getResponseSize(url) {
  const response = await fetch(url);
  // Will hold the size of the response, in bytes.
  let responseSize = 0;
  // 使用 for-await-of 循环。异步 generator 会遍历响应值的每一部分
  for await (const chunk of streamAsyncIterator(response.body)) {
    // Incrementing the total response length.
    responseSize += chunk.length;
  }

  console.log(`Response Size: ${responseSize} bytes`);
  // expected output: "Response Size: 1071472"
  return responseSize;
}
getResponseSize("https://jsonplaceholder.typicode.com/photos");
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-for-in-and-for-of-statements |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `Symbol.asyncIterator`
* `Statements/for...of`


try...catch - JavaScript

try...catch
===========

**`try...catch`** 语句标记要尝试的语句块，并指定一个出现异常时抛出的响应。

尝试一下
----

语法
--
```

try {
   try_statements
}
[catch (exception_var_1 if condition_1) { // non-standard
   catch_statements_1
}]
...
[catch (exception_var_2) {
   catch_statements_2
}]
[finally {
   finally_statements
}]
```

`try_statements`

需要被执行的语句。

`catch_statements_1`, `catch_statements_2`

如果在`try`块里有异常被抛出时执行的语句。

`exception_var_1`, `exception_var_2`

用于保存关联`catch`子句的异常对象的标识符。

`condition_1`

一个条件表达式。

`finally_statements`

在`try`语句块之后执行的语句块。无论是否有异常抛出或捕获这些语句都将执行。

描述
--

`try`语句包含了由一个或者多个语句组成的`try`块，和至少一个`catch`块或者一个`finally`块的其中一个，或者两个兼有，下面是三种形式的`try`声明：

1. `try...catch`
2. `try...finally`
3. `try...catch...finally`

`catch`子句包含`try`块中抛出异常时要执行的语句。也就是，你想让`try`语句中的内容成功，如果没成功，你想控制接下来发生的事情，这时你可以在`catch`语句中实现。如果在`try`块中有任何一个语句（或者从`try`块中调用的函数）抛出异常，控制立即转向`catch`子句。如果在`try`块中没有异常抛出，会跳过`catch`子句。

`finally`子句在`try`块和`catch`块之后执行但是在下一个`try`声明之前执行。无论是否有异常抛出或捕获它总是执行。

你可以嵌套一个或者更多的`try`语句。如果内部的`try`语句没有`catch`子句，那么将会进入包裹它的`try`语句的`catch`子句。

你也可以用`try`语句去处理 JavaScript 异常。参考JavaScript 指南了解更多关于 Javascript 异常的信息。

### 无条件的 `catch` 块

当使用单个无条件`catch`子句时，抛出的任何异常时都会进入到`catch`块。例如，当在下面的代码中发生异常时，控制转移到`catch`子句。

```js
try {
  throw "myException"; // generates an exception
} catch (e) {
  // statements to handle any exceptions
  logMyErrors(e); // pass exception object to error handler
}
```

`catch`块指定一个标识符（在上面的示例中为 e），该标识符保存由`throw`语句指定的值。`catch`块是唯一的，因为当输入`catch`块时，JavaScript 会创建此标识符，并将其添加到当前作用域；标识符仅在`catch`块执行时存在；`catch`块执行完成后，标识符不再可用。

### 条件 `catch` 块

**非标准:** 该特性是非标准的，请尽量不要在生产环境中使用它！

你也可以用一个或者更多条件`catch`子句来处理特定的异常。在这种情况下，当异常抛出时将会进入合适的`catch`子句中。在下面的代码中，`try`块的代码可能会抛出三种异常：`TypeError`，`RangeError`和`EvalError`。当一个异常抛出时，控制将会进入与其对应的`catch`语句。如果这个异常不是特定的，那么控制将转移到无条件`catch`子句。

当用一个无条件`catch`子句和一个或多个条件语句时，无条件`catch`子句必须放在最后。否则当到达条件语句之前所有的异常将会被非条件语句拦截。

提醒：这个功能不符合 ECMAscript 规范。

```js
try {
    myroutine(); // may throw three types of exceptions
} catch (e if e instanceof TypeError) {
    // statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
    // statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
    // statements to handle EvalError exceptions
} catch (e) {
    // statements to handle any unspecified exceptions
    logMyErrors(e); // pass exception object to error handler
}
```

下面用符合 ECMAscript 规范的简单的 JavaScript 来编写相同的“条件 catch 子句”（显然更加冗长的，但是可以在任何地方运行）：

```js
try {
  myRoutine();
} catch (e) {
  if (e instanceof RangeError) {
    // statements to handle this very common expected error
  } else {
    throw e; // re-throw the error unchanged
  }
}
```

### 异常标识符

当`try`块中的抛出一个异常时，`exception_var`（如`catch (e)`中的`e`）用来保存被抛出声明指定的值。你可以用这个标识符来获取关于被抛出异常的信息。

这个标识符是`catch`子语句内部的。换言之，当进入`catch`子语句时标识符创建，`catch`子语句执行完毕后，这个标识符将不再可用。

```js
function isValidJSON(text) {
  try {
    JSON.parse(text);
    return true;
  } catch {
    return false;
  }
}
```

### `finally` 块

`finally` 块包含的语句会在 `try` 块和 `catch` 块执行之后、及 `try...catch...finally` 块后面的语句执行之前执行。控制流始终会进入 `finally` 块，可以通过以下方式之一执行：

* `try` 块正常执行结束后（没有抛出异常）立即执行；
* 在 `catch` 块正常执行完毕后立即执行；
* 紧接着在 `try` 代码块或 `catch` 代码块中的控制流语句（`return`、`throw`、`break`、`continue`）执行之前执行。

如果 `try` 代码块抛出异常，即使没有 `catch` 代码块来处理异常，`finally` 代码块仍会执行，在这种情况下，异常仍会在 `finally` 代码块执行完毕后立即抛出。

下面的示例展示了 `finally` 代码块的一种使用情况。代码先打开一个文件，然后执行使用该文件的语句；即使出现异常，`finally` 块也会确保文件在使用后始终关闭。

```js
openMyFile();
try {
  // tie up a resource
  writeMyFile(theData);
} finally {
  closeMyFile(); // always close the resource
}
```

示例
--

### 嵌套 try 块

首先让我们看看这里发生什么：

```js
try {
  try {
    throw new Error("oops");
  } finally {
    console.log("finally");
  }
} catch (ex) {
  console.error("outer", ex.message);
}

// Output:
// "finally"
// "outer" "oops"
```

现在，如果我们已经在 try 语句中，通过增加一个 catch 语句块捕获了异常

```js
try {
  try {
    throw new Error("oops");
  } catch (ex) {
    console.error("inner", ex.message);
  } finally {
    console.log("finally");
  }
} catch (ex) {
  console.error("outer", ex.message);
}

// Output:
// "inner" "oops"
// "finally"
```

现在，让我们再次抛出错误。

```js
try {
  try {
    throw new Error("oops");
  } catch (ex) {
    console.error("inner", ex.message);
    throw ex;
  } finally {
    console.log("finally");
  }
} catch (ex) {
  console.error("outer", ex.message);
}

// Output:
// "inner" "oops"
// "finally"
// "outer" "oops"
```

任何给定的异常只会被离它最近的封闭 catch 块捕获一次。当然，在“inner”块抛出的任何新异常（因为 catch 块里的代码也可以抛出异常），将会被“outer”块所捕获。

### 从 finally 语句块返回

如果从`finally`块中返回一个值，那么这个值将会成为整个`try-catch-finally`的返回值，无论是否有`return`语句在`try`和`catch`中。这包括在`catch`块里抛出的异常。

```js
try {
  try {
    throw new Error("oops");
  } catch (ex) {
    console.error("inner", ex.message);
    throw ex;
  } finally {
    console.log("finally");
    return;
  }
} catch (ex) {
  console.error("outer", ex.message);
}

// 注：此 try catch 语句需要在 function 中运行才能作为函数的返回值，否则直接运行会报语法错误
// Output:
// "inner" "oops"
// "finally"
```

因为 finally 块里的 return 语句，"oops" 没有抛出到外层，从 catch 块返回的值同样适用。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-try-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `Error`
* `throw`


import - JavaScript

import
======

静态 **`import`** 语句用于导入由另一个模块导出的绑定。无论是否声明了 `strict mode`，导入的模块都运行在严格模式下。在浏览器中，`import` 语句只能在声明了 `type="module"` 的 `script` 的标签中使用。

此外，还有一个类似函数的动态 `import()`，它不需要依赖 `type="module"` 的 script 标签。

在 script 标签中使用 `nomodule` 属性，可以确保向后兼容。

在你希望按照一定的条件或者按需加载模块的时候，动态 `import()` 是非常有用的。而静态型的 `import` 是初始化加载依赖项的最优选择，使用静态 `import` 更容易从代码静态分析工具和 tree shaking 中受益。

语法
--

```js
import defaultExport from "module-name";
import \* as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /\* … \*/ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /\* … \*/ } from "module-name";
import defaultExport, \* as name from "module-name";
import "module-name";
```

`defaultExport`

导入模块的默认导出接口的引用名。

`module-name`

要导入的模块。访问修饰符与主机相关。其通常是包含目标模块的 `.js` 文件的相对或绝对路径名。在 Node 中，通常在引用 `node_modules` 中的包时使用不添加扩展名的导入。某些特定的打包工具可能允许不添加扩展名的导入模块方式，请检查你的环境以确定导入方式。模块名只允许单引号和双引号的字符串。

`name`

导入模块对象整体的别名，在引用导入模块时，它将作为一个命名空间来使用。

`exportN`

被导入模块的导出接口的名称。

`aliasN`

将引用指定的导入的名称。必须是有效的 JavaScript 标识符。

描述
--

`name` 参数是“导入模块对象”的名称，它将用一种名称空间来引用导入模块的接口。export 参数指定单个的命名导出，而 `import * as name` 语法导入所有导出接口，即导入模块整体。以下示例阐明该语法。

### 导入整个模块的内容

这将`myModule`插入当前作用域，其中包含来自位于`/modules/my-module.js`文件中导出的所有接口。

```js
import \* as myModule from "/modules/my-module.js";
```

在这里，访问导出接口意味着使用模块名称（在本例为“myModule”）作为命名空间。例如，如果上面导入的模块包含一个接口 `doAllTheAmazingThings()`，你可以这样调用：

```js
myModule.doAllTheAmazingThings();
```

### 导入单个接口

给定一个名为 `myExport` 的对象或值，它已经从模块 `my-module` 导出（因为整个模块被导出）或显式地导出（使用 `export` 语句），将 `myExport` 插入当前作用域。

```js
import { myExport } from "/modules/my-module.js";
```

### 导入多个接口

这将 `foo` 和 `bar` 插入当前作用域。

```js
import { foo, bar } from "/modules/my-module.js";
```

### 导入带有别名的接口

你可以在导入时重命名接口。例如，将 `shortName` 插入当前作用域。

```js
import { reallyReallyLongModuleExportName as shortName } from "/modules/my-module.js";
```

### 导入时重命名多个接口

使用别名导入模块的多个接口。

```js
import {
  reallyReallyLongModuleMemberName as shortName,
  anotherLongModuleName as short,
} from "/modules/my-module.js";
```

### 仅为副作用而导入一个模块

整个模块仅为副作用（中性词，无贬义含义）而导入，而不导入模块中的任何内容（接口）。这将运行模块中的全局代码，但实际上不导入任何值。

```js
import "/modules/my-module.js";
```

### 导入默认值

引入模块可能有一个 `default` `export`（无论它是对象，函数，类等）可用。然后可以使用 `import` 语句来导入这样的默认接口。

最简单的用法是直接导入默认值：

```js
import myDefault from "/modules/my-module.js";
```

也可以同时将 `default` 语法与上述用法（命名空间导入或命名导入）一起使用。在这种情况下，`default` 导入必须首先声明。例如：

```js
import myDefault, \* as myModule from "/modules/my-module.js";
// myModule used as a namespace
```

或者

```js
import myDefault, { foo, bar } from "/modules/my-module.js";
// specific, named imports
```

当用动态导入的方式导入默认导出时，其工作方式有所不同。你需要从返回的对象中解构并重命名 "default" 键。

```js
(async () => {
  if (somethingIsTrue) {
    const {
      default: myDefault,
      foo,
      bar,
    } = await import("/modules/my-module.js");
  }
})();
```

### 动态 import

标准用法的 import 导入的模块是静态的，会使所有被导入的模块，在加载时就被编译（无法做到按需编译，降低首页加载速度）。有些场景中，你可能希望根据条件导入模块或者按需导入模块，这时你可以使用动态导入代替静态导入。下面的是你可能会需要动态导入的场景：

* 当静态导入的模块很明显的降低了代码的加载速度且被使用的可能性很低，或者并不需要马上使用它。
* 当静态导入的模块很明显的占用了大量系统内存且被使用的可能性很低。
* 当被导入的模块，在加载时并不存在，需要异步获取。
* 当导入模块的说明符，需要动态构建。（静态导入只能使用静态说明符）
* 当被导入的模块有副作用（这里说的副作用，可以理解为模块中会直接运行的代码），这些副作用只有在触发了某些条件才被需要时。（原则上来说，模块不能有副作用，但是很多时候，你无法控制你所依赖的模块的内容）

请不要滥用动态导入（只有在必要情况下采用）。静态框架能更好的初始化依赖，而且更有利于静态分析工具和 tree shaking 发挥作用。

关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用，将返回一个 `promise`。

```js
import("/modules/my-module.js").then((module) => {
  // Do something with the module.
});
```

这种使用方式也支持 `await` 关键字。

```js
let module = await import("/modules/my-module.js");
```

示例
--

### 标准导入

下面的代码将会演示如何从辅助模块导入以协助处理 AJAX JSON 请求。

#### 模块：file.js

```js
function getJSON(url, callback) {
  let xhr = new XMLHttpRequest();
  xhr.onload = function () {
    callback(this.responseText);
  };
  xhr.open("GET", url, true);
  xhr.send();
}

export function getUsefulContents(url, callback) {
  getJSON(url, (data) => callback(JSON.parse(data)));
}
```

#### 主程序：main.js

```js
import { getUsefulContents } from "/modules/file.js";

getUsefulContents("http://www.example.com", (data) => {
  doSomethingUseful(data);
});
```

### 动态导入

此示例展示了如何基于用户操作去加载功能模块到页面上，在例子中通过点击按钮，然后会调用模块内的函数。当然这不是能实现这个功能的唯一方式，`import()` 函数也可以支持 `await`。

```js
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
  link.addEventListener("click", (e) => {
    e.preventDefault();

    import("/modules/my-module.js")
      .then((module) => {
        module.loadPageInto(main);
      })
      .catch((err) => {
        main.textContent = err.message;
      });
  });
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-imports |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `export`
* `import()` (en-US)
* `import.meta`
* 预览 ES6 模块以及 ES2015、ES2016 和更多内容，载于 blogs.windows.com (2016)
* ES6 深入浅出：模块，载于 hacks.mozilla.org (2015)
* 深入挖掘 ES 模块，载于 hacks.mozilla.org (2018)
* 探索 JS，第 16 章：模块，由 Dr. Axel Rauschmayer 撰写
* Export 和 Import，载于 javascript.info


while - JavaScript

while
=====

**while 语句**可以在某个条件表达式为真的前提下，循环执行指定的一段代码，直到那个表达式不为真时结束循环。

尝试一下
----

语法
--
```

while (condition)
  statement
```

`condition`

条件表达式，在每次循环前被求值。如果求值为真，`statement`就会被执行。如果求值为假，则跳出`while`循环执行后面的语句。

`statement`

只要条件表达式求值为真，该语句就会一直被执行。要在循环中执行多条语句，可以使用块语句（`{ ... }`）包住多条语句。注意：使用`break`语句在`condition`计算结果为真之前停止循环。

示例
--

下面的 `while` 循环会一直循环若干次，直到 `n` 等于 `3`。

```js
var n = 0;
var x = 0;

while (n < 3) {
  n++;
  x += n;
}
```

在每次循环中，`n` 都会自增 `1`，然后再把 `n` 加到 `x` 上。因此，在每轮循环结束后，`x` 和 `n` 的值分别是：

* 第一轮后：`n` = 1，`x` = 1
* 第二轮后：`n` = 2，`x` = 3
* 第三轮后：`n` = 3，`x` = 6

当完成第三轮循环后，条件表达式`n`< 3 不再为真，因此循环终止。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-while-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `do...while`
* `for`


break - JavaScript

break
=====

**break 语句**中止当前循环，`switch`语句或`label` 语句，并把程序控制流转到紧接着被中止语句后面的语句。

语法
--
```

break [label];
```

`label` 可选

与语句标签相关联的标识符。如果 break 语句不在一个循环或 `switch` 语句中，则该项是必须的。

描述
--

`break`语句包含一个可选的标签，可允许程序摆脱一个被标记的语句。`break`语句需要内嵌在引用的标签中。被标记的语句可以是任何 `块`语句；不一定是循环语句。

break 语句不能在 function 函数体中直接使用，break 语句应嵌套在要中断的当前循环、switch 或 label 语句中。

示例
--

### break in while loop

下面的函数里有个 `break` 语句，当 `i` 为 3 时，会中止 `while` 循环，然后返回 3 \* `x` 的值。

```js
function testBreak(x) {
  var i = 0;

  while (i < 6) {
    if (i == 3) {
      break;
    }
    i += 1;
  }

  return i \* x;
}
```

### break in switch statements

在下面的代码中， `break` 使用在 `switch` 语句中，当遇到匹配到 case 后，就会执行相应的代码并中断循环体。
```

const food = "sushi";

switch (food) {
  case "sushi":
    console.log("Sushi is originally from Japan.");
    break;
  case "pizza":
    console.log("Pizza is originally from Italy.");
    break;
  default:
    console.log("I have never heard of that dish.");
    break;
}
```

### break in labeled blocks

下面的代码中一起使用 `break` 语句和被标记的块语句。一个 `break` 语句必须内嵌在它引用的标记中。注意，`inner_block` 内嵌在 `outer_block` 中。

```js
outer\_block: {
  inner\_block: {
    console.log("1");
    break outer_block; // breaks out of both inner\_block and outer\_block
    console.log(":-("); // skipped
  }

  console.log("2"); // skipped
}
```

### break in labeled blocks that throw

下面的代码同样使用了 `break` 语句和被标记的块语句，但是产生了一个语法错误，因为它的 `break` 语句在 `block_1` 中，但是引用了 `block_2`。`break` 语句必须内嵌在它引用的标签中。

```js
block\_1:{
  console.log ('1');
  break block_2;            // SyntaxError: label not found
}

block\_2:{
  console.log ('2');
}
```

### break within functions

在下面的代码同样会产生 SyntaxError，因为它并没被正确的使用在循环、switch 或 label 语句中。
```

function testBreak(x) {
  var i = 0;

  while (i < 6) {
    if (i == 3) {
      (function() {
        break;
      })();
    }
    i += 1;
  }

return i * x;
}

testBreak(1); // SyntaxError: Illegal break statement
```

```
block_1: {
  console.log('1');
  ( function() {
    break block_1; // SyntaxError: Undefined label 'block_1'
  })();
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-break-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `continue`
* label
* `switch`


let - JavaScript

let
===

**`let`** 声明用于声明可重新赋值的块级作用域局部变量，并且可以将每个变量初始化为一个值（可选）。

尝试一下
----

语法
--

```js
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /\* …, \*/ nameN = valueN;
```

### 参数

`nameN`

要声明的变量的名称，必须是合法的 JavaScript 标识符或解构绑定模板。

`valueN` 可选

变量的初始值，可以是任意合法的表达式。默认值为 `undefined`。

描述
--

用 `let` 声明的变量的作用域是最靠近并包含 `let` 声明的以下花括号闭合语法结构的一个：

* 块语句
* `switch` 语句
* `try...catch` 语句
* `let` 位于其开头的 `for` 语句之一的主体
* 函数主体
* 类静态初始化块 (en-US)

如果没有被这些结构包含，则是当前模块或脚本的顶级作用域。

相较于 `var`，`let` 声明有以下不同点：

* `let` 声明的作用域是块或函数。
* `let` 声明的变量只能在执行到声明所在的位置之后才能被访问（参见暂时性死区）。因此，`let` 声明通常被视为是非提升的。
* `let` 声明在脚本的顶级作用域上声明变量时不会在全局对象上创建属性。
* `let` 声明的变量不能被同一个作用域中的任何其他声明重复声明。
* `let` 是*声明*，而不是*语句*的开头。这意味着，你不能将单独的 `let` 声明当做块的主体使用（因为这样做会让变量无法被访问）。

```js
if (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
```

注意：在非严格模式下允许将 `let` 作为 `var` 或者 `function` 的标识符名称，但你应当避免将 `let` 用作标识符以防止发生不期望的语法混淆。

许多风格的教程（包括 MDN 的）推荐只要变量没有在其作用域中被重新赋值，就应该使用 `const` 而不是 `let`。这样能更清楚地表明变量的类型（或值，如果其为原始值）永远不会改变。此外也推荐用 `let` 存放可变的非原始值。

`let` 关键字后方的列表叫做*绑定* (en-US)*列表*，使用逗号分隔，其中的逗号*不是*逗号运算符，并且 `=` 符号也*不是*赋值运算符。后初始化的变量能够引用列表中之前初始化的变量。

### 暂时性死区

用 `let`、`const` 或 `class` 声明的变量可以称其从代码块的开始一直到代码执行到变量声明的位置并被初始化前，都处于一个“暂时性死区”（Temporal dead zone，TDZ）中。

当变量处于暂时性死区之中时，其尚未被初始化，并且任何访问其的尝试都将导致抛出 `ReferenceError`。当代码执行到变量被声明的位置时，变量会被初始化为一个值。如果变量声明中未指定初始值，则变量将被初始化为 `undefined`。

这与 `var` 声明的变量不同，如果在声明位置前访问 `var` 声明的变量会返回 `undefined`。以下代码演示了在声明位置前访问 `let` 和 `var` 声明的变量的不同结果。

```js
{
  // 暂时性死区始于作用域开头
  console.log(bar); // undefined
  console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
  var bar = 1;
  let foo = 2; // 暂时性死区结束（对 foo 而言）
}
```

使用“暂时性”一词是因为这个区域取决于代码执行的时间点，而不是代码编写的顺序。例如，下面的代码能够运行，是因为虽然使用 `let` 变量的函数写在变量声明之前，但函数是在暂时性死区外面被调用的。

```js
{
  // 暂时性死区始于作用域开头
  const func = () => console.log(letVar); // 没问题

  // 在暂时性死区内访问 letVar 会抛出 `ReferenceError`

  let letVar = 3; // 暂时性死区结束（对 letVar 而言）
  func(); // 在暂时性死区外调用
}
```

在暂时性死区内对 `let` 声明的变量使用 `typeof` 运算符也会抛出 `ReferenceError`：

```js
typeof i; // ReferenceError: Cannot access 'i' before initialization
let i = 10;
```

这与对未声明的变量和存放 `undefined` 值的变量使用 `typeof` 运算符不同：

```js
console.log(typeof undeclaredVariable); // "undefined"
```

### 重复声明

`let` 声明的变量不能被同一个作用域中的任何其他声明重复声明，包括 `let`、`const`、`class`、`function`、`var` 和 `import` 声明。

```js
{
  let foo;
  let foo; // SyntaxError: Identifier 'foo' has already been declared
}
```

在函数主体中用 `let` 声明的变量不能与参数同名，在 `catch` 块中用 `let` 声明的变量不能与被 `catch` 绑定的标识符同名。

```js
function foo(a) {
  let a = 1; // SyntaxError: Identifier 'a' has already been declared
}
try {
} catch (e) {
  let e; // SyntaxError: Identifier 'e' has already been declared
}
```

如果你在交互式解释器中实验，比如 Firefox web 控制台（**更多工具** > **Web 开发者工具** > **控制台**），当你分开两次输入并运行含有同一个名称的 `let` 声明时，你会得到同上的重复声明错误。有关更进一步的讨论参见这个议题：Firefox bug 1580891。Chrome 的控制台则允许在不同次的输入中重复声明 `let` 变量。

你或许会在 `switch` 语句中遇到错误，因为语句只有一个代码块。

```js
let x = 1;

switch (x) {
  case 0:
    let foo;
    break;
  case 1:
    let foo; // SyntaxError: Identifier 'foo' has already been declared
    break;
}
```

要避免这个错误，用新的块语句将每个 `case` 封闭。

```js
let x = 1;

switch (x) {
  case 0: {
    let foo;
    break;
  }
  case 1: {
    let foo;
    break;
  }
}
```

示例
--

### 作用域规则

`let` 声明的变量的作用域只在其声明的块或子块内部，在这一点上，它与 `var` 非常相似。二者之间主要的区别在于 `var` 声明的变量的作用域是整个闭合的函数。

```js
function varTest() {
  var x = 1;
  {
    var x = 2; // 同一个变量
    console.log(x); // 2
  }
  console.log(x); // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2; // 不同的变量
    console.log(x); // 2
  }
  console.log(x); // 1
}
```

在程序的顶级作用域和函数作用域中，`let` 则和 `var` 不一样，它不会在全局对象上创建属性。例如：

```js
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined
```

#### 暂时性死区和词法作用域

以下代码会导致 `ReferenceError`：

```js
function test() {
  var foo = 33;
  if (foo) {
    let foo = foo + 55; // ReferenceError
  }
}
test();
```

由于外部变量 `var foo` 有值，因此会执行 `if` 语句块，但是由于词法作用域，该值在块内不可用：`if` 块内的标识符 `foo` 是 `let foo`。表达式 `foo + 55` 会抛出 `ReferenceError` 异常，是因为 `let foo` 还没完成初始化——它仍处于暂时性死区内。

在下面的情况中，这种现象可能会使你感到困惑。指令 `let n of n.a` 已经处于 `for...of` 循环块的作用域内，因此，标识符 `n.a` 被解析为位于指令（`let n`）本身的 `n` 对象的属性 `a`。因为 `n` 的声明尚未执行结束，它仍然处于暂时性死区内。

```js
function go(n) {
  // n 在此处被定义
  console.log(n); // { a: [1, 2, 3] }

  for (let n of n.a) {
    // ^ ReferenceError
    console.log(n);
  }
}

go({ a: [1, 2, 3] });
```

### 其他情况

当 `let` 被用在块中时，`let` 将变量的作用域限制在块内。这不同于 `var`，`var` 的作用域为其被声明的函数内。

```js
var a = 1;
var b = 2;

{
  var a = 11; // 作用域为全局
  let b = 22; // 作用域为块内

  console.log(a); // 11
  console.log(b); // 22
}

console.log(a); // 11
console.log(b); // 2
```

然而，下面的 `var` 与 `let` 连携的声明方式会抛出 `SyntaxError`，因为 `var` 并不限制在块作用域内，导致它们处于同一作用域。这会导致隐式地重复声明变量。

```js
let x = 1;

{
  var x = 2; // 重复声明的 SyntaxError
}
```

### 带有解构的声明

每个 `=` 的左侧都可以是一个绑定模板，这使得能够一次性创建多个变量。

```js
const result = /(a+)(b+)(c+)/.exec("aaabcc");
let [, a, b, c] = result;
console.log(a, b, c); // "aaa" "b" "cc"
```

更多信息，参见解构赋值。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-let-and-const-declarations |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `var`
* `const`
* 变量提升
* 深入 ES6：`let` 和 `const`——hacks.mozilla.org（2015）
* Firefox 44 中 `let` 和 `const` 的破坏性变更——blog.mozilla.org（2015）
* 你不懂 JS：作用域和闭包 第三章：函数对决块作用域——Kyle Simpson
* 什么是暂时性死区？——Stack Overflow
* `let` 和 `var` 在用法上有什么不同？——Stack Overflow
* 为什么“let”被选为了 JavaScript 块作用域变量声明的名字？——Stack Overflow


block - JavaScript

block
=====

**块语句**（或其他语言的**复合语句**）用于组合零个或多个语句。该块由一对大括号界定，可以是`labelled`：

语法
--

### 块声明
```

{ StatementList }
```

### 标记块声明
```

LabelIdentifier: { StatementList }
```

`StatementList`

在块语句中分组的语句。

`LabelIdentifier`

用于视觉识别的可选`label`或`break`的目标。

描述
--

其他语言中通常将语句块称为**复合语句**。它允许你使用多个语句，其中 JavaScript 只需要一个语句。将语句组合成块是 JavaScript 中的常见做法。相反的做法是可以使用一个空语句，你不提供任何语句，虽然一个是必需的。

### 块级作用域

#### 在非严格模式 (non-strict mode) 下的`var` 或者函数声明时

通过`var`声明的变量或者非严格模式下 (non-strict mode) 创建的函数声明**没有**块级作用域。在语句块里声明的变量的作用域不仅是其所在的函数或者 script 标签内，所设置变量的影响会在超出语句块本身之外持续存在。换句话说，这种语句块不会引入一个作用域。尽管单独的语句块是合法的语句，但在 JavaScript 中你不会想使用单独的语句块，因为它们不像你想象的 C 或 Java 中的语句块那样处理事物。例如：

```js
var x = 1;
{
  var x = 2;
}
console.log(x); // 输出 2
```

输出结果是 2，因为块中的 `var x`语句与块前面的`var x`语句作用域相同。在 C 或 Java 中，这段代码会输出 1。

#### 使用`let`和 `const`

相比之下，使用 `let`和`const`声明的变量是**有**块级作用域的。

```js
let x = 1;
{
  let x = 2;
}
console.log(x); // 输出 1
```

`x = 2`仅限在定义它的块中。

`const`的结果也是一样的：

```js
const c = 1;
{
  const c = 2;
}
console.log(c); // 输出 1，而且不会报错
```

注意，位于块范围之内的 `const c = 2` 并不会抛出`SyntaxError: Identifier 'c' has already been declared`这样的语法错误，因为在它自己的块中它可能是唯一一个被声明的常量。

##### 使用 let 声明的变量在块级作用域内能强制执行更新变量，下面的两个例子对比：

```js
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 10
a[1](); // 10
a[6](); // 10

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 0
a[1](); // 1
a[6](); // 6
```

#### 使用`function`

函数声明同样被限制在声明他的语句块内：
```

foo('outside');  // TypeError: foo is not a function
{
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside'); // 正常工作并且打印 'foo is called inside'
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-block |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `while`
* `if...else`
* `let`


debugger - JavaScript

debugger
========

**`debugger` 语句**调用任何可用的调试功能，例如设置断点。如果没有调试功能可用，则此语句不起作用。

语法
--

```js
debugger;
```

示例
--

下面的例子演示了一个包含 `debugger` 语句的函数，当函数被调用时，会尝试调用一个可用的调试器进行调试。

```js
function potentiallyBuggyCode() {
  debugger;
  // do potentially buggy stuff to examine, step through, etc.
}
```

当 debugger 被调用时，执行暂停在 `debugger` 语句的位置。就像在脚本源代码中的断点一样。

![开发人员工具打开到调试器面板的浏览器，显示如何在调试器语句处暂停执行，以允许仔细检查变量、作用域、事件等。](/zh-CN/docs/Web/JavaScript/Reference/Statements/debugger/screen_shot_2014-02-07_at_9.14.35_am.png)

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-debugger-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 在火狐开发者工具中调试


async function* - JavaScript

async function\*
================

**`async function*`** 声明定义了一个*异步生成器函数*，其返回一个 `AsyncGenerator` 对象。

尝试一下
----

你也可以使用 `AsyncGeneratorFunction` 构造函数或者 `async function*` 表达式语法定义异步生成器函数。

语法
--

```js
async function\* name(param0) {
  statements
}
async function\* name(param0, param1) {
  statements
}
async function\* name(param0, param1, /\* … ,\*/ paramN) {
  statements
}
```

**备注：** 异步生成器函数没有对应的箭头函数。

### 参数

`name`

函数名。

`param` 可选

函数形式参数的名称。

`statements` 可选

构成函数体的语句。

描述
--

异步生成器函数兼具异步函数和生成器函数的特性。你可以在函数体中使用 `await` 和 `yield` 关键字。这使你能够使用 `await` 方便地处理异步任务，同时利用生成器函数的惰性。

与使用 `function*` 声明的标准生成器函数不同，异步生成器函数返回一个符合异步可迭代协议的 `AsyncGenerator` 对象。每次调用 `next()` 都会返回一个 `Promise`，该 promise 会兑现为迭代器结果对象。

当从异步生成器产生一个 promsie 时，迭代器结果 promise 的最终状态将与生成器产生的 promise 状态相同。例如：

```js
async function\* foo() {
  yield Promise.reject(1);
}

foo()
  .next()
  .catch((e) => console.error(e));
```

因为生成器内部生产的 promise 被拒绝，迭代器的结果也将被拒绝，所以将输出 `1`。异步生成器的兑现结果的 `value` 将不会是另一个 promise。

`async function*` 声明会被提升到其作用域的顶部，并可以在其作用域的任何地方调用。

示例
--

### 声明异步函数

异步生成器函数总是产生结果的 promise——即使每个 `yield` 步骤是同步的。

```js
async function\* myGenerator(step) {
  await new Promise((resolve) => setTimeout(resolve, 10));
  yield 0;
  yield step;
  yield step \* 2;
}

const gen = myGenerator(2);
gen
  .next()
  .then((res) => {
    console.log(res); // { value: 0, done: false }
    return gen.next();
  })
  .then((res) => {
    console.log(res); // { value: 2, done: false }
    return gen.next();
  })
  .then((res) => {
    console.log(res); // { value: 4, done: false }
    return gen.next();
  })
  .then((res) => {
    console.log(res); // { value: undefined, done: true }
    return gen.next();
  });
```

### 使用异步生成器函数去读一系列文件

在这个示例中，我们使用 Node 的 `fs/promises` 模块读取一系列文件并且仅当请求时获取它的内容。

```js
async function\* readFiles(directory) {
  const files = await fs.readdir(directory);
  for (const file of files) {
    const stats = await fs.stat(file);
    if (stats.isFile()) {
      yield {
        name: file,
        content: await fs.readFile(file, "utf8"),
      };
    }
  }
}

const files = readFiles(".");
console.log((await files.next()).value);
// 可能的输出；{ name: 'file1.txt', content: '...' }
console.log((await files.next()).value);
// 可能的输出：{ name: 'file2.txt', content: '...' }
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-async-generator-function-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `async function*` 表达式
* `AsyncGeneratorFunction` 对象
* 迭代器协议
* `GeneratorFunction` 对象
* `yield`
* `yield*`
* `Function` 对象
* 函数


export - JavaScript

export
======

在创建 JavaScript 模块时，**`export`** 语句用于从模块中导出实时绑定的函数、对象或原始值，以便其他程序可以通过 `import` 语句使用它们。被导出的绑定值依然可以在本地进行修改。在使用 import 进行导入时，这些绑定值只能被导入模块所读取，但在 export 导出模块中对这些绑定值进行修改，所修改的值也会实时地更新。

无论你是否声明，导出的模块都处于`严格模式`。export 语句不能用在嵌入式脚本中。

语法
--

存在两种 exports 导出方式：

1. 命名导出（每个模块包含任意数量）
2. 默认导出（每个模块包含一个）

```js
// 导出单个特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };

// 解构导出并重命名
export const { name1, name2: bar } = o;

// 默认导出
export default expression;
export default function (…) { … } // also class, function\*
export default function name1(…) { … } // also class, function\*
export { name1 as default, … };

// 导出模块合集
export \* from …; // does not set the default export
export \* as name1 from …; // Draft ECMAScript® 2O21
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
```

`nameN`

要导出的标识符（以便其他脚本通过 `import` 语句进行导入）.

描述
--

有两种不同的导出方式，命名导出和默认导出。你能够在每一个模块中定义多个命名导出，但是只允许有一个默认导出。每种方式对应于上述的一种语法：

命名导出：

```js
// 导出事先定义的特性
export { myFunction, myVariable };

// 导出单个特性（可以导出 var，let，
//const,function,class）
export let myVariable = Math.sqrt(2);
export function myFunction() { ... };
```

默认导出：

```js
// 导出事先定义的特性作为默认值
export { myFunction as default };

// 导出单个特性作为默认值
export default function () { ... }
export default class { .. }

// 每个导出都覆盖前一个导出
```

在导出多个值时，命名导出非常有用。在导入期间，必须使用相应对象的相同名称。

但是，可以使用任何名称导入默认导出，例如：

```js
// 文件 test.js
let k;
export default k = 12;
```

```js
// 另一个文件
import m from "./test"; // 由于 k 是默认导出，所以可以自由使用 import m 替代 import k
console.log(m); // 输出为 12
```

你也可以重命名命名导出以避免命名冲突：

```js
export { myFunction as function1, myVariable as variable };
```

### 重导出 / 聚合

为了使模块导入变得可用，在一个父模块中“导入/导出”这些不同模块也是可行的。也就是说，你可以创建单个模块，集中多个模块的多个导出。

这个可以使用“export from”语法实现：

```js
export { default as function1, function2 } from "bar.js";
```

与之形成对比的是联合使用导入和导出：

```js
import { default as function1, function2 } from "bar.js";
export { function1, function2 };
```

但这里的 `function1` 和 `function2` 在当前模块中变得不可用。

**备注：** 尽管与 import 等效，但以下语法在语法上无效：

```js
import DefaultExport from "bar.js"; // 有效的
```

```js
export DefaultExport from "bar.js"; // 无效的
```

这里正确的做法是重命名这个导出：

```js
export { default as DefaultExport } from "bar.js";
```

示例
--

### 使用命名导出

在模块 `my-module.js` 中，可能包含以下代码：

```js
// module "my-module.js"
function cube(x) {
  return x \* x \* x;
}

const foo = Math.PI + Math.SQRT2;

var graph = {
  options: {
    color: "white",
    thickness: "2px",
  },
  draw: function () {
    console.log("From graph draw function");
  },
};

export { cube, foo, graph };
```

然后，在你的 HTML 页面的顶级模块中：

```js
import { cube, foo, graph } from "my-module.js";

graph.options = {
  color: "blue",
  thickness: "3px",
};

graph.draw();
console.log(cube(3)); // 27
console.log(foo); // 4.555806215962888
```

着重注意以下几点：

* 在你的 HTML 中需要包含 type="module" 的 `<script>` 元素这样的脚本，以便它被识别为模块并正确处理
* 不能通过 `file://` URL 运行 JS 模块 — 这将导致 CORS 错误。你需要通过 HTTP 服务器运行。
### 使用默认导出

如果我们要导出一个值或得到模块中的返回值，就可以使用默认导出：

```js
// module "my-module.js"

export default function cube(x) {
  return x \* x \* x;
}
```

然后，在另一个脚本中，可以直接导入默认导出：

```js
import cube from "./my-module.js";
console.log(cube(3)); // 27
```

### 模块重定向

举个例子，假如我们有如下层次结构：

* `childModule1.js`: 导出 `myFunction` 和 `myVariable`
* `childModule2.js`: 导出 `myClass`
* `parentModule.js`: 作为聚合器（不做其他事情）
* 顶层模块：调用 `parentModule.js` 的导出项

你的代码看起来应该像这样：

```js
// childModule1.js 中
let myFunction = ...; // assign something useful to myFunction
let myVariable = ...; // assign something useful to myVariable
export {myFunction, myVariable};
```

```js
// childModule2.js 中
let myClass = ...; // assign something useful to myClass
export myClass;
```

```js
// parentModule.js 中
// 仅仅聚合 childModule1 和 childModule2 中的导出
// 以重新导出他们
export { myFunction, myVariable } from "childModule1.js";
export { myClass } from "childModule2.js";
```

```js
// 顶层模块中
// 我们可以从单个模块调用所有导出，因为 parentModule 事先
// 已经将他们“收集”/“打包”到一起
import { myFunction, myVariable, myClass } from "parentModule.js";
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-exports |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `import`
* JavaScript modules guide
* ES6 in Depth: Modules, Hacks blog post by Jason Orendorff
* ES modules: A cartoon deep-dive, Hacks blog post by Lin Clark
* Axel Rauschmayer's book: "Exploring JS: Modules"


for...in - JavaScript

for...in
========

**`for...in`** **语句**以任意顺序迭代一个对象的除Symbol以外的可枚举属性，包括继承的可枚举属性。

语法
--
```

for (variable in object)
  statement
```

`variable`

在每次迭代时，variable 会被赋值为不同的属性名。

`object`

非 Symbol 类型的可枚举属性被迭代的对象。

### 数组迭代和 `for...in`

**备注：** `for...in`不应该用于迭代一个关注索引顺序的 `Array`。

### 仅迭代自身的属性

如果你只要考虑对象本身的属性，而不是它的原型，那么使用 `getOwnPropertyNames()` 或执行 `hasOwnProperty()` 来确定某属性是否是对象本身的属性（也能使用`propertyIsEnumerable`）。或者，如果你知道不会有任何外部代码干扰，你可以使用检查方法扩展内置原型。

为什么用 for ... in?
----------------

`for ... in`是为遍历对象属性而构建的，不建议与数组一起使用，数组可以用`Array.prototype.forEach()`和`for ... of`，那么`for ... in`的到底有什么用呢？

它最常用的地方应该是用于调试，可以更方便的去检查对象属性（通过输出到控制台或其他方式）。尽管对于处理存储数据，数组更实用些，但是你在处理有`key-value`数据（比如属性用作“键”），需要检查其中的任何键是否为某值的情况时，还是推荐用`for ... in`。

示例
--

下面的函数接受一个对象作为参数。被调用时迭代传入对象的所有可枚举属性然后返回一个所有属性名和其对应值的字符串。

```js
var obj = { a: 1, b: 2, c: 3 };

for (var prop in obj) {
  console.log("obj." + prop + " = " + obj[prop]);
}

// Output:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"
```

下面的函数说明了`hasOwnProperty()`的用法：继承的属性不显示。

```js
var triangle = { a: 1, b: 2, c: 3 };

function ColoredTriangle() {
  this.color = "red";
}

ColoredTriangle.prototype = triangle;

var obj = new ColoredTriangle();

for (var prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  }
}

// Output:
// "obj.color = red"
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-for-in-and-for-of-statements |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

兼容性：初始化函数表达式
------------

在 SpiderMonkey 40 版本之前，可以在使用一个初始化表达式（`i=0`）在一个`for...in`循环中：

```js
var obj = {a: 1, b: 2, c: 3};
for (var i = 0 in obj) {
  console.log(obj[i]);
}
// 1
// 2
// 3
```

这个非标准行为现在在版本 40 及更高版本中被忽略，并将在严格模式（Firefox bug 748550 和 Firefox bug 1164741）中呈现`SyntaxError`（"for-in loop head declarations may not have initializers"）错误。

像其他引擎 V8(Chrome)，Chakra (IE/Edge)，JSC (WebKit/Safari) 正在研究去除这种不标准的行为。

参见
--

* `for...of` 一个类似的迭代属性值的语句
* `for`
* 属性的可枚举性和所有权
* `Object.getOwnPropertyNames()`
* `Object.hasOwn()`
* `Array.prototype.forEach()`


do...while - JavaScript

do...while
==========

**`do...while` 语句**创建一个执行指定语句的循环，直到`condition`值为 false。在执行`statement` 后检测`condition`，所以指定的`statement`至少执行一次。

语法
--
```

do
   statement
while (condition);
```

`statement`

执行至少一次的语句，并在每次 `condition` 值为真时重新执行。想执行多行语句，可使用`block`语句（`{ ... }`）包裹这些语句。

`condition`

循环中每次都会计算的表达式。如果 `condition` 值为真， `statement` 会再次执行。当 `condition` 值为假，则跳到`do...while`之后的语句。

示例
--

### 使用 `do...while`

下面的例子中，`do...while` 循环至少迭代一次，并且继续迭代直到 `i`不再小于 5 时结束。

### HTML 内容

```html
<div id="example"></div>
```

### JavaScript 内容

```js
var result = "";
var i = 0;
do {
  i += 1;
  result += i + " ";
} while (i < 5);
document.getElementById("example").innerHTML = result;
```

### 结果

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-do-while-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `while`
* `for`
* `break`
* `continue`


const - JavaScript

const
=====

常量是块级范围的，非常类似用 let 语句定义的变量。但常量的值是无法（通过重新赋值）改变的，也不能被重新声明。

尝试一下
----

语法
--
```

const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]];
```

`nameN`

常量名称，可以是任意合法的标识符。

`valueN`

常量值，可以是任意合法的表达式。

描述
--

此声明创建一个常量，其作用域可以是全局或本地声明的块。与`var`变量不同，全局常量不会变为 window 对象的属性。需要一个常数的初始化器；也就是说，你必须在声明的同一语句中指定它的值（这是有道理的，因为以后不能更改）。

**`const` 声明**创建一个值的只读引用。但这并不意味着它所持有的值是不可变的，只是变量标识符不能重新分配。例如，在引用内容是对象的情况下，这意味着可以改变对象的内容（例如，其参数）。

关于“暂存死区”的所有讨论都适用于`let`和`const`。

一个常量不能和它所在作用域内的其他变量或函数拥有相同的名称。

示例
--

### const 基本用法

常量在声明的时候可以使用大小写，但通常情况下全部用大写字母。

```js
// 定义常量 MY\_FAV 并赋值 7
const MY\_FAV = 7;

// 报错 - Uncaught TypeError: Assignment to constant variable.
MY\_FAV = 20;

// MY\_FAV is 7
console.log("my favorite number is: " + MY\_FAV);

// 尝试重新声明会报错
// Uncaught SyntaxError: Identifier 'MY\_FAV' has already been declared
const MY\_FAV = 20;

// MY\_FAV 保留给上面的常量，这个操作会失败
var MY\_FAV = 20;

// 也会报错
let MY\_FAV = 20;
```

### 块作用域

注意块作用域的性质很重要

```js
if (MY\_FAV === 7) {
  // 没问题，并且创建了一个块作用域变量 MY\_FAV
  // (works equally well with let to declare a block scoped non const variable)
  let MY\_FAV = 20;

  // MY\_FAV 现在为 20
  console.log("my favorite number is " + MY\_FAV);

  // 这被提升到全局上下文并引发错误
  var MY\_FAV = 20;
}

// MY\_FAV 依旧为 7
console.log("my favorite number is " + MY\_FAV);
```

### 常量要求一个初始值

```js
// 报错
// Uncaught SyntaxError: Missing initializer in const declaration

const FOO;
```

### 常量可以定义成对象和数组

常量可以定义成对象和数组

```js
const MY\_OBJECT = { key: "value" };

// 重写对象和上面一样会失败
// Uncaught TypeError: Assignment to constant variable.
MY\_OBJECT = { OTHER\_KEY: "value" };

// 对象属性并不在保护的范围内
// 下面这个声明会成功执行
MY\_OBJECT.key = "otherValue"; // Use Object.freeze() to make object immutable

// 也可以用来定义数组
const MY\_ARRAY = [];
// 可以向数组填充数据
MY\_ARRAY.push("A"); // ["A"]
// 但是，将一个新数组赋给变量会引发错误
// Uncaught TypeError: Assignment to constant variable.
MY\_ARRAY = ["B"];
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-let-and-const-declarations |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `var`
* `let`
* JavaScript 指南中的常量


var - JavaScript

var
===

**`var` 语句** 用于声明一个函数范围或全局范围的变量，并可将其初始化为一个值（可选）。

尝试一下
----

语法
--

```js
var varname1 [= value1] [, varname2 [= value2] ... [, varnameN [= valueN]]];
```

`varnameN`

变量名。变量名可以定义为任何合法标识符。

`valueN` 可选

变量的初始化值。该值可以是任何合法的表达式。默认值为 `undefined`。

另外，解构赋值语法也可用于变量声明。

```js
var { bar } = foo; // foo = { bar:10, baz:12 };
/\* 创建一个名为 'bar' 的变量，其值为 10 \*/
```

描述
--

无论在何处声明变量，都会在执行任何代码之前进行处理。这被称为变量提升，我们将在下面进一步讨论。

用 `var` 声明的变量的作用域是它当前的执行上下文及其闭包（嵌套函数），或者对于声明在任何函数外的变量来说是全局。使用 `var` 重复声明 JavaScript 变量并不会抛出错误（即使在严格模式 (strict mode) 下），同时，变量也不会丢失其值，直到调用其他的赋值操作。

```js
"use strict";
function foo() {
  var x = 1;
  function bar() {
    var y = 2;
    console.log(x); // 1 (函数 `bar` 包含了 `x`)
    console.log(y); // 2 (`y` 在作用域内)
  }
  bar();
  console.log(x); // 1 (`x` 在作用域内)
  console.log(y); // 在严格模式（strict mode）下将抛出 ReferenceError，`y` 仅在 `bar` 函数的作用域内
}

foo();
```

使用 `var` 声明的变量将在任何代码执行前被创建，这被称为变量提升。这些变量的初始值为 `undefined`。

```js
"use strict";
console.log(x); // undefined (注意，不会抛出 ReferenceError)
console.log("still going..."); // still going...
var x = 1;
console.log(x); // 1
console.log("still going..."); // still going...
```

在全局上下文中，使用 `var` 声明的变量将作为全局对象的**不可配置**属性被添加。这意味着它的属性描述符无法被修改，也无法使用 `delete` 删除。其对应的名字也将被添加到 全局环境记录（global environment record）（它构成了全局词法环境 (global lexical environment) 的一部分）的 `[[VarNames]]` 插槽内的列表中。`[[VarNames]]` 中的命名列表使运行时能够区分“全局变量”和“全局对象的直接属性”。

全局变量（以全局对象的属性的形式被创建）不可配置的原因是：该标识符被视为一个变量，而不是全局对象的**直接属性**。JavaScript 具有自动化的内存管理机制，因此“能够使用 `delete` 删除全局变量”是没有意义的。

```js
"use strict";
var x = 1;
globalThis.hasOwnProperty("x"); // true
delete globalThis.x; // 在严格模式下，将抛出 TypeError，否则静默失败（fail silently）。
delete x; // 在严格模式下，将抛出 SyntaxError，否则静默失败。
```

注意，在 Node.js CommonJS modules 以及原生 ECMAScript modules 中，顶层变量被声明在模块（module）的作用域内，而不是以属性的形式被添加到全局对象中。

### 未限定标识符的赋值

全局对象位于作用域链的顶部。在尝试将名称解析为值时，将沿着作用域链搜索该名称。这意味着，全局对象中的属性在所有作用域中均可见，而无需使用 `globalThis.`、`window.` 或 `global.` 来限定标识符。

所以你可以这样写：

```js
function foo() {
  String("s"); // 注意，函数 `String` 是隐式可见的
}
```

这是因为：

```js
globalThis.hasOwnProperty("String"); // true
```

所以在未限定标识符的情况下，最终也会在全局对象中搜索名称。你无需使用 `globalThis.String`，而只需使用 `String`。在非严格模式下，如果作用域链中没有声明同名的变量，则对未限定标识符的赋值将会在全局对象下创建同名属性（译者注：即作为全局变量被创建）。

```js
foo = "f"; // 在非严格模式下，假设你想在全局对象下创建名为 `foo` 的属性
globalThis.hasOwnProperty("foo"); // true
```

在 ECMAScript 5 的严格模式 下，此行为具有不同的表现。在严格模式下，对未限定的标识符赋值将抛出 `ReferenceError`，以避免意外创建全局对象属性。

注意，上面的说明意味着：与流行的错误信息相反，JavaScript 并没有隐式变量声明，它只是有一个类似的语法。

### 变量提升

由于变量声明（以及其他声明）总是在任意代码执行之前处理的，所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用，这个行为叫做“hoisting”。“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置。

```js
bla = 2;
var bla;

// 可以隐式地（implicitly）将以上代码理解为：

var bla;
bla = 2;
```

因此，建议始终在作用域顶部声明变量（全局代码的顶部和函数代码的顶部），这可以清楚知道哪些变量是函数作用域（局部），哪些变量在作用域链上解决。

重要的是，提升将影响变量声明，而不会影响其值的初始化。仅在运行到赋值语句时初始化变量的值。在此之前，变量的值始终为 `undefined`（但已被声明）：

```js
function do\_something() {
  console.log(bar); // undefined
  var bar = 111;
  console.log(bar); // 111
}

// 可以隐式地将以上代码理解为：

function do\_something() {
  var bar;
  console.log(bar); // undefined
  bar = 111;
  console.log(bar); // 111
}
```

示例
--

### 声明并初始化两个变量

```js
var a = 0,
  b = 0;
```

### 给两个变量赋值成字符串值

```js
var a = "A";
var b = a;

// 等效于：

var a,
  b = (a = "A");
```

留意其中的顺序：

```js
var x = y,
  y = "A";
console.log(x + y); // undefinedA
```

在这里，`x` 和 `y` 在代码执行前就已经创建了，而赋值操作发生在创建之后。当“`x = y`”执行时，`y` 已经存在，所以不会抛出 `ReferenceError`，并且它的值是 `undefined`。所以，`x` 被赋予 `undefined` 值。然后，`y` 被赋予 `'A'`。于是，在执行完第一行之后，才出现了这样的结果：`x === undefined && y === 'A'`。

### 多个变量的初始化

```js
var x = 0;
function f() {
  var x = (y = 1); // x 在函数内部声明，y 则在全局作用域下声明
}
f();

console.log(x, y); // 0 1

// 在非严格模式下：
// x 是全局变量。
// y 是隐式声明的全局变量。
```

在严格模式下运行相同的示例：

```js
"use strict";

var x = 0;
function f() {
  var x = (y = 1); // 严格模式下将抛出 ReferenceError
}
f();

console.log(x, y);
```

### 隐式全局变量和外部函数作用域

看起来像是隐式全局作用域的变量也有可能是其外部函数变量的引用：

```js
var x = 0; // x 是全局变量，并且赋值为 0

console.log(typeof z); // // undefined，因为 z 还不存在

function a() {
  var y = 2; // y 被声明成函数 a 作用域的变量，并且赋值为 2

  console.log(x, y); // 0 2

  function b() {
    x = 3; // 全局变量 x 被赋值为 3
    y = 4; // 已存在的外部函数的 y 变量被赋值为 4
    z = 5; // 创建新的全局变量 z，并且赋值为 5
    // (在严格模式下抛出 ReferenceError)
  }

  b(); // 调用 b 时创建了全局变量 z
  console.log(x, y, z); // 3 4 5
}

a(); // Also calls b.
console.log(x, z); // 3 5
console.log(typeof y); // undefined，因为 y 是 a 函数的局部变量
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-variable-statement |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `let`
* `const`


function - JavaScript

function
========

**函数声明**定义一个具有指定参数的函数。

你还可以使用 `Function` 构造函数和 一个`function expression` 定义函数。

语法
--
```

function name([param,[, param,[..., param]]]) {
   [statements]
}
```

`name`

函数名

`param`

要传递给函数的参数的名称。不同引擎中的最大参数数量不同。

`statements`

包含函数体的语句。

描述
--

一个被函数声明创建的函数是一个 Function 对象，具有 Function 对象的所有属性、方法和行为。查看 Function 以获取 function 的详细信息。

函数也可以被表达式创建（ function expression ）

函数可以被有条件来声明，这意味着，在一个 if 语句里，函数声明是可以嵌套的。有的浏览器会将这种有条件的声明看成是无条件的声明，无论这里的条件是 true 还是 false，浏览器都会创建函数。因此，它们不应该被使用。

默认情况下，函数是返回 undefined 的。想要返回一个其他的值，函数必须通过一个 return 语句指定返回值。

### 有条件的创建函数

函数可以被有条件来声明，这意味着，函数声明可能出现在一个 if 语句里，但是，这种声明方式在不同的浏览器里可能有不同的效果。因此，不应该在生产环境代码中使用这种声明方式，应该使用函数表达式来代替。

```js
var hoisted = "foo" in this;
console.log(
  `'foo' name ${
 hoisted ? "is" : "is not"
 } hoisted. typeof foo is ${typeof foo}`,
);
if (false) {
  function foo() {
    return 1;
  }
}

// 在 Chrome 里：
// 'foo' 变量名被提升，但是 typeof foo 为 undefined
//
// 在 Firefox 里：
// 'foo' 变量名被提升。但是 typeof foo 为 undefined
//
// 在 Edge 里：
// 'foo' 变量名未被提升。而且 typeof foo 为 undefined
//
// 在 Safari 里：
// 'foo' 变量名被提升。而且 typeof foo 为 function
```

注意，即使把上面代码中的 if(false) 改为 if(true)，结果也是一样的

```js
var hoisted = "foo" in this;
console.log(
  `'foo' name ${
 hoisted ? "is" : "is not"
 } hoisted. typeof foo is ${typeof foo}`,
);
if (true) {
  function foo() {
    return 1;
  }
}

// 在 Chrome 里：
// 'foo' 变量名被提升，但是 typeof foo 为 undefined
//
// 在 Firefox 里：
// 'foo' 变量名被提升。但是 typeof foo 为 undefined
//
// 在 Edge 里：
// 'foo' 变量名未被提升。而且 typeof foo 为 undefined
//
// 在 Safari 里：
// 'foo' 变量名被提升。而且 typeof foo 为 function
```

### 函数声明提升

JavaScript 中的**函数声明**被提升到了**函数定义**。你可以在函数声明之前使用该函数：

```js
hoisted(); // logs "foo"

function hoisted() {
  console.log("foo");
}
```

**备注：** 函数表达式 `function expressions` 不会被提升：

```js
notHoisted(); // TypeError: notHoisted is not a function

var notHoisted = function () {
  console.log("bar");
};
```

示例
--

### 使用函数

下面的代码声明了一个函数，该函数返回了销售的总金额，参数是产品 a,b,c 分别的销售的数量。

```js
function calc\_sales(units\_a, units\_b, units\_c) {
  return units_a \* 79 + units_b \* 129 + units_c \* 699;
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-function-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 函数指南
* Function
* `Function`
* `function` 表达式
* `function*`
* `async function`
* `async function*`


class - JavaScript

class
=====

Baseline Widely available
-------------------------

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

* Learn more
* See full compatibility
* Report feedback
**class 声明**创建一个基于原型继承的具有给定名称的新类。

尝试一下
----

你也可以使用类表达式定义类。但是不同于类表达式，类声明不允许再次声明已经存在的类，否则将会抛出一个类型错误。

语法
--

```js
class name [extends] {
  // class body
}
```

描述
--

和类表达式一样，类声明体在严格模式下运行。构造函数是可选的。

类声明不可以提升（这与函数声明不同）。

示例
--

### 声明一个类

在下面的例子中，我们首先定义一个名为 Polygon 的类，然后继承它来创建一个名为 Square 的类。注意，构造函数中使用的 super() 只能在构造函数中使用，并且必须在使用 this 关键字前调用。

```js
class Polygon {
  constructor(height, width) {
    this.name = "Polygon";
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(length) {
    super(length, length);
    this.name = "Square";
  }
}
```

**警告：**

### 重复定义类

重复声明一个类会引起类型错误。

```js
class Foo {}
class Foo {}
// Uncaught TypeError: Identifier 'Foo' has already been declared
```

若之前使用类表达式定义了一个类，则再次声明这个类同样会引起类型错误。

```js
let Foo = class {};
class Foo {}
// Uncaught TypeError: Identifier 'Foo' has already been declared
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-class-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `function` declaration
* `class` expression
* Classes


label - JavaScript

label
=====

**标记语句**可以和 `break` 或 `continue` 语句一起使用。标记就是在一条语句前面加个可以引用的标识符（identifier）。

尝试一下
----

**备注：** 使用标记的循环或语句块非常罕见。通常情况下，可以使用函数调用而不是（基于标记的）循环跳转。

语法
--
```

label :
   statement
```

`label`

任何不属于保留关键字的 JavaScript 标识符。

`statement`

JavaScript 语句。`break` 可用于任何标记语句，而 `continue` 可用于循环标记语句。

描述
--

可使用一个标签来唯一标记一个循环，然后使用 `break` 或 `continue` 语句来指示程序是否中断循环或继续执行。

需要注意的是，JavaScript 没有 `goto` 语句，标记只能和 `break` 或 `continue` 一起使用。

在严格模式中，你不能使用“`let`”作为标签名称。它会抛出一个 `SyntaxError`（因为 let 是一个保留的标识符）。

示例
--

### 在 `for` 循环中使用带标记的 `continue` 语句

```js
var i, j;

loop1: for (i = 0; i < 3; i++) {
  //The first for statement is labeled "loop1"
  loop2: for (j = 0; j < 3; j++) {
    //The second for statement is labeled "loop2"
    if (i === 1 && j === 1) {
      continue loop1;
    }
    console.log("i = " + i + ", j = " + j);
  }
}

// Output is:
// "i = 0, j = 0"
// "i = 0, j = 1"
// "i = 0, j = 2"
// "i = 1, j = 0"
// "i = 2, j = 0"
// "i = 2, j = 1"
// "i = 2, j = 2"
// Notice how it skips both "i = 1, j = 1" and "i = 1, j = 2"
```

### 使用带标记的 `continue` 语句

若给定一个数据数组和一个测试数组，则下面的例子会统计通过测试的数据的数量。

```js
var itemsPassed = 0;
var i, j;

top: for (i = 0; i < items.length; i++) {
  for (j = 0; j < tests.length; j++) {
    if (!tests[j].pass(items[i])) {
      continue top;
    }
  }

  itemsPassed++;
}
```

### 在 `for` 循环中使用带标记的 `break`

```js
var i, j;

loop1: for (i = 0; i < 3; i++) {
  //The first for statement is labeled "loop1"
  loop2: for (j = 0; j < 3; j++) {
    //The second for statement is labeled "loop2"
    if (i == 1 && j == 1) {
      break loop1;
    }
    console.log("i = " + i + ", j = " + j);
  }
}

// Output is:
// "i = 0, j = 0"
// "i = 0, j = 1"
// "i = 0, j = 2"
// "i = 1, j = 0"
// Notice the difference with the previous continue example
```

### 使用带标记的 `break` 语句

若给定一个数据数组和一个测试数组，则下面的例子会判断是否所有数据均通过了测试。

```js
var allPass = true;
var i, j;

top: for (i = 0; items.length; i++)
  for (j = 0; j < tests.length; i++)
    if (!tests[j].pass(items[i])) {
      allPass = false;
      break top;
    }
```

### 在标记块中使用 `break`

你可以在代码块中使用标记，但只有 `break` 语句可以使用非循环标记。

```js
foo: {
  console.log("face");
  break foo;
  console.log("this will not be executed");
}
console.log("swap");

// this will log:

// "face"
// "swap
```

### 标记函数声明

从 ECMAScript 2015 开始，标准的函数声明现在对规范的 Web 兼容性附件中的非严格代码进行了标准化。

```js
L: function F() {}
```

在严格模式中，这会抛出 `SyntaxError`：

```js
"use strict";
L: function F() {}
// SyntaxError: functions cannot be labelled
```

无论是否处于严格模式下，生成器函数都不能被标记：

```js
L: function\* F() {}
// SyntaxError: generator functions cannot be labelled
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-labelled-statements |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `break`
* `continue`


构造方法 - JavaScript

构造方法
====

Baseline Widely available
-------------------------

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

* Learn more
* See full compatibility
* Report feedback
**`constructor`** 是一种用于创建和初始化`class`创建的对象的特殊方法。

尝试一下
----

语法
--
```

constructor([arguments]) { ... }
```

描述
--

在一个类中只能有一个名为“constructor”的特殊方法。一个类中出现多次构造函数 (`constructor)`方法将会抛出一个 `SyntaxError` 错误。

在一个构造方法中可以使用`super`关键字来调用一个父类的构造方法。

如果没有显式指定构造方法，则会添加默认的 constructor 方法。

如果不指定一个构造函数 (constructor) 方法，则使用一个默认的构造函数 (constructor)。

示例
--

### 使用`constructor`方法

以下代码片段来自 类的实例（在线 demo）。

```js
class Square extends Polygon {
  constructor(length) {
    // 在这里，它调用了父类的构造函数，并将 lengths 提供给 Polygon 的"width"和"height"
    super(length, length);
    // 注意：在派生类中，必须先调用 super() 才能使用 "this"。
    // 忽略这个，将会导致一个引用错误。
    this.name = "Square";
  }
  get area() {
    return this.height \* this.width;
  }
  set area(value) {
    // 注意：不可使用 this.area = value
    // 否则会导致循环 call setter 方法导致爆栈
    this._area = value;
  }
}
```

### 另一个例子

看看这个代码片段

```js
class Polygon {
  constructor() {
    this.name = "Polygon";
  }
}

class Square extends Polygon {
  constructor() {
    super();
  }
}

class Rectangle {}

Object.setPrototypeOf(Square.prototype, Rectangle.prototype);

console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true

let newInstance = new Square();
console.log(newInstance.name); //Polygon
```

这里，**Square**类的原型被改变，但是在正在创建一个新的正方形实例时，仍然调用前一个基类**Polygon**的构造函数。

### 默认构造方法

如前所述，如果不指定构造方法，则使用默认构造函数。对于基类，默认构造函数是：

```js
constructor() {}
```

对于派生类，默认构造函数是：

```js
constructor(...args) {
  super(...args);
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-static-semantics-constructormethod |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参阅
--

* super()
* `class` expression
* `class` declaration
* Classes


extends - JavaScript

extends
=======

Baseline Widely available
-------------------------

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

* Learn more
* See full compatibility
* Report feedback
**`extends`** 关键字用于类声明或者类表达式中，以创建一个类，该类是另一个类的子类。

语法
--
```

class ChildClass extends ParentClass { ... }
```

描述
--

`extends` 关键字用来创建一个普通类或者内建对象的子类。

继承的 `.prototype` 必须是一个 `Object` 或者 `null`。

示例
--

### 使用 `extends`

第一个例子是根据名为 `Polygon` 类创建一个名为 `Square` 的类。这个例子是从这个在线演示中提取出来的。

```js
class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // Note: In derived classes, super() must be called before you
    // can use 'this'. Leaving this out will cause a reference error.
    this.name = "Square";
  }

  get area() {
    return this.height \* this.width;
  }
}
```

### 使用 `extends`与内置对象

这个示例继承了内置的 `Date` 对象。这个例子是从这个在线演示中提取出来的。

```js
class myDate extends Date {
  constructor() {
    super();
  }

  getFormattedDate() {
    var months = [
      "Jan",
      "Feb",
      "Mar",
      "Apr",
      "May",
      "Jun",
      "Jul",
      "Aug",
      "Sep",
      "Oct",
      "Nov",
      "Dec",
    ];
    return (
      this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear()
    );
  }
}
```

### 扩展 `null`

可以像扩展普通类一样扩展 `null`，但是新对象的原型将不会继承 `Object.prototype` (en-US)。

```js
class nullExtends extends null {
  constructor() {}
}

Object.getPrototypeOf(nullExtends); // Function.prototype
Object.getPrototypeOf(nullExtends.prototype); // null

new nullExtends(); //ReferenceError: this is not defined
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-class-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

扩展阅读
----

* 类
* super


公有类字段 - JavaScript

公有类字段
=====

公有静态字段和公有实例字段都是可编辑、可枚举和可配置的属性。因此，不同于私有对应值（private counterpart）的是，它们参与原型的继承。

语法
--

```js
class ClassWithInstanceField {
  instanceField = "instance field";
}

class ClassWithStaticField {
  static staticField = "static field";
}

class ClassWithPublicInstanceMethod {
  publicMethod() {
    return "hello world";
  }
}
```

示例
--

### 公有静态字段

公有静态字段在你想要创建一个只在每个类里面只存在一份，而不会存在于你创建的每个类的实例中的属性时可以用到。你可以用它存放缓存数据、固定结构数据或者其他你不想在所有实例都复制一份的数据。

公有静态字段是使用关键字 `static` 声明的。我们在声明一个类的时候，使用 `Object.defineProperty()` 方法将公有静态字段添加到类的构造函数中。在类被声明之后，可以从类的构造函数访问公有静态字段。

```js
class ClassWithStaticField {
  static staticField = "static field";
}

console.log(ClassWithStaticField.staticField);
// 预期输出值："static field"
```

没有设定初始化的字段将默认被初始化为 `undefined`。

```js
class ClassWithStaticField {
  static staticField;
}

console.assert(Object.hasOwn(ClassWithStaticField, "staticField"));
console.log(ClassWithStaticField.staticField);
// 预期输出值："undefined"
```

公有静态字段不会在子类中重复初始化，但我们可以通过原型链访问它们。

```js
class ClassWithStaticField {
  static baseStaticField = "base field";
}

class SubClassWithStaticField extends ClassWithStaticField {
  static subStaticField = "sub class field";
}

console.log(SubClassWithStaticField.subStaticField);
// 预期输出值："sub class field"

console.log(SubClassWithStaticField.baseStaticField);
// 预期输出值："base field"
```

在初始化字段时，`this` 指向的是类的构造函数。你也可以通过名字引用构造函数，并使用 `super` 获取到存在的父类的构造函数。

```js
class ClassWithStaticField {
  static baseStaticField = "base static field";
  static anotherBaseStaticField = this.baseStaticField;

  static baseStaticMethod() {
    return "base static method output";
  }
}

class SubClassWithStaticField extends ClassWithStaticField {
  static subStaticField = super.baseStaticMethod();
}

console.log(ClassWithStaticField.anotherBaseStaticField);
// 预期输出值："base static field"

console.log(SubClassWithStaticField.subStaticField);
// 预期输出值："base static method output"
```

### 公有实例字段

公有实例字段存在于类的每一个实例中。通过声明一个公有字段，我们可以确保该字段一直存在，而类的定义则会更加像是自我描述。

公有实例字段可以在基类的构造过程中（构造函数主体运行前）使用 `Object.defineProperty()` 添加，也可以在子类构造函数中的 `super()` 函数结束后添加。

```js
class ClassWithInstanceField {
  instanceField = "instance field";
}

const instance = new ClassWithInstanceField();
console.log(instance.instanceField);
// 预期输出值："instance field"
```

没有设定初始化的字段将默认被初始化为 `undefined`。

```js
class ClassWithInstanceField {
  instanceField;
}

const instance = new ClassWithInstanceField();
console.assert(Object.hasOwn(instance, "instanceField"));
console.log(instance.instanceField);
// 预期输出值："undefined"
```

和属性（properties）一样，字段名可以由计算得出。

```js
const PREFIX = "prefix";

class ClassWithComputedFieldName {
  [`${PREFIX}Field`] = "prefixed field";
}

const instance = new ClassWithComputedFieldName();
console.log(instance.prefixField);
// 预期输出值："prefixed field"
```

在初始化字段时，`this` 指向的是类正在构造中的实例。和公有实例方法相同的是：你可以在子类中使用 `super` 来访问父类的原型。

```js
class ClassWithInstanceField {
  baseInstanceField = "base field";
  anotherBaseInstanceField = this.baseInstanceField;
  baseInstanceMethod() {
    return "base method output";
  }
}

class SubClassWithInstanceField extends ClassWithInstanceField {
  subInstanceField = super.baseInstanceMethod();
}

const base = new ClassWithInstanceField();
const sub = new SubClassWithInstanceField();

console.log(base.anotherBaseInstanceField);
// 预期输出值："base field"

console.log(sub.subInstanceField);
// 预期输出值："base method output"
```

因为类的实例字段是在对应的构造函数运行之前添加的，所以你可以在构造函数中访问字段的值。

```js
class ClassWithInstanceField {
  instanceField = "instance field";

  constructor() {
    console.log(this.instanceField);
    this.instanceField = "new value";
  }
}

const instance = new ClassWithInstanceField(); // 输出 "instance field"
console.log(instance.instanceField); // "new value"
```

但是，因为派生类的实例字段是在 `super()` 返回之后定义的，所以基类的构造函数无法访问派生类的字段。

```js
class Base {
  constructor() {
    console.log("Base constructor:", this.field);
  }
}

class Derived extends Base {
  field = 1;
  constructor() {
    super();
    console.log("Derived constructor:", this.field);
  }
}

const instance = new Derived();
// Base constructor: undefined
// Derived constructor: 1
```

因为类字段是通过 `[[Define]]` 语义（本质上是 `Object.defineProperty()`）添加的，所以派生类中的字段声明并不会调用基类中的 setter。此行为不同于在构造函数中使用 `this.field = …`。

```js
class Base {
  set field(val) {
    console.log(val);
  }
}

class DerivedWithField extends Base {
  field = 1;
}

const instance = new DerivedWithField(); // No log

class DerivedWithConstructor extends Base {
  constructor() {
    super();
    this.field = 1;
  }
}

const instance2 = new DerivedWithConstructor(); // Logs 1
```

**备注：** 在类字段规范最终确定使用 `[[Define]]` 语义前，包括 Babel 和 tsc 在内的大多数转译器都将类字段转换为 `DerivedWithConstructor` 的形式，这在类字段标准化后产生了一些错误。

公有方法
----

### 公有静态方法

关键字 **`static`** 将为一个类定义一个静态方法。静态方法不是在一个实例之上被调用，而是在类自身之上被调用。它们通常是工具函数，比如用来创建或者复制对象。

```js
class ClassWithStaticMethod {
  static staticMethod() {
    return "static method has been called.";
  }
}

console.log(ClassWithStaticMethod.staticMethod());
// 预期输出值："static method has been called."
```

静态方法是在类的声明阶段用 `Object.defineProperty()` 方法添加到类的构造函数中的。静态方法是可编辑、不可枚举和可配置的。

### 公有实例方法

正如其名，公有实例方法是可以在类的实例中使用的。

```js
class ClassWithPublicInstanceMethod {
  publicMethod() {
    return "hello world";
  }
}

const instance = new ClassWithPublicInstanceMethod();
console.log(instance.publicMethod());
// 预期输出值："hello world"
```

公有实例方法是在类的声明阶段用 `Object.defineProperty()` 方法添加到类中的。静态方法是可编辑、不可枚举和可配置的。

你可以使用生成器（generator）、异步和异步生成器方法。

```js
class ClassWithFancyMethods {
  \*generatorMethod() {}
  async asyncMethod() {}
  async \*asyncGeneratorMethod() {}
}
```

在实例的方法中，`this` 指向的是实例本身，你可以使用 `super` 访问到父类的原型，由此你可以调用父类的方法。

```js
class BaseClass {
  msg = "hello world";
  basePublicMethod() {
    return this.msg;
  }
}

class SubClass extends BaseClass {
  subPublicMethod() {
    return super.basePublicMethod();
  }
}

const instance = new SubClass();
console.log(instance.subPublicMethod());
// 预期输出值："hello world"
```

`getter` 和 `setter` 是和类的属性绑定的特殊方法，分别会在其绑定的属性被取值、赋值时调用。使用 get 和 set 语法定义实例的公有 `getter` 和 `setter`。

```js
class ClassWithGetSet {
  #msg = "hello world";
  get msg() {
    return this.#msg;
  }
  set msg(x) {
    this.#msg = `hello ${x}`;
  }
}

const instance = new ClassWithGetSet();
console.log(instance.msg);
// 预期输出值："hello world"

instance.msg = "cake";
console.log(instance.msg);
// 预期输出值："hello cake"
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-FieldDefinition |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 所有 JS 类元素的语义
* v8.dev 站点中关于公有和私有类字段的文章


static - JavaScript

static
======

Baseline Widely available
-------------------------

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

* Learn more
* See full compatibility
* Report feedback
类（class）通过 **static** 关键字定义静态方法。不能在类的实例上调用静态方法，而应该通过类本身调用。这些通常是实用程序方法，例如创建或克隆对象的功能。

尝试一下
----

语法
--
```

static methodName() { ... }
```

描述
--

静态方法调用直接在类上进行，不能在类的实例上调用。静态方法通常用于创建实用程序函数。

调用静态方法
------

### 从另一个静态方法

静态方法调用同一个类中的其他静态方法，可使用 `this` 关键字。

```js
class StaticMethodCall {
  static staticMethod() {
    return "Static method has been called";
  }
  static anotherStaticMethod() {
    return this.staticMethod() + " from another static method";
  }
}
StaticMethodCall.staticMethod();
// 'Static method has been called'

StaticMethodCall.anotherStaticMethod();
// 'Static method has been called from another static method'
```

### 从类的构造函数和其他方法

非静态方法中，不能直接使用 `this` 关键字来访问静态方法。而是要用类名来调用：`CLASSNAME.STATIC_METHOD_NAME()` ，或者用构造函数的属性来调用该方法： `this.constructor.STATIC_METHOD_NAME()`.

```js
class StaticMethodCall {
  constructor() {
    console.log(StaticMethodCall.staticMethod());
    // 'static method has been called.'
    console.log(this.constructor.staticMethod());
    // 'static method has been called.'
  }
  static staticMethod() {
    return "static method has been called.";
  }
}
```

示例
--

下面的例子说明了这几点：

1. 静态方法如何在类上实现。
2. 具有静态成员的类，可以被子类化。
3. 什么情况下静态方法可以调用，什么情况下不能调用。

```js
class Tripple {
  static tripple(n = 1) {
    return n \* 3;
  }
}

class BiggerTripple extends Tripple {
  static tripple(n) {
    return super.tripple(n) \* super.tripple(n);
  }
}

console.log(Tripple.tripple()); // 3
console.log(Tripple.tripple(6)); // 18

let tp = new Tripple();

console.log(BiggerTripple.tripple(3)); // 81（不会受父类实例化的影响）
console.log(tp.tripple()); // 'tp.tripple 不是一个函数'.
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-class-definitions |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `class` 表达式
* `class` 声明
* 类


私有属性 - JavaScript

私有属性
====

类属性在默认情况下是公有的，但可以使用增加哈希前缀 `#` 的方法来定义私有类字段，这一隐秘封装的类特性由 JavaScript 自身强制执行。

语法
--

```js
class ClassWithPrivateField {
  #privateField;
}

class ClassWithPrivateMethod {
  #privateMethod() {
    return "hello world";
  }
}

class ClassWithPrivateStaticField {
  static #PRIVATE\_STATIC\_FIELD;
}

class ClassWithPrivateStaticMethod {
  static #privateStaticMethod() {
    return "hello world";
  }
}
```

示例
--

### 私有字段

私有字段包括私有实例字段和私有静态字段。

#### 私有实例字段

私有实例字段使用 `#名称`（发音为“哈希名称”）声明，这些名称以 `#` 开头。即 `#` 是名称本身的一部分，声明和访问时也需要加上。私有字段在类声明的构造方法中就可被访问。

从作用域之外引用 `#` 名称、内部在未声明的情况下引用私有字段、或尝试使用 `delete` 移除声明的字段都会抛出语法错误。

```js
class ClassWithPrivateField {
  #privateField;

  constructor() {
    this.#privateField = 42;
    delete this.#privateField;   // 语法错误
    this.#undeclaredField = 444; // 语法错误
  }
}

const instance = new ClassWithPrivateField()
instance.#privateField === 42;   // 语法错误
```

**备注：** 可以使用 `in` 运算符检查私有字段（或私有方法）是否存在。当私有字段或私有方法存在时，运算符返回 `true`，否则返回 `false`。

类似于公有字段，私有字段在构造（construction）基类或调用子类的 `super()` 方法时被添加到类实例中。

```js
class ClassWithPrivateField {
  #privateField;

  constructor() {
    this.#privateField = 42;
  }
}

class SubClass extends ClassWithPrivateField {
  #subPrivateField;

  constructor() {
    super();
    this.#subPrivateField = 23;
  }
}

new SubClass();
// SubClass {#privateField: 42, #subPrivateField: 23}
```

#### 私有静态字段

私有静态字段在解析类结构时被添加到类的构造方法（constructor）中。且静态变量只能被静态方法调用的限制仍然成立。

```js
class ClassWithPrivateStaticField {
  static #PRIVATE\_STATIC\_FIELD;

  static publicStaticMethod() {
    ClassWithPrivateStaticField.#PRIVATE\_STATIC\_FIELD = 42;
    return ClassWithPrivateStaticField.#PRIVATE\_STATIC\_FIELD;
  }
}

console.log(ClassWithPrivateStaticField.publicStaticMethod() === 42);
// true
```

私有静态字段有一个来源限制：只有定义该私有静态字段的类能访问该字段。使用 **`this`** 可能会出现意想不到的行为。在下方的例子中，`this` 是 `SubClass` 类（而不是 `BaseClassWithPrivateStaticField` 类）的引用，所以尝试调用 `SubClass.basePublicStaticMethod()` 会抛出 `TypeError`。

```js
class BaseClassWithPrivateStaticField {
  static #PRIVATE\_STATIC\_FIELD;

  static basePublicStaticMethod() {
    this.#PRIVATE\_STATIC\_FIELD = 42;
    return this.#PRIVATE\_STATIC\_FIELD;
  }
}

class SubClass extends BaseClassWithPrivateStaticField {}

let error = null;

try {
  SubClass.basePublicStaticMethod();
} catch (e) {
  error = e;
}

console.log(error instanceof TypeError);
// true
console.log(error);
// TypeError: Cannot write private member #PRIVATE\_STATIC\_FIELD
// to an object whose class did not declare it
```

### 私有方法

#### 私有实例方法

私有实例方法是类实例上可用的方法，它们的访问方式与私有实例字段相同。

```js
class ClassWithPrivateMethod {
  #privateMethod() {
    return "hello world";
  }

  getPrivateMessage() {
    return this.#privateMethod();
  }
}

const instance = new ClassWithPrivateMethod();
console.log(instance.getPrivateMessage());
// hello world
```

私有实例方法可以是生成器方法、异步方法或异步生成器方法，也可以是私有的 getter 和 setter。

```js
class ClassWithPrivateAccessor {
  #message;

  get #decoratedMessage() {
    return `🎬${this.#message}🛑`;
  }
  set #decoratedMessage(msg) {
    this.#message = msg;
  }

  constructor() {
    this.#decoratedMessage = "hello world";
    console.log(this.#decoratedMessage);
  }
}

new ClassWithPrivateAccessor();
// 🎬hello world🛑
```

#### 私有静态方法

像它们的公有等价方法一样，私有静态方法是在类本身而非类的实例上调用的。像私有静态字段一样，只能从类声明内部访问它们。

```js
class ClassWithPrivateStaticMethod {
  static #privateStaticMethod() {
    return 42;
  }

  static publicStaticMethod1() {
    return ClassWithPrivateStaticMethod.#privateStaticMethod();
  }

  static publicStaticMethod2() {
    return this.#privateStaticMethod();
  }
}

console.log(ClassWithPrivateStaticMethod.publicStaticMethod1() === 42);
// true
console.log(ClassWithPrivateStaticMethod.publicStaticMethod2() === 42);
// true
```

私有静态方法可以是生成器方法，异步方法或异步生成器方法。

前面提到的私有静态字段的限制同样适用于私有静态方法。同样地，使用 **`this`** 可能会出现意想不到的行为。在下方的例子中，`this` 是 `Derived` 类（而不是 `Base` 类）的引用，所以尝试调用 `Derived.publicStaticMethod2()` 会抛出 `TypeError`。

```js
class Base {
  static #privateStaticMethod() {
    return 42;
  }
  static publicStaticMethod1() {
    return Base.#privateStaticMethod();
  }
  static publicStaticMethod2() {
    return this.#privateStaticMethod();
  }
}

class Derived extends Base {}

console.log(Derived.publicStaticMethod1());
// 42
console.log(Derived.publicStaticMethod2());
// TypeError: Cannot read private member #privateStaticMethod
// from an object whose class did not declare it
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-PrivateIdentifier |
| ECMAScript Language Specification # prod-00OK517S |

浏览器兼容性
------

### javascript.classes.private\_class\_fields

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

### javascript.classes.private\_class\_fields\_in

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

### javascript.classes.private\_class\_methods

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* Working with private class features (en-US)
* Public class fields
* The
Semantics of All JS Class Elements
* Public and private class fields
 article at the v8.dev site


InternalError: too much recursion - JavaScript

InternalError: too much recursion
=================================

信息
--
```

InternalError: too much recursion
```

错误类型
----

`内部错误`.

什么地方出错了？
--------

一个调用自身的函数被称作*递归函数*。一些情况下，递归函数类似于一个循环，都重复地执行一个代码段许多次，都需要一个条件（用于避免无尽循环或此处的无尽递归）。当出现过于深层的递归或无尽递归时，JavaScript 将会抛出此错误。

示例
--

根据递归终止的条件，该函数将递归地执行 10 次。

```js
function loop(x) {
  if (x >= 10) {
    // "x >= 10" 是递归终止条件
    return;
  }
  // 进行一些操作...
  loop(x + 1); // 递归调用
}
loop(0);
```

将递归条件设置为一个极大的数值，将不能运行：

```js
function loop(x) {
  if (x >= 1000000000000) {
    return;
  }
  // 进行一些操作...
  loop(x + 1);
}
loop(0);

// InternalError: too much recursion
```

相关页面
----

* 递归
* Recursive functions


SyntaxError: redeclaration of formal parameter "x" - JavaScript

SyntaxError: redeclaration of formal parameter "x"
==================================================

信息
--
```

SyntaxError: redeclaration of formal parameter "x" (Firefox)
SyntaxError: Identifier "x" has already been declared (Chrome)
SyntaxError: Cannot declare a let variable twice: 'x' (WebKit)
```

错误类型
----

`SyntaxError`哪里出错了？
------

某个变量名称已经作为函数参数出现了，但是又使用了 `let` 在函数体里重声明了。在 JavaScript 中不允许使用 let 在相同的函数或块范围内重新声明相同的变量。

示例
--

在本例中，参数 "arg" 又重新声明了：

```js
function f(arg) {
  let arg = "foo";
}

// SyntaxError: redeclaration of formal parameter "arg"
```

如果要更改函数体中的“arg”的值，可以像下面一样，但不需要再次声明同一个变量。换句话说：你可以省略 let 关键字。如果要创建一个新变量，则需要将其重命名，因为其与函数参数有冲突。

```js
function f(arg) {
  arg = "foo";
}

function f(arg) {
  let bar = "foo";
}
```

兼容性提醒
-----

* 在 Firefox 49 之前，会抛出的是 `TypeError`（Firefox bug 1275240）。
相关
--

* `let`
* `const`
* `var`
* Declaring variables in the JavaScript Guide


TypeError: Reduce of empty array with no initial value - JavaScript

TypeError: Reduce of empty array with no initial value
======================================================

消息
--
```

TypeError: reduce of empty array with no initial value
```

错误类型
----

`TypeError`哪里出问题了？
-------

在 JavaScript 中，有一些 reduce 函数：

* `Array.prototype.reduce()`，`Array.prototype.reduceRight()` 和
* `TypedArray.prototype.reduce()`， `TypedArray.prototype.reduceRight()`)。

这些函数有选择性的接收 `initialValue` (将会被用作回调函数第一次被调用的参数)。然而，如果没有初始值提供，它会用 `Array` 或 `TypedArray` 的第一个元素作为初始值。当提供一个空数组的时候这个错误会出现，因为那种情况下没有初始值被返回。

示例
--

### 无效的情况

当和过滤器（`Array.prototype.filter()`、`TypedArray.prototype.filter()`）结合使用的时候，可能会出现列表中所有元素被移除的情况。因此导致初始值为空。

```js
var ints = [0, -1, -2, -3, -4, -5];
ints
  .filter((x) => x > 0) // removes all elements
  .reduce((x, y) => x + y); // no more elements to use for the initial value.
```

类似的，当选择器中有瑕疵的时候相同的问题会发生，或者是列表中未预期的数量的元素：

```js
var names = document.getElementsByClassName("names");
var name_list = Array.prototype.reduce.call(
  names,
  (acc, name) => acc + ", " + name,
);
```

### 有效的情况

这个问题有两种可能的解决办法：

一种是提供一个初始值作为操作符的中立元素，比如加法里的 0，乘法里的 1，或者是合并中的一个空字符串。

```js
var ints = [0, -1, -2, -3, -4, -5];
ints
  .filter((x) => x < 0) // removes all elements
  .reduce((x, y) => x + y, 0); // the initial value is the neutral element of the addition
```

另一种办法是两方处理空的情况，要么在调用 `reduce` 之前，或者是在添加一个未预料的初始虚拟址后的回调函数中：

```js
var names = document.getElementsByClassName("names");

var name_list1 = "";
if (names1.length >= 1)
  name_list1 = Array.prototype.reduce.call(
    names,
    (acc, name) => acc + ", " + name,
  );
// name\_list1 == "" when names is empty.

var name_list2 = Array.prototype.reduce.call(
  names,
  (acc, name) => {
    if (acc == "")
      // initial value
      return name;
    return acc + ", " + name;
  },
  "",
);
// name\_list2 == "" when names is empty.
```

另见
--

* `Array.prototype.reduce()`
* `Array.prototype.reduceRight()`
* `TypedArray.prototype.reduce()`
* `TypedArray.prototype.reduceRight()`
* `Array`
* `TypedArray`
* `Array.prototype.filter()`
* `TypedArray.prototype.filter()`


X.prototype.y called on incompatible type - JavaScript

X.prototype.y called on incompatible type
=========================================

错误提示
----
```

TypeError: Function.prototype.toString called on incompatible object (Firefox)
TypeError: Function.prototype.bind called on incompatible target (Firefox)
TypeError: Method Set.prototype.add called on incompatible receiver undefined (Chrome)
TypeError: Bind must be called on a function (Chrome)
```

错误类型
----

`TypeError`哪里出错了？
------

当这个错误被抛出时，属于某个对象的函数被调用，但是没有提供给 `this` 与其所期望的函数类型一致的参数。

在调用 `Function.prototype.call()` 或`Function.prototype.apply()` 方法，但是为 `this` 提供的绑定对象与预期的不匹配时，这个问题就会出现。

该问题还会出现于将一个（存储在一个对象中的）函数提供给另外一个函数作为参数时。在这种情况下，函数所在的对象并非该函数的 `this` 绑定的目标对象。为了解决这个问题，你或者为其提供一个 lambda 表达式，该表达式完成函数要完成的任务，或者调用 `Function.prototype.bind()` 函数为 `this` 强制绑定期望的对象。

示例
--

### 错误示例

```js
var mySet = new Set();
["bar", "baz"].forEach(mySet.add);
// mySet.add is a function, but "mySet" is not captured as this.

var myFun = function () {};
["bar", "baz"].forEach(myFun.bind);
// myFun.bind is a function, but "myFun" is not captured as this.
```

### 正确示例

```js
var mySet = new Set();
["bar", "baz"].forEach(mySet.add.bind(mySet));
// This works due to binding "mySet" as this.

var myFun = function () {};
["bar", "baz"].forEach((x) => myFun.bind(x));
// This works using the "bind" function. It creates a lambda forwarding the argument.
```

相关内容
----

* `Function.prototype.call()`
* `Function.prototype.apply()`
* `Function.prototype.bind()`


SyntaxError: applying the 'delete' operator to an unqualified name is deprecated - JavaScript

SyntaxError: applying the 'delete' operator to an unqualified name is deprecated
================================================================================

错误提示
----
```

SyntaxError: applying the 'delete' operator to an unqualified name is deprecated (Firefox)
SyntaxError: Delete of an unqualified identifier in strict mode. (Chrome)
```

错误类型
----

`SyntaxError` 仅出现在严格模式下。

哪里出错了？
------

在 JavaScript 中，普通变量是不能通过 `delete` 操作符来删除的。在严格模式下，试图去删除一个变量会报错，这是不允许的。

delete 操作符只能用于删除对象中的属性。只有可配置的对象属性才“符合”被删除的条件。

与一般流行的观点相反的是，delete 操作符与直接释放内存**无关**。内存管理是通过切断引用来间接实现的。可以参考内存管理页面与 delete 操作符页面来获取更多的细节信息。

这个错误提示只出现于严格模式。在非严格模式下，该操作返回 false。

示例
--

在 JavaScript 中，普通变量是不能删除的，在严格模式下会报错：

```js
"use strict";

var x;

// ...

delete x;

// SyntaxError: applying the 'delete' operator to an unqualified name
// is deprecated
```

要释放变量引用的内容，可以将变量值设置为 `null`:

```js
"use strict";

var x;

// ...

x = null;

// x can be garbage collected
```

相关内容
----

* `delete`
* 内存管理
* TypeError: property "x" is non-configurable and can't be deleted


TypeError: invalid Array.prototype.sort argument - JavaScript

TypeError: invalid Array.prototype.sort argument
================================================

信息
--
```

TypeError: invalid Array.prototype.sort argument (Firefox)
```

错误类型
----

`TypeError`哪里出错了？
------

`Array.prototype.sort()` 的参数预期为 `undefined` 或者是一个比较操作数的函数。

示例
--

### 无效的

```js
[1, 3, 2].sort(5); // TypeError

var cmp = { asc: (x, y) => x >= y, dsc: (x, y) => x <= y };
[1, 3, 2].sort(cmp[this.key] || "asc"); // TypeError
```

### 有效的

```js
[1, 3, 2].sort(); // [1, 2, 3]

var cmp = { asc: (x, y) => x >= y, dsc: (x, y) => x <= y };
[1, 3, 2].sort(cmp[this.key || "asc"]); // [1, 2, 3]
```

相关
--

* `Array.prototype.sort()`


SyntaxError: missing name after . operator - JavaScript

SyntaxError: missing name after . operator
==========================================

错误提示
----
```

SyntaxError: missing name after . operator
```

错误类型
----

`SyntaxError`哪里出错了？
------

点操作符 (`.`) 用于属性访问。你需要指定要访问的属性名称。对于属性名称是运算表达式的属性访问，需要将属性访问的方式从点操作符转换为方括号操作符。这样你就可以在其中进行表达式运算了。或许你想要进行字符串拼接操作？那么请使用加号操作符（+）。请参考下面的示例。

示例
--

### 属性访问

在 JavaScript 中，属性访问器或者使用点操作符 (.)，或者使用方括号操作符 ([])，但是二者不能同时使用。方括号操作符允许属性名称为运算表达式的属性访问。

```js
var obj = { foo: { bar: "baz", bar2: "baz2" } };
var i = 2;

obj.[foo].[bar]
// SyntaxError: missing name after . operator

obj.foo."bar"+i;
// SyntaxError: missing name after . operator
```

为了进行代码修复，你需要向下面这样来访问对象：

```js
obj.foo.bar; // "baz"
// or alternatively
obj["foo"]["bar"]; // "baz"

// computed properties require square brackets
obj.foo["bar" + i]; // "baz2"
```

### 属性访问与字符串拼接

如果你是中其他编程语言（如 PHP）转到 JavaScript 的，很容易将点操作符 (.) 与连接操作符 (+) 混用：

```js
console.log("Hello" . "world");

// SyntaxError: missing name after . operator
```

相反要使用加号来进行字符串拼接：

```js
console.log("Hello" + "World");
```

相关页面
----

* 属性访问器


TypeError: can't assign to property "x" on "y": not an object - JavaScript

TypeError: can't assign to property "x" on "y": not an object
=============================================================

信息
--
```

TypeError: can't assign to property "x" on {y}: not an object (Firefox)
TypeError: Cannot create property 'x' on {y} (Chrome)
```

错误类型
----

`TypeError`.

原因
--

在 `Strict_mode`下，当试图给一个symbol,string,number或者一个boolean类型的数据创建一个属性时就会报 `TypeError`, 任何 Primitive 值都不允许有property.

这个问题可能是由一个错误的值被放在了一个错误的地方导致的，或者预期`String`或`Number`的对象变体

示例
--

### 错误的情况

```js
"use strict";

var foo = "my string";
// 下面这行代码在非严格模式下不会执行。
foo.bar = {}; // TypeError: can't assign to property "bar" on "my string": not an object
```

### 如何正确使用

有两种方式，第一种修复这部分代码阻止primitive被用于这种情况，或者可以通过使用对象构造器创建来修复。

```js
"use strict";

var foo = new String("my string");
foo.bar = {};
```

页面相关
----

* `Strict_mode`
* primitive


SyntaxError: test for equality (==) mistyped as assignment (=)? - JavaScript

SyntaxError: test for equality (==) mistyped as assignment (=)?
===============================================================

消息
--
```

Warning: SyntaxError: test for equality (==) mistyped as assignment (=)?
```

错误类型
----

`SyntaxError` 只在严格模式下会出现的警告。

什么地方出错了？
--------

在通常期望进行相等判定（`==`）的地方出现了赋值`（=`）。为了帮助调试，JavaScript（在开启严格模式的情况下）会对这种情况进行警告。

示例
--

### 条件表达式内的赋值

不建议在条件表达式中（例如 `if...else`）使用简单赋值语句，因为在扫视代码的时候赋值操作与相等判定容易产生混淆。例如，不要使用以下写法：

```js
if (x = y) {
  // do the right thing
}
```

如果你需要在条件表达式中使用赋值语句，通常的做法是用一对括号把赋值语句包起来。例如：

```js
if ((x = y)) {
  // do the right thing
}
```

否则，你的本意可能是想用比较操作符（如 `==` 或 `===`）：

```js
if (x == y) {
  // do the right thing
}
```

相关页面
----

* Strict mode
* `if...else`
* Comparison operators (en-US)


TypeError: invalid 'in' operand "x" - JavaScript

TypeError: invalid 'in' operand "x"
===================================

错误提示
----
```

TypeError: invalid 'in' operand "x" (Firefox)
TypeError: Cannot use 'in' operator to search for 'x' in y (Chrome)
```

错误类型
----

`TypeError`哪里出错了？
------

`in` 操作符只可以用来检测对象中是否存在某个属性，而不能用来在字符串、数字或者其他基本类型的数据中进行检索。

示例
--

### 在字符串中进行检索

与其他语言不同（如 Python），不能使用 `in` 操作符在字符串中进行检索。

```js
"Hello" in "Hello World";
// TypeError: invalid 'in' operand "Hello World"
```

可以使用 `String.prototype.indexOf()` 来代替：

```js
"Hello World".indexOf("Hello") !== -1;
// true
```

### 操作数不能为 null 或者 undefined

确保你将要进行探测的对象不为 `null` 或者 `undefined`.

```js
var foo = null;
"bar" in foo;
// TypeError: invalid 'in' operand "foo"
```

in 操作符的预期操作数只有对象类型。

```js
var foo = { baz: "bar" };
"bar" in foo; // false

"PI" in Math; // true
"pi" in Math; // false
```

### 在数组中进行检索

当使用 in 操作符来对 `Array` 对象进行检索的时候一定要特别小心，因为它检测的是索引值而不是位于索引位置的值。

```js
var trees = ["redwood", "bay", "cedar", "oak", "maple"];
3 in trees; // true
"oak" in trees; // false
```

相关内容
----

* `in` operator


SyntaxError: JSON.parse: bad parsing - JavaScript

SyntaxError: JSON.parse: bad parsing
====================================

信息
--
```

SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: end of data when ',' or ']' was expected
SyntaxError: JSON.parse: expected ',' or ']' after array element
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when ':' was expected
SyntaxError: JSON.parse: expected ':' after property name in object
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property-value pair in object literal
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data
```

错误类型
----

`SyntaxError`哪里出错了？
------

`JSON.parse()` 会把一个字符串解析成 JSON 对象。如果字符串书写正确，那么其将会被解析成一个有效的 JSON，但是这个字符串被检测出错误语法的时候将会抛出错误。

示例
--

### `JSON.parse()` 不允许在末尾添加多余的逗号

下面两行代码都会抛出错误：

```js
JSON.parse("[1, 2, 3, 4, ]");
JSON.parse('{"foo" : 1, }');
// SyntaxError JSON.parse: unexpected character
// at line 1 column 14 of the JSON data
```

省略末尾多余的逗号解析 JSON 就是正确：

```js
JSON.parse("[1, 2, 3, 4 ]");
JSON.parse('{"foo" : 1 }');
```

### JSON 的属性名必须使用双引号

属性名上不能使用单引号，例如： 'foo'。

```js
JSON.parse("{'foo' : 1 }");
// SyntaxError: JSON.parse: expected property name or '}'
// at line 1 column 2 of the JSON data
```

取而代之，写成 "foo"：

```js
JSON.parse('{"foo" : 1 }');
```

### 前导 0 和小数点

数字不能用 0 开头，比如 01，并且你的小数点后面必须跟着至少一个数字。

```js
JSON.parse('{"foo" : 01 }');
// SyntaxError: JSON.parse: expected ',' or '}' after property value
// in object at line 1 column 2 of the JSON data

JSON.parse('{"foo" : 1. }');
// SyntaxError: JSON.parse: unterminated fractional number
// at line 1 column 2 of the JSON data
```

正确的写法应该是只写一个 1，不书写前面的 0。在小数点的后面至少要跟上一个数字：

```js
JSON.parse('{"foo" : 1 }');
JSON.parse('{"foo" : 1.0 }');
```

参见
--

* `JSON`
* `JSON.parse()`
* `JSON.stringify()`


TypeError: "x" has no properties - JavaScript

TypeError: "x" has no properties
================================

错误信息
----
```

TypeError: null has no properties
TypeError: undefined has no properties
```

错误类型
----

`TypeError`.

哪里出错了？
------

`null` 和 `undefined`中，没有你需要的属性。

示例
--

```js
null.foo;
// 错误类型：null 没有这个属性

undefined.bar;
// 错误类型：undefined 没有这个属性
```

参考
--

* `null`
* `undefined`


SyntaxError: Using //@ to indicate sourceURL pragmas is deprecated. Use //# instead - JavaScript

SyntaxError: Using //@ to indicate sourceURL pragmas is deprecated. Use //# instead
===================================================================================

信息
--
```

Warning: SyntaxError: Using //@ to indicate sourceURL pragmas is deprecated. Use //# instead

Warning: SyntaxError: Using //@ to indicate sourceMappingURL pragmas is deprecated. Use //# instead
```

错误类型
----

`SyntaxError` 的警告。不会终止 JavaScript 的执行。

哪里错了？
-----

在 JavaScript 源码中使用了已废弃的 source map 语法。

JavaScript 源代码经常被组合和压缩，以便能更高效地从服务器获取它们。使用了 source maps，调试器就可以将正在执行的代码映射到原始源文件。

因为在 IE 浏览器中，只要页面存在 `//@cc_on` 注释，都会被 IE JScript 引擎解释为要打开条件编译，所以 source map 的规范更改了语法。条件编译注释 是 IE 的一个鲜为人知的特性，但是它破坏了 jQuery 和其他库的 source map。

示例
--

### 废弃的语法

使用 "@" 符号的语法已经被废弃了。

```js
//@ sourceMappingURL=http://example.com/path/to/your/sourcemap.map
```

### 标准语法

使用 "#" 符号代替。

```js
//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map
```

或者，你也可以为 JavaScript 文件设置 header，以避免添加注释：

```js
X-SourceMap: /path/to/file.js.map
```

参见
--

* How to use a source map – Firefox Tools documentation
* Introduction to source maps – HTML5 rocks


RangeError: invalid array length - JavaScript

RangeError: invalid array length
================================

错误信息
----
```

RangeError: invalid array length (Firefox)
RangeError: Invalid array length (Chrome)
RangeError: Invalid array buffer length (Chrome)
```

错误类型
----

`RangeError`什么地方出错了？
--------

无效的数组错误长度通常会在以下情形中出现：

* 当创建一个长度为负数或者长度大于等于 2^32 的`Array` 或者 `ArrayBuffer` 时。
* 当设置 `Array.length` 属性为负数或者长度大于等于 2^32 时。

为什么 Array（数组）和 ArrayBuffer（数组缓冲区）的长度会受到限制？因为 Array 和 ArrayBuffer 的 length（长度）属性被定义为一个 32 位无符号整形（unsigned 32-bit integer）的值，所以它只能存储 0 - 2^32 - 1 之间的数。

当你使用构造函数来创建一个数组的时候，你可能想使用字面值的形式，第一个参数会被解释为数组的长度。

或者说，你想要在设置数组之前确定它的长度，或把它作为一个构造函数的参数。

示例
--

### 错误的示例

```js
new Array(Math.pow(2, 40));
new Array(-1);
new ArrayBuffer(Math.pow(2, 32)); // 32 位系统
new ArrayBuffer(-1);

const a = [];
a.length = a.length - 1; // 将 length 属性的值设置为 -1

const b = new Array(Math.pow(2, 32) - 1);
b.length = b.length + 1; // 将 length 属性的值设置为 2^32
b.length = 2.5; // 将 length 属性设置为浮点数

const c = new Array(2.5); // 传入浮点数
```

### 正确的示例

```js
[Math.pow(2, 40)]; // [ 1099511627776 ]
[-1]; // [ -1 ]
new ArrayBuffer(Math.pow(2, 32) - 1);
new ArrayBuffer(Math.pow(2, 33)); // 64-bit systems after Firefox 89
new ArrayBuffer(0);

const a = [];
a.length = Math.max(0, a.length - 1);

const b = new Array(Math.pow(2, 32) - 1);
b.length = Math.min(0xffffffff, b.length + 1);
// 0xffffffff 0xffffffff 是 2^32 - 1 的十六进制表示方式
// 它也可以被写作 (-1 >>> 0)

b.length = 3;

const c = new Array(3);
```

参见
--

* `Array`
* `Array.length`
* `ArrayBuffer`


TypeError: can't define property "x": "obj" is not extensible - JavaScript

TypeError: can't define property "x": "obj" is not extensible
=============================================================

错误提示
----
```

TypeError: can't define property "x": "obj" is not extensible (Firefox)
TypeError: Cannot define property: "x", object is not extensible. (Chrome)
```

错误类型
----

`TypeError`哪里出错了？
------

通常情况下，对象是可以进行扩展的，即可以向其添加新的属性。然而当使用 `Object.preventExtensions()` 将一个对象标记为不再可以扩展的情况下，就无法对该对象添加在其被标记为不可扩展之前所拥有的属性之外的新属性了。

示例
--

在严格模式下，向已经标记为不可扩展的对象添加新属性会报 TypeError 错误。而在非严格模式下，添加属性“x”会被静默忽略。

```js
"use strict";

var obj = {};
Object.preventExtensions(obj);

obj.x = "foo";
// TypeError: can't define property "x": "obj" is not extensible
```

在严格模式和非严格模式下两种模式下，调用`Object.defineProperty()` 向标记为不可扩展的对象添加新属性都会报 TypeError 错误。

```js
var obj = {};
Object.preventExtensions(obj);

Object.defineProperty(obj, "x", { value: "foo" });
// TypeError: can't define property "x": "obj" is not extensible
```

为了修复这个错误，你可以彻底移除 `Object.preventExtensions()` 语句，或者将其移动位置，使得属性在对象被标记为不可扩展之前添加。当然如果不需要试图添加的属性的话，你也可以将其移除。

```js
"use strict";

var obj = {};
obj.x = "foo"; // add property first and only then prevent extensions

Object.preventExtensions(obj);
```

相关内容
----

* `Object.preventExtensions()`


ReferenceError: assignment to undeclared variable "x" - JavaScript

ReferenceError: assignment to undeclared variable "x"
=====================================================

信息
--
```

ReferenceError: assignment to undeclared variable "x" (Firefox)
ReferenceError: "x" is not defined (Chrome)
ReferenceError: Variable undefined in strict mode (Edge)
```

错误类型
----

仅在严格模式中出现 `ReferenceError` 警告。

发生了什么？
------

在代码里赋值了一个未声明的变量。换句话说，有处没有带着 `var` 关键字的赋值。事实上已声明的和未声明的变量之间有一些差异，这可能会导致意想不到的结果，这就是为什么 JavaScript 在严格模式打印出这种错误。

关于已声明和未声明的变量，其有三个注意事项：

* 已声明的变量被约束在其执行上下文中。未声明的变量总是全局的。
* 已声明的变量在执行任何代码之前就创建了。未声明的变量则不存在，直到执行相关的代码。
* 已声明的变量是其执行上下文（函数或全局）的不可配置属性。而未声明的变量是可配置的（例如可以删除）。

更多信息及例子，请参考 `var` 页面。

关于未声明变量的赋值的错误仅在严格模式里出现。在非严格模式中，这些将被忽略。

示例
--

### 无效的

在本例中，"bar" 是未声明的变量。

```js
function foo() {
  "use strict";
  bar = true;
}
foo(); // ReferenceError: assignment to undeclared variable bar
```

### 有效的

为了使 "bar" 是一个已声明变量，你需要在其前面加一个 var 关键字。

```js
function foo() {
  "use strict";
  var bar = true;
}
foo();
```

相关
--

* Strict mode


SyntaxError: "use strict" not allowed in function with non-simple parameters - JavaScript

SyntaxError: "use strict" not allowed in function with non-simple parameters
============================================================================

信息
--
```

Firefox:
句法错误："use strict" 不允许在带默认参数的函数中
句法错误："use strict" 不允许在带 rest 参数的函数中
句法错误："use strict" 不允许在带解构参数的函数中

Chrome:
句法错误：非法的'use strict'指令，在带有非简单参数列表的函数中
```

错误类型
----

`SyntaxError`.

哪里出错了？
------

在函数顶部直接写了 `"use strict"` ，而该函数拥有以下的参数其中之一：

* 默认参数
* 剩余参数
* 解构赋值

根据 ECMAScript 规范，不允许在这些函数的顶部使用“use strict”指令。

示例
--

### 函数语句

在这种情况下，函数 sum 具有默认参数 a = 1 和 b = 2：

```js
function sum(a=1, b=2) {
  // SyntaxError: "use strict" not allowed in function with default parameter
  "use strict";
  return a + b;
}
```

如果这个函数应该处于 strict mode，并且整个脚本或封装函数也可以在严格模式下，可以移动 "use strict" 指令到函数之外：

```js
"use strict";
function sum(a = 1, b = 2) {
  return a + b;
}
```

### 函数表达式

函数表达式可以使用另一种解决方法：

```js
var sum = function sum([a, b]) {
  // SyntaxError: "use strict" not allowed in function with destructuring parameter
  "use strict";
  return a + b;
};
```

这可以转换为以下表达式：

```js
var sum = (function () {
  "use strict";
  return function sum([a, b]) {
    return a + b;
  };
})();
```

### 箭头函数

如果箭头函数需要访问 `this`，则可以将箭头函数作为封闭函数来使用：

```js
var callback = (...args) => {
  // SyntaxError: "use strict" not allowed in function with rest parameter
  "use strict";
  return this.run(args);
};
```

这可以转换为以下表达式：

```js
var callback = (() => {
  "use strict";
  return (...args) => {
    return this.run(args);
  };
})();
```

参见
--

* Strict mode
* 函数语句
* 函数表达式
* 默认参数
* 剩余参数
* 解构参数


TypeError: "x" is (not) "y" - JavaScript

TypeError: "x" is (not) "y"
===========================

JavaScript 异常“*x* is (not) *y*”在出现与期望不符的类型（通常为意外获得的 `undefined` 或 `null` 值）时被抛出。

消息
--
```

TypeError: Cannot read properties of undefined (reading 'x') (V8-based)
TypeError: "x" is undefined (Firefox)
TypeError: "undefined" is not an object (Firefox)
TypeError: undefined is not an object (evaluating 'obj.x') (Safari)

TypeError: "x" is not a symbol (V8-based & Firefox)
TypeError: Symbol.keyFor requires that the first argument be a symbol (Safari)
```

错误类型
----

`TypeError`。

什么地方出错了？
--------

出现了与期望不符的类型。这个错误常常由 `undefined` 或 `null` 值引起。

此外，某些方法，例如 `Object.create()` 或 `Symbol.keyFor()`，要求必须提供特定类型的参数。

示例
--

### 错误情形

```js
// undefined and null cases on which the substring method won't work
const foo = undefined;
foo.substring(1); // TypeError: foo is undefined

const foo = null;
foo.substring(1); // TypeError: foo is null

// Certain methods might require a specific type
const foo = {};
Symbol.keyFor(foo); // TypeError: foo is not a symbol

const foo = "bar";
Object.create(foo); // TypeError: "foo" is not an object or null
```

### 解决方法

要解决值为 `undefined` 或 `null` 的空指针问题，你可以首先测试值是否为 `undefined` 或 `null`。

```js
if (foo !== undefined && foo !== null) {
  // Now we know that foo is defined, we are good to go.
}
```

或者，你如果能确定 `foo` 的值不会是其他的假值（如：`""` 或 `0`），或者排除这些情况不是问题，那你可以简单地测试其是否为真。

```js
if (foo) {
  // Now we know that foo is truthy, it will necessarily not be null/undefined.
}
```

参见
--

* `undefined`
* `null`