赋值（=） - JavaScript

赋值（=）
=====

简单赋值运算符（`=`）用于给变量赋值。赋值表达式本身的值为要赋值的值。为了将一个值赋给多个变量，可以链式使用赋值运算符。

尝试一下
----

语法
--

```js
x = y
```

示例
--

### 简单赋值和链式赋值

```js
// 假设已经存在以下变量
// x = 5
// y = 10
// z = 25

x = y; // x 为 10
x = y = z; // x, y 都为 25
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* JS 指南中的赋值运算符


yield - JavaScript

yield
=====

`yield` 关键字用于暂停和恢复生成器函数。

尝试一下
----

语法
--
```

[rv] = yield [expression];
```

`expression`

定义通过迭代器协议从生成器函数返回的值。如果省略，则返回 `undefined`。

`rv`

返回传递给生成器的 `next()` 方法的可选值，以恢复其执行。

描述
--

`yield` 关键字使生成器函数执行暂停，`yield` 关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的 `return` 关键字。

`yield` 关键字实际返回一个 `IteratorResult` 对象，它有两个属性，`value` 和 `done`。`value` 属性是对 `yield` 表达式求值的结果，而 `done` 是 `false`，表示生成器函数尚未完全完成。

一旦遇到 `yield` 表达式，生成器的代码将被暂停运行，直到生成器的 `next()` 方法被调用。每次调用生成器的 `next()` 方法时，生成器都会恢复执行，直到达到以下某个值：

* `yield`，导致生成器再次暂停并返回生成器的新值。下一次调用 `next()` 时，在 `yield` 之后紧接着的语句继续执行。
* `throw` 用于从生成器中抛出异常。这让生成器完全停止执行，并在调用者中继续执行，正如通常情况下抛出异常一样。
* 到达生成器函数的结尾。在这种情况下，生成器的执行结束，并且 `IteratorResult` 给调用者返回 `value` 的值是 `undefined` 并且 `done` 为 `true`。
* 到达 `return` 语句。在这种情况下，生成器的执行结束，并将 `IteratorResult` 返回给调用者，其 `value` 的值是由 `return` 语句指定的，并且 `done` 为 `true`。

如果将参数传递给生成器的 `next()` 方法，则该值将成为生成器当前 `yield` 操作返回的值。

在生成器的代码路径中的 `yield` 运算符，以及通过将其传递给 `Generator.prototype.next()` 指定新的起始值的能力之间，生成器提供了强大的控制力。

**警告：** 不幸地是，`next()` 是不对称的，但这并不是没有帮助：它总是向当前暂停的 `yield` 发送一个值，但是返回的是 `yield` 之后表达式的运算结果。

示例
--

### 使用 yield

以下代码是一个生成器函数的声明。

```js
function\* countAppleSales() {
  const saleList = [3, 7, 5];
  for (let i = 0; i < saleList.length; i++) {
    yield saleList[i];
  }
}
```

一旦生成器函数已定义，可以通过构造一个迭代器来使用它。

```js
const appleStore = countAppleSales(); // Generator { }
console.log(appleStore.next()); // { value: 3, done: false }
console.log(appleStore.next()); // { value: 7, done: false }
console.log(appleStore.next()); // { value: 5, done: false }
console.log(appleStore.next()); // { value: undefined, done: true }
```

你也可以将带有 `next(value)` 的值发送给生成器。在 `rv = yield expression` 这个语法中，`step` 用于接收返回值——尽管第一次调用 `next()` 时忽略传递给生成器 `next()` 方法的值。

```js
function\* counter(value) {
  let step;

  while (true) {
    step = yield value++;
    if (step) {
      value += step;
    }
  }
}

const generatorFunc = counter(0);
console.log(generatorFunc.next().value); // 0
console.log(generatorFunc.next().value); // 1
console.log(generatorFunc.next().value); // 2
console.log(generatorFunc.next().value); // 3
console.log(generatorFunc.next(10).value); // 14
console.log(generatorFunc.next().value); // 15
console.log(generatorFunc.next(10).value); // 26
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-YieldExpression |

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

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

参见
--

* 迭代协议
* `function*`
* `function*` 表达式
* `yield*`


new.target - JavaScript

new.target
==========

 **`new.target`** 属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中，`new.target`返回一个指向构造方法或函数的引用。在普通的函数调用中，`new.target` 的值是`undefined`。语法
--
```

new.target
```

描述
--

`new.target`语法由一个关键字"`new`"，一个点，和一个属性名"`target`"组成。通常"`new.`"`的`作用是提供属性访问的上下文，但这里"`new.`"其实不是一个真正的对象。不过在构造方法调用中，`new.target`指向被`new`调用的构造函数，所以"`new.`"成为了一个虚拟上下文。

`new.target`属性适用于所有函数访问的元属性。在 arrow functions 中，`new.target` 指向最近的外层函数的`new.target`（An arrow function expression does not have its own this, arguments, super , or new.target) 。

示例
--

### 函数调用中的 new.target

在普通的函数调用中（和作为构造函数来调用相对），`new.target`的值是`undefined`。这使得你可以检测一个函数是否是作为构造函数通过new被调用的。

```js
function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
```

### 构造方法中的 new.target

在类的构造方法中，`new.target`指向直接被`new`执行的构造函数。并且当一个父类构造方法在子类构造方法中被调用时，情况与之相同。

```js
class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A {
  constructor() {
    super();
  }
}

var a = new A(); // logs "A"
var b = new B(); // logs "B"

class C {
  constructor() {
    console.log(new.target);
  }
}
class D extends C {
  constructor() {
    super();
  }
}

var c = new C(); // logs class C{constructor(){console.log(new.target);}}
var d = new D(); // logs class D extends C{constructor(){super();}}
```

从上面类 C 和 D 的例子可以看出来，new.target 指向的是初始化类的类定义。比如当 D 通过 new 初始化的时候，打印出了 D 的类定义，C 的例子与之类似，打印出的是 C 的类定义。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-built-in-function-objects |

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

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

参见
--

* 函数
* 类
* `new`
* `this`


void 运算符 - JavaScript

void 运算符
========

**`void` 运算符**对给定的表达式进行求值，然后返回 `undefined`。

尝试一下
----

语法
--

```js
void expression
```

描述
--

这个运算符允许在表达式执行完成时，产生（某些地方）期望获得的 `undefined` 值。

`void` 运算符通常只用于获取 `undefined` 的原始值，一般使用 `void(0)`（等同于 `void 0`）。在上述情况中，也可以使用全局变量 `undefined` 来代替。

需要注意考虑 `void` 运算符的优先级，以下加括号的表达式的例子可以帮助你清楚地理解 `void` 操作符的优先级：

```js
void 2 === "2"; // (void 2) === '2'，返回 false
void (2 === "2"); // void (2 === '2')，返回 undefined
```

示例
--

### 立即调用的函数表达式

在使用立即调用的函数表达式时，`function` 关键字不可直接位于语句开头，因为该表达式会被解析为函数声明，并会在解析到代表调用的括号时产生语法错误。如果是匿名函数，那么如果函数被解析为声明，就会立即产生语法错误。

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

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

为了使函数被解析为表达式，`function` 关键字必须出现在一个只接受表达式而不是语句的位置。这可以通过在关键字前加一个一元运算符来实现，它只接受表达式作为操作数。函数调用的优先级比一元运算符高，所以它将被首先执行。它的返回值（几乎总是 `undefined`）将被传递给一元运算符，然后立即被丢弃。

在所有的一元运算符中，`void` 提供了最好的语义，因为它明确表示函数调用的返回值应该被丢弃。

```js
void (function () {
  console.log("Executed!");
})();

// Output: "Executed!"
```

这比用圆括号包裹函数表达式要长一些，圆括号的作用是强迫 `function` 关键字被解析为表达式的开始，而不是语句。

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

### JavaScript URI

当用户点击一个以 `javascript:` 开头的 URI 时，它会执行 URI 中的代码，然后用返回的值替换页面内容，除非返回的值是 `undefined`。`void` 运算符可用于返回 `undefined`。例如：

```html
<a href="javascript:void(0);"> 这个链接点击之后不会做任何事情 </a>

<a href="javascript:void(document.body.style.backgroundColor='green');">
  点击这个链接会让页面背景变成绿色。
</a>
```

**备注：** 利用 `javascript:` 伪协议来执行 JavaScript 代码是不推荐的，推荐的做法是为链接元素绑定事件。

### 在箭头函数中避免泄漏

箭头函数标准中，允许在函数体不使用括号来直接返回值。如果右侧调用了一个原本没有返回值的函数，其返回值改变后，则会导致非预期的副作用。安全起见，当函数返回值不会被使用到的时候，应该使用 `void` 运算符，来确保 API 改变时，并不会改变箭头函数的行为。

```js
button.onclick = () => void doSomething();
```

这确保了当 `doSomething` 的返回值从 `undefined` 变为 `true` 的时候，不会改变代码的行为。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-void-operator |

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

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

参见
--

* `undefined`


逻辑非（!） - JavaScript

逻辑非（!）
======

**逻辑非**（`!`，逻辑连接取反）运算符将真值或假值转换为对应的相反值，经常用于布尔（逻辑）值。当与非布尔值使用时，如果其操作数可以转化为 `true`，则返回 `false`，否则返回 `true`。

尝试一下
----

语法
--

```js
!expr
```

描述
--

如果其操作数可以转化为 `true`，则返回 `false`，否则返回 `true`。

能够转化为 `true` 的值叫做真值，能够转化为 `false` 的值叫做假值。

能够转化为 false 的表达式的示例如下：

* `null`；
* `NaN`；
* `0`；
* 空字符串（`""` 或 `''` 或 ````）；
* `undefined`。

尽管 `!` 运算符可以与非布尔操作数一起使用，但它仍然被认为是一个布尔运算符，因为它的返回值总是可以被转换为布尔基本类型。要明确地将其返回值（或任何一般的表达式）转换为相应的布尔值，请使用双非运算符（`!!`）或 `Boolean` 构造函数。

示例
--

### 使用非运算符

下列代码展示了 `!`（逻辑非）运算符的用法。

```js
!true; // !t returns false
!false; // !f returns true
!""; // !f returns true
!"Cat"; // !t returns false
```

### 双非运算符（`!!`）

可以使用几个非运算符串联起来，明确地强制将任何值转换为相应的布尔基本类型。这种转换是基于值的“真实性”或“虚假性”（详见真值和假值）。

也可以使用 `Boolean` 函数完成转换。

```js
!!true; // !!truthy returns true
!!{}; // !!truthy returns true: any object is truthy...
!!new Boolean(false); // ...even Boolean objects with a false .valueOf()!
!!false; // !!falsy returns false
!!""; // !!falsy returns false
!!Boolean(false); // !!falsy returns false
```

### 非运算符转换

下述涉及到**布尔值**的运算：

```js
!!bCondition
```

总是与此表达式相同：

```js
bCondition
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-logical-not-operator |

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

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

参见
--

* `Boolean`
* 真值
* 假值


不相等（!=） - JavaScript

不相等（!=）
=======

不相等运算符（`!=`）检查其两个操作数是否不相等，并返回布尔结果。与严格不相等运算符不同，它会转换并比较不同类型的操作数。

尝试一下
----

语法
--

```js
x != y
```

描述
--

不等式运算符检查其操作数是否不相等。这是相等运算符的否定，因此以下两行将始终给出相同的结果：

```js
x != y;

!(x == y);
```

有关比较算法的详细信息，请参见相等运算符的页面。

与相等运算符一样，不相等运算符将尝试转换和比较不同类型的操作数：

```js
3 != "3"; // false
```

为避免这种情况，并要求将不同类型视为不同，请使用严格不相等运算符：

```js
3 !== "3"; // true
```

示例
--

### 没有类型转换的比较

```js
1 != 2; // true
"hello" != "hola"; // true

1 != 1; // false
"hello" != "hello"; // false
```

### 有类型转换的比较

```js
"1" != 1; // false
1 != "1"; // false
0 != false; // false
0 != null; // true
0 != undefined; // true
0 != !!null; // false，看看逻辑非运算符
0 != !!undefined; // false，看看逻辑非运算符
null != undefined; // false

const number1 = new Number(3);
const number2 = new Number(3);
number1 != 3; // false
number1 != number2; // true
```

### 比较对象

```js
const object1 = {
  key: "value",
};

const object2 = {
  key: "value",
};

console.log(object1 != object2); // true
console.log(object1 != object1); // false
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-equality-operators |

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

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

参见
--

* 相等运算符
* 严格相等运算符
* 严格不相等运算符


乘法（*） - JavaScript

乘法（\*）
======

**乘法**（**`*`**）运算符计算操作数的乘积。

尝试一下
----

语法
--

```js
x \* y
```

示例
--

### 数值乘法

```js
2 \* 2; // 4
-2 \* 2; // -4
```

### 与 Infinity 相乘

```js
Infinity \* 0; // NaN
Infinity \* Infinity; // Infinity
```

### 非数值乘法

```js
"foo" \* 2; // NaN
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-multiplicative-operators |

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

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

参见
--

* 加法运算符
* 减法运算符
* 除法运算符
* 取余运算符
* 幂运算符
* 自增运算符
* 自减运算符
* 一元减运算符
* 一元加运算符


await - JavaScript

await
=====

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

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

* Learn more
* See full compatibility
* Report feedback
`await` 操作符用于等待一个 `Promise` 兑现并获取它兑现之后的值。它只能在异步函数或者模块顶层中使用。

语法
--

```js
await expression;
```

### 参数

`expression`

要等待的 `Promise` 实例，Thenable 对象，或任意类型的值。

### 返回值

返回从 `Promise` 实例或 thenable 对象取得的处理结果。如果等待的值不符合 thenable，则返回表达式本身的值。

### 异常

拒绝（reject）的原因会被作为异常抛出。

描述
--

`await` 通常用于拆开 promise 的包装，使用方法是传递一个 `Promise` 作为 `expression`。使用 `await` 总会暂停当前异步函数的执行，在该 `Promise` 敲定（settled，指兑现或拒绝）后继续执行。函数的执行恢复（resume）时，`await` 表达式的值已经变成了 `Promise` 兑现的值。

若该 `Promise` 被拒绝（rejected），`await` 表达式会把拒绝的原因（reason）抛出。当前函数（`await` 所在的函数）会出现在抛出的错误的栈追踪（stack trace），否则当前函数就不会在栈追踪出现。

`await` 总会同步地对表达式求值并处理，处理的行为与 `Promise.resolve()` 一致，不属于原生 `Promise` 的值全都会被隐式地转换为 `Promise` 实例后等待。处理的规则为，若表达式：

* 是一个原生 `Promise`（原生`Promise` 的实例或其派生类的实例，且满足 `expression.constructor === Promise`），会被直接用于等待，等待由原生代码实现，该对象的 `then()` 不会被调用。
* 是 thenable 对象（包括非原生的 `Promise` 实例、polyfill、Proxy、派生类等），会构造一个新 `Promise` 用于等待，构造时会调用该对象的 `then()` 方法。
* 不是 thenable 对象，会被包装进一个已兑现的 `Promise` 用于等待，其结果就是表达式的值。
示例
--

### 等待 Promise 的兑现

当一个 `Promise` 被传递给 `await` 操作符，`await` 将等待该 `Promise` 兑现，并在兑现后返回该 `Promise` 兑现的值。

```js
function resolveAfter2Seconds(x) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  let x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}

f1();
```

### 转换为 promise

若表达式的值不是 `Promise`，`await` 会把该值转换为已兑现的 `Promise`，然后返回其结果。

```js
async function f3() {
  const y = await 20;
  console.log(y); // 20

  const obj = {};
  console.log((await obj) === obj); // true
}

f3();
```

### promise 被拒绝

如果 `Promise` 被拒绝，则抛出拒绝的原因。

```js
async function f4() {
  try {
    const z = await Promise.reject(30);
  } catch (e) {
    console.error(e); // 30
  }
}

f4();
```

### 处理被拒绝的 promise

你可以链式调用 `catch()`（而不是使用 `try`）以在等待 promise 兑现之前处理被拒绝的 promise。

```js
const response = await promisedFunction().catch((err) => {
  console.error(err);
  return "default response";
});
// response will be "default response" if the promise is rejected
```

### 在顶层使用 await

在模块的顶层，你可以单独使用关键字 `await`（异步函数的外面）。也就是说一个模块如果包含用了 `await` 的子模块，该模块就会等待该子模块，这一过程并不会阻塞其他子模块。

下面是一个在 `export` 表达式中使用了 Fetch API 的例子。任何文件只要导入这个模块，后面的代码就会等待，直到 fetch 完成。

```js
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());

export default await colors;
```

### await 对执行过程的影响

当函数执行到 `await` 时，被等待的表达式会立即执行，所有依赖该表达式的值的代码会被暂停，并推送进微任务队列（microtask queue）。然后主线程被释放出来，用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise，也会发生这种情况。例如，考虑以下代码：

```js
async function foo(name) {
  console.log(name, "start");
  console.log(name, "middle");
  console.log(name, "end");
}

foo("First");
foo("Second");

// First start
// First middle
// First end
// Second start
// Second middle
// Second end
```

对应到 `Promise` 的写法是：

```js
function foo(name) {
  return new Promise((resolve) => {
    console.log(name, "start");
    console.log(name, "middle");
    console.log(name, "end");
    resolve();
  });
}
```

执行到 `await` 时，后面的代码就会整体被安排进一个新的微任务，此后的函数体变为异步执行。

```js
async function foo(name) {
  console.log(name, "start");
  await console.log(name, "middle");
  console.log(name, "end");
}

foo("First");
foo("Second");

// First start
// First middle
// Second start
// Second middle
// First end
// Second end
```

对应的 `Promise` 写法是：

```js
function foo(name) {
  return new Promise((resolve) => {
    console.log(name, "start");
    resolve(console.log(name, "middle"));
  }).then(() => {
    console.log(name, "end");
  });
}
```

虽然这里的 `then()` 看起来很多余，其中的代码完全可以被合并到构造器的回调里，但不管该 `Promise` 的状态如何，`then()` 的回调**总会被异步执行**，`await` 的行为也一样。因此，只要情况不是必须或可能需要等待 `Promise` 的结果，就应该避免使用 `await`。

其他微任务能在函数执行恢复之前执行：

```js
let i = 0;

queueMicrotask(function test() {
  i++;
  console.log("microtask", i);
  if (i < 3) {
    queueMicrotask(test);
  }
});

(async () => {
  console.log("async function start");
  for (let i = 1; i < 3; i++) {
    await null;
    console.log("async function resume", i);
  }
  await null;
  console.log("async function end");
})();

queueMicrotask(() => {
  console.log("queueMicrotask() after calling async function");
});

console.log("script sync part end");

// Logs:
// async function start
// script sync part end
// microtask 1
// async function resume 1
// queueMicrotask() after calling async function
// microtask 2
// async function resume 2
// microtask 3
// async function end
```

此案例中，`test()` 总会在异步函数恢复执行前被调用，呈现轮流的调度。微任务被执行的顺序通常就是入队的先后顺序，而 `console.log("queueMicrotask() after calling async function");` 比 `await` 晚入队，因此 `"queueMicrotask() after calling async function"` 在异步函数第一次恢复之后才输出。

### 改善栈追踪

有时，当异步函数直接返回一个 `Promise` 时我们会省略 `await`。

```js
async function noAwait() {
  // Some actions...

  return /\* await \*/ lastAsyncTask();
}
```

但是假如这个 `Promise` 的由来是调用了异步函数，且该异步函数的异步部分抛出了错误：

```js
async function lastAsyncTask() {
  await null;
  throw new Error("failed");
}

async function noAwait() {
  return lastAsyncTask();
}

noAwait();

// Error: failed
// at lastAsyncTask
```

栈追踪中只出现了 `lastAsyncTask`，这是因为抛出错误时 `noAwait` 已经返回——某种意义上该 `Promise` 已经与 `noAwait` 无关。若要改善栈追踪，你可以用 `await` 提前等待，错误就会在函数体结束前抛出，接着该错误会被包装进一个新的 `Promise`，因错误被 `await` 在主调函数的函数体抛出，主调函数将会出现在栈追踪。

```js
async function lastAsyncTask() {
  await null;
  throw new Error("failed");
}

async function withAwait() {
  return await lastAsyncTask();
}

withAwait();

// Error: failed
// at lastAsyncTask
// at async withAwait
```

但是，这样会有一点性能牺牲，毕竟 `Promise` 会被拆装了又再次包装。

规范
--

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

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

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

参见
--

* `async 函数`
* `async 函数表达式`
* `AsyncFunction` 对象
* v8.dev 上的 Top level await


小于等于（<=） - JavaScript

小于等于（<=）
========

小于等于运算符（`<=`）在左操作数小于等于右操作数时返回 `true`，否则返回 `false`。

尝试一下
----

语法
--

```js
x <= y
```

描述
--

操作数比较使用与小于运算符相同的算法，除了相等的值（在尝试转换后）会返回 `true`。

示例
--

### 字符串与字符串比较

```js
console.log("a" <= "b"); // true
console.log("a" <= "a"); // true
console.log("a" <= "3"); // false
```

### 字符串与数值比较

```js
console.log("5" <= 3); // false
console.log("3" <= 3); // true
console.log("3" <= 5); // true

console.log("hello" <= 5); // false
console.log(5 <= "hello"); // false
```

### 数值与数值比较

```js
console.log(5 <= 3); // false
console.log(3 <= 3); // true
console.log(3 <= 5); // true
```

### 数值与大整型比较

```js
console.log(5n <= 3); // false
console.log(3 <= 3n); // true
console.log(3 <= 5n); // true
```

### 比较 Boolean、null、undefined 和 NaN

```js
console.log(true <= false); // false
console.log(true <= true); // true
console.log(false <= true); // true

console.log(true <= 0); // false
console.log(true <= 1); // true

console.log(null <= 0); // true
console.log(1 <= null); // false

console.log(undefined <= 3); // false
console.log(3 <= undefined); // false

console.log(3 <= NaN); // false
console.log(NaN <= 3); // false
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-relational-operators |

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

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

参见
--

* 大于运算符
* 大于等于运算符
* 小于运算符


运算符优先级 - JavaScript

运算符优先级
======

**运算符的优先级**决定了表达式中运算执行的先后顺序。优先级高的运算符会作为优先级低的运算符的操作数。

尝试一下
----

优先级和结合性
-------

考虑由下面的表示法描述的表达式。其中，OP1 和 OP2 都是操作符的占位符。

```js
a OP1 b OP2 c
```

如果 `OP1` 和 `OP2` 具有不同的优先级（见下表），则优先级最高的运算符先执行，不用考虑结合性。观察乘法如何具有比加法更高的优先级并首先执行，即使加法是首先写入代码的。

```js
console.log(3 + 10 \* 2); // 输出 23
console.log(3 + (10 \* 2)); // 输出 23 因为这里的括号是多余的
console.log((3 + 10) \* 2); // 输出 26 因为括号改变了优先级
```

左结合（左到右）相当于把左边的子表达式加上小括号 `(a OP b) OP c`，右结合（右到左）相当于 `a OP (b OP c)`。赋值运算符是右结合的，所以你可以这么写：

```js
a = b = 5; // 相当于 a = (b = 5);
```

预期结果是 `a` 和 `b` 的值都会成为 5。这是因为赋值运算符的返回结果就是赋值运算符右边的那个值，具体过程是：首先 `b` 被赋值为 5，然后 `a` 也被赋值为 `b = 5` 的返回值，也就是 5。

另一个例子是，只有幂运算符是右结合的，而其他算术运算符都是左结合的。有趣的是，无论结合性和优先级如何，求值顺序总是从左到右。

|  |  |
| --- | --- |
| 代码 | 输出 |
|
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的除法运算符 (/)
console.log(echo("left", 6) / echo("right", 2));
```

 |
```
Evaluating the left side
Evaluating the right side
3
```

 |
|
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的幂运算符 (\*\*)
console.log(echo("left", 2) \*\* echo("right", 3));
```
 |
```
Evaluating the left side
Evaluating the right side
8
```
 |

当有多个具有相同优先级的运算符时，结合性的差异就会发挥作用。仅使用一个或多个不同优先级的运算符时，结合性不会影响输出，如上面的例子所示。在下面的示例中，观察使用多个相同运算符时结合性会如何影响输出。

|  |  |
| --- | --- |
| 代码 | 输出 |
|
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的除法运算符 (/)
console.log(echo("left", 6) / echo("middle", 2) / echo("right", 3));
```

 |
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
1
```

 |
|
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的幂运算符 (\*\*)
console.log(echo("left", 2) \*\* echo("middle", 3) \*\* echo("right", 2));
```

 |
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
512
```

 |
|
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里左边和中间的被圆括号包围的求幂表达式
console.log((echo("left", 2) \*\* echo("middle", 3)) \*\* echo("right", 2));
```
 |
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
64
```
 |

观察上面的代码片段，`6 / 3 / 2` 与 `(6 / 3) / 2` 是相同的，因为除法是左结合的。而幂运算符是右结合的，所以 `2 ** 3 ** 2` 与 `2 ** (3 ** 2)` 是相同的。因此，`(2 ** 3) ** 2` 会更改执行顺序，并导致输出上表中的 64。

请记住，判断执行顺序的时候，优先级在结合性之前。所以，混合求除法和幂时，求幂会先于除法。例如，`2 ** 3 / 3 ** 2` 的结果是 0.8888888888888888，因为它相当于 `(2 ** 3) / (3 ** 2)`。

### 分组和短路的注意事项

在下表中，**分组（Grouping）** 具有最高优先级。然而，这并不意味着总是优先对分组符号 `( … )` 内的表达式进行求值，尤其是涉及短路时。

短路是条件求值的术语。例如，在表达式 `a && (b + c)` 中，如果 `a` 为虚值（falsy），那么即使 `(b + c)` 在圆括号中，也不会被求值。我们可以说逻辑或运算符（“OR”）是“短路的”。除了逻辑或运算符外，其他短路运算符还包括逻辑与（“AND”）、空值合并、可选链和条件（三元）运算符。下面有更多例子：

```js
a || b \* c; // 首先对 `a` 求值，如果 `a` 为真值则直接返回 `a`
a && b < c; // 首先对 `a` 求值，如果 `a` 为虚值则直接返回 `a`
a ?? (b || c); // 首先对 `a` 求值，如果 `a` 不是 `null` 或 `undefined` 则直接返回 `a`
a?.b.c; // 首先对 `a` 求值，如果 `a` 是 `null` 或 `undefined` 则直接返回 `undefined`
```

示例
--

```js
3 > 2 && 2 > 1;
// 返回 true

3 > 2 > 1;
// 返回 false，因为 3 > 2 是 true，然后 true 会在比较运算符中
// 被隐式转换为 1，因此 true > 1 会变为 1 > 1，结果是 false
// 加括号可以更清楚：(3 > 2) > 1
```

汇总表
---

下面的表格将所有运算符按照优先级的不同从高（19）到低（1）排列。

请注意，下表中故意不包含展开语法——原因可以引用 Stack Overflow 上的一个回答，“展开语法不是一个运算符，因此没有优先级。它是数组字面量和函数调用（和对象字面量）语法的一部分。”

| 优先级 | 运算符类型 | 结合性 | 运算符 |
| --- | --- | --- | --- |
| 19 | 分组 | n/a（不相关） | `( … )` |
| 18 | 成员访问 | 从左到右 | `… . …` |
| 需计算的成员访问 | 从左到右 | `… [ … ]` |
| `new`（带参数列表） | n/a | `new … ( … )` |
| 函数调用 | 从左到右 | `… ( … )` |
| 可选链（Optional chaining） | 从左到右 | `?.` |
| 17 | `new`（无参数列表） | 从右到左 | `new …` |
| 16 | 后置递增 | n/a | `… ++` |
| 后置递减 | `… --` |
| 15 | 逻辑非 (!) | 从右到左 | `! …` |
| 按位非 (~) | `~ …` |
| 一元加法 (+) | `+ …` |
| 一元减法 (-) | `- …` |
| 前置递增 | `++ …` |
| 前置递减 | `-- …` |
| `typeof` | `typeof …` |
| `void` | `void …` |
| `delete` | `delete …` |
| `await` | `await …` |
| 14 | 幂 (\*\*) | 从右到左 | `… ** …` |
| 13 | 乘法 (\*) | 从左到右 | `… * …` |
| 除法 (/) | `… / …` |
| 取余 (%) | `… % …` |
| 12 | 加法 (+) | 从左到右 | `… + …` |
| 减法 (-) | `… - …` |
| 11 | 按位左移 (<<) | 从左到右 | `… << …` |
| 按位右移 (>>) | `… >> …` |
| 无符号右移 (>>>) | `… >>> …` |
| 10 | 小于 (<) | 从左到右 | `… < …` |
| 小于等于 (<=) | `… <= …` |
| 大于 (>) | `… > …` |
| 大于等于 (>=) | `… >= …` |
| `in` | `… in …` |
| `instanceof` | `… instanceof …` |
| 9 | 相等 (==) | 从左到右 | `… == …` |
| 不相等 (!=) | `… != …` |
| 一致/严格相等 (===) | `… === …` |
| 不一致/严格不相等 (!==) | `… !== …` |
| 8 | 按位与 (&) | 从左到右 | `… & …` |
| 7 | 按位异或 (^) | 从左到右 | `… ^ …` |
| 6 | 按位或 (|) | 从左到右 | `… | …` |
| 5 | 逻辑与 (&&) | 从左到右 | `… && …` |
| 4 | 逻辑或 (||) | 从左到右 | `… || …` |
| 空值合并 (??) | 从左到右 | `… ?? …` |
| 3 | 条件（三元）运算符 | 从右到左 | `… ? … : …` |
| 2 | 赋值 | 从右到左 | `… = …` |
| `… += …` |
| `… -= …` |
| `… **= …` |
| `… *= …` |
| `… /= …` |
| `… %= …` |
| `… <<= …` |
| `… >>= …` |
| `… >>>= …` |
| `… &= …` |
| `… ^= …` |
| `… |= …` |
| `… &&= …` |
| `… ||= …` |
| `… ??= …` |
| 1 | 逗号 / 序列 | 从左到右 | `… , …` |


圆括号运算符 - JavaScript

圆括号运算符
======

圆括号运算符 `( )` 用于控制表达式中的运算优先级。

尝试一下
----

语法
--

```js
(expression)
```

说明
--

圆括号运算符由一对圆括号组成，包裹表达式和子表达式用来覆盖常规的运算符优先级，达到低优先级的表达式比高优先级的表达式更早运算。

示例
--

下面的代码展示了加法运算先于乘法运算的情况。

```js
const a = 1;
const b = 2;
const c = 3;

// 默认优先级
a + b \* c; // 7
// 默认运算顺序同下
a + (b \* c); // 7

// 现在优先考虑乘法之前的加法
(a + b) \* c; // 9

// 这相当于
a \* c + b \* c; // 9
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-grouping-operator |

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

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

参见
--

* 运算符优先级
* `delete`
* `typeof`


按位非（~） - JavaScript

按位非（~）
======

按位非运算符（`~`）将操作数的位反转。如同其他位运算符一样，它将操作数转化为 32 位的有符号整型。

尝试一下
----

语法
--

```js
~a
```

描述
--

操作数被转换为 32 位有符号整型，并以一系列比特（0 和 1）表示。超过 32 位的数字将丢弃其最高有效位。如下例子中，超过 32 位的整数将转换为 32 位整数：
```

Before: 11100110111110100000000000000110000000000001
After:              10100000000000000110000000000001
```

运算结果的每一位数都由操作数相应位反转而来。

`NOT` 运算的真值表如下所示：

| a | NOT a |
| --- | --- |
| 0 | 1 |
| 1 | 0 |
```

 9 (base 10) = 00000000000000000000000000001001 (base 2)
               --------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)
```

32 位有符号整数操作数根据补码运算规则进行反转，也就是说，最高有效位表示负数。

按位非运算时，任何数字 `x` 的运算结果都是 `-(x + 1)`。例如，`~-5` 运算结果为 `4`。

请注意，由于数字 `~-1` 和 `~4294967295`（232 - 1）均使用 32 位表示形式，它们的运算结果均为 `0`。

示例
--

### 使用按位取反

```js
~0; // -1
~-1; // 0
~1; // -2
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-bitwise-not-operator |

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

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

参见
--

* JavaScript 指南：位运算符


严格不相等（!==） - JavaScript

严格不相等（!==）
==========

严格不相等运算符（`!==`）检查它的两个对象是否不相等，返回一个布尔结果。与不相等运算符不同，严格不相等运算符总是认为不同类型的对象是不同的。

尝试一下
----

语法
--

```js
x !== y
```

描述
--

严格不相等运算符检查其对象是否不相等。它是严格相等运算符的否定，因此下面两行总是会给出相同的结果：

```js
x !== y;

!(x === y);
```

有关比较算法的详细信息，请参阅严格相等运算符的页面。

与严格相等运算符一样，严格不相等算符始终认为不同类型的对象是不同的：

```js
3 !== "3"; // true
```

示例
--

### 比较相同类型的对象

```js
"hello" !== "hello"; // false
"hello" !== "hola"; // true

3 !== 3; // false
3 !== 4; // true

true !== true; // false
true !== false; // true

null !== null; // false
```

### 比较不同类型的操作数

```js
"3" !== 3; // true
true !== 1; // true
null !== undefined; // true
```

### 比较对象

```js
const object1 = {
  key: "value",
};

const object2 = {
  key: "value",
};

console.log(object1 !== object2); // true
console.log(object1 !== object1); // false
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-equality-operators |

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

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

参见
--

* 相等运算符
* 不相等运算符
* 严格相等运算符


typeof - JavaScript

typeof
======

**`typeof`** 运算符返回一个字符串，表示操作数的类型。

尝试一下
----

语法
--

```js
typeof operand
```

### 参数

`operand`

表示要返回类型的对象或基本类型的表达式。

描述
--

下表总结了 `typeof` 可能的返回值。有关类型和基本类型的更多信息，可查看 JavaScript 数据结构 页面。

| 类型 | 结果 |
| --- | --- |
| Undefined | `"undefined"` |
| Null | `"object"`（原因） |
| Boolean | `"boolean"` |
| Number | `"number"` |
| BigInt | `"bigint"` |
| String | `"string"` |
| Symbol | `"symbol"` |
| Function（在 ECMA-262 中实现 [[Call]]；classes也是函数) | `"function"` |
| 其他任何对象 | `"object"` |

这个值列表是详尽的。没有符合规范的引擎曾被报告过产生（或历史上产生过）除列出的值之外的值。在规范删除 `typeof` 为不可调用的非标准外来对象返回实现定义的字符串的行为之前，旧的 Internet Explorer 是已知的唯一一个实现额外返回值的浏览器。

示例
--

### 基本用法

```js
// 数值
typeof 37 === "number";
typeof 3.14 === "number";
typeof 42 === "number";
typeof Math.LN2 === "number";
typeof Infinity === "number";
typeof NaN === "number"; // 尽管它是 "Not-A-Number" (非数值) 的缩写
typeof Number(1) === "number"; // Number 会尝试把参数解析成数值
typeof Number("shoe") === "number"; // 包括不能将类型强制转换为数字的值

typeof 42n === "bigint";

// 字符串
typeof "" === "string";
typeof "bla" === "string";
typeof `template literal` === "string";
typeof "1" === "string"; // 注意内容为数字的字符串仍是字符串
typeof typeof 1 === "string"; // typeof 总是返回一个字符串
typeof String(1) === "string"; // String 将任意值转换为字符串，比 toString 更安全

// 布尔值
typeof true === "boolean";
typeof false === "boolean";
typeof Boolean(1) === "boolean"; // Boolean() 会基于参数是真值还是虚值进行转换
typeof !!1 === "boolean"; // 两次调用 !（逻辑非）运算符相当于 Boolean()

// Symbols
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";

// Undefined
typeof undefined === "undefined";
typeof declaredButUndefinedVariable === "undefined";
typeof undeclaredVariable === "undefined";

// 对象
typeof { a: 1 } === "object";

// 使用 Array.isArray 或者 Object.prototype.toString.call
// 区分数组和普通对象
typeof [1, 2, 4] === "object";

typeof new Date() === "object";
typeof /regex/ === "object";

// 下面的例子令人迷惑，非常危险，没有用处。避免使用它们。
typeof new Boolean(true) === "object";
typeof new Number(1) === "object";
typeof new String("abc") === "object";

// 函数
typeof function () {} === "function";
typeof class C {} === "function";
typeof Math.sin === "function";
```

### typeof null

```js
// JavaScript 诞生以来便如此
typeof null === "object";
```

在 JavaScript 最初的实现中，JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 `null` 代表的是空指针（大多数平台下值为 0x00），因此，null 的类型标签是 0，`typeof null` 也因此返回 `"object"`。（参考来源）

曾有一个 ECMAScript 的修复提案（通过选择性加入的方式），但被拒绝了。该提案会导致 `typeof null === 'null'`。

### 使用 new 操作符

所有使用 `new` 调用的构造函数都将返回非基本类型（`"object"` 或 `"function"`）。大多数返回对象，但值得注意的例外是 `Function`，它返回一个函数。

```js
const str = new String("String");
const num = new Number(100);

typeof str; // "object"
typeof num; // "object"

const func = new Function();

typeof func; // "function"
```

### 语法中需要圆括号

`typeof` 操作符的优先级高于加法（`+`）等二进制操作符。因此，需要用括号来计算加法结果的类型。

```js
// 括号有无将决定表达式的类型。
const someData = 99;

typeof someData + " Wisen"; // "number Wisen"
typeof (someData + " Wisen"); // "string"
```

### 与未声明和未初始化变量的交互

`typeof` 通常总是保证为它提供的任何操作数返回一个字符串。即使使用未声明的标识符，`typeof` 也会返回 `"undefined"`，而不是抛出错误。

```js
typeof undeclaredVariable; // "undefined"
```

但在加入了块级作用域的 let 和 const 之后，在其被声明之前对块中的 `let` 和 `const` 变量使用 `typeof` 会抛出一个 ReferenceError。块作用域变量在块的头部处于“暂存死区”，直至其被初始化，在这期间，访问变量将会引发错误。

```js
typeof newLetVariable; // ReferenceError
typeof newConstVariable; // ReferenceError
typeof newClass; // ReferenceError

let newLetVariable;
const newConstVariable = "hello";
class newClass {}
```

### document.all 的异常行为

当前所有浏览器都公开了一个类型为 `undefined` 的非标准宿主对象 `Document.All`。

```js
typeof document.all === "undefined";
```

虽然 `document.all` 也是假值，与 `undefined` 非严格相等，但它不是 `undefined`。在 Web 标准中，`document.all` 具有 `"undefined"` 类型的情况被归类为“故意违反”原始 ECMAScript Web 兼容性标准。

### 获取具体类型的自定义方法

`typeof` 是非常有用的，但它不像需要的那样万能。例如，`typeof []` 是 `"object"`，以及 `typeof new Date()`、`typeof /abc/` 等。

为了更明确地检查类型，这里我们提供了一个自定义的 `type(value)` 函数，它主要模仿 `typeof` 的行为，但对于非基本类型（即对象和函数），它在可能的情况下返回更详细的类型名。

```js
function type(value) {
  if (value === null) {
    return "null";
  }
  const baseType = typeof value;
  // 基本类型
  if (!["object", "function"].includes(baseType)) {
    return baseType;
  }

  // Symbol.toStringTag 通常指定对象类的“display name”
  // 它在 Object.prototype.toString() 中使用。
  const tag = value[Symbol.toStringTag];
  if (typeof tag === "string") {
    return tag;
  }

  // 如果它是一个函数，其源代码以 "class" 关键字开头
  if (
    baseType === "function" &&
    Function.prototype.toString.call(value).startsWith("class")
  ) {
    return "class";
  }

  // 构造函数的名称；例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
  const className = value.constructor.name;
  if (typeof className === "string" && className !== "") {
    return className;
  }

  // 在这一点上，没有合适的方法来获取值的类型，因此我们使用基本实现。
  return baseType;
}
```

要检查可能不存在的变量，否则会抛出 `ReferenceError`，请使用 `typeof nonExistentVar === 'undefined'`，因为自定义代码无法模仿这种行为。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-typeof-operator |

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

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

参见
--

* `instanceof`
* `document.all` willful violation of the standard


逻辑与赋值（&&=） - JavaScript

逻辑与赋值（&&=）
==========

**逻辑与赋值**（`x &&= y`）运算仅在 `x` 为真值时为其赋值。

尝试一下
----

语法
--

```js
expr1 &&= expr2
```

描述
--

逻辑与的*短路运算*意味着 `x &&= y` 与下式等价：

```js
x && (x = y);
```

如果左操作数不为真值，则由于逻辑与运算符的短路运算，不进行赋值操作。例如，由于 `x` 为 `const`（常量），以下式子不会抛出错误：

```js
const x = 0;
x &&= 2;
```

也不会触发 setter 函数：

```js
const x = {
  get value() {
    return 0;
  },
  set value(v) {
    console.log("调用了 setter");
  },
};
x.value &&= 2;
```

实际上，如果 `x` 不为真值，则根本不会对 `y` 求值。

```js
const x = 0;
x &&= console.log("y 进行了求值");
// 什么都不会输出
```

示例
--

### 使用逻辑与赋值

```js
let x = 0;
let y = 1;

x &&= 0; // 0
x &&= 1; // 0
y &&= 1; // 1
y &&= 0; // 0
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* 逻辑与（&&）
* 空值合并运算符（`??`）
* 按位与赋值（`&=`）
* 真值
* 假值


按位与赋值（&=） - JavaScript

按位与赋值（&=）
=========

按位与赋值运算符（`&=`）使用两个操作数的二进制表示，对它们进行按位与运算并将结果赋值给变量。

尝试一下
----

语法
--

```js
x &= y // x = x & y
```

示例
--

### 使用按位与赋值

```js
let a = 5;
// 5: 00000000000000000000000000000101
// 2: 00000000000000000000000000000010
a &= 2; // 0
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* JS 指南中的赋值运算符
* 按位与运算符


yield* - JavaScript

yield\*
=======

**`yield*` 表达式**用于委托给另一个`generator` 或可迭代对象。

语法
--
```

 yield* [[expression]];
```

`expression`

返回一个可迭代对象的表达式。

描述
--

`yield*` 表达式迭代操作数，并产生它返回的每个值。

`yield*` 表达式本身的值是当迭代器关闭时返回的值（即`done`为`true`时）。

示例
--

### 委托给其他生成器

以下代码中，`g1()` `yield` 出去的每个值都会在 `g2()` 的 `next()` 方法中返回，就像那些 `yield` 语句是写在 `g2()` 里一样。

```js
function\* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function\* g2() {
  yield 1;
  yield\* g1();
  yield 5;
}

var iterator = g2();

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

### 委托给其他可迭代对象

除了生成器对象这一种可迭代对象，`yield*` 还可以 `yield` 其他任意的可迭代对象，比如说数组、字符串、`arguments` 对象等等。

```js
function\* g3() {
  yield\* [1, 2];
  yield\* "34";
  yield\* arguments;
}

var iterator = g3(5, 6);

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

### `yield*` 表达式的值

`yield*` 是一个表达式，不是语句，所以它会有自己的值。

```js
function\* g4() {
  yield\* [1, 2, 3];
  return "foo";
}

var result;

function\* g5() {
  result = yield\* g4();
}

var iterator = g5();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true },
// 此时 g4() 返回了 { value: "foo", done: true }

console.log(result); // "foo"
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-generator-function-definitions-runtime-semantics-evaluation |

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

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

参见
--

* 迭代器协议
* `function*`
* `function*` 表达式
* `yield`


展开语法 - JavaScript

展开语法
====

**展开语法 (Spread syntax),** 可以在函数调用/数组构造时，将数组表达式或者 string 在语法层面展开；还可以在构造字面量对象时，将对象表达式按 key-value 的方式展开。(**译者注**: 字面量一般指 `[1, 2, 3]` 或者 `{name: "mdn"}` 这种简洁的构造方式)尝试一下
----

语法
--

函数调用：
```

myFunction(...iterableObj);
```

字面量数组构造或字符串：
```

[...iterableObj, '4', ...'hello', 6];
```

构造字面量对象时，进行克隆或者属性拷贝（ECMAScript 2018 规范新增特性）：
```

let objClone = { ...obj };
```

示例
--

### 在函数调用时使用展开语法

#### 等价于 apply 的方式

如果想将数组元素迭代为函数参数，一般使用`Function.prototype.apply` 的方式进行调用。

```js
function myFunction(x, y, z) {}
var args = [0, 1, 2];
myFunction.apply(null, args);
```

有了展开语法，可以这样写：

```js
function myFunction(x, y, z) {}
var args = [0, 1, 2];
myFunction(...args);
```

所有参数都可以通过展开语法来传值，也不限制多次使用展开语法。

```js
function myFunction(v, w, x, y, z) {}
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
```

#### 在 new 表达式中应用

使用 `new` 关键字来调用构造函数时，不能**直接**使用数组 + `apply` 的方式（`apply` 执行的是调用 `[[Call]]` , 而不是构造 `[[Construct]]`）。当然，有了展开语法，将数组展开为构造函数的参数就很简单了：

```js
var dateFields = [1970, 0, 1]; // 1970 年 1 月 1 日
var d = new Date(...dateFields);
```

如果不使用展开语法，想将数组元素传给构造函数，实现方式可能是这样的：

```js
function applyAndNew(constructor, args) {
  function partial() {
    return constructor.apply(this, args);
  }
  if (typeof constructor.prototype === "object") {
    partial.prototype = Object.create(constructor.prototype);
  }
  return partial;
}

function myConstructor() {
  console.log("arguments.length: " + arguments.length);
  console.log(arguments);
  this.prop1 = "val1";
  this.prop2 = "val2";
}

var myArguments = ["hi", "how", "are", "you", "mr", null];
var myConstructorWithArguments = applyAndNew(myConstructor, myArguments);

console.log(new myConstructorWithArguments());
// (myConstructor 构造函数中): arguments.length: 6
// (myConstructor 构造函数中): ["hi", "how", "are", "you", "mr", null]
// ("new myConstructorWithArguments"中): {prop1: "val1", prop2: "val2"}
```

### 构造字面量数组时使用展开语法

#### 构造字面量数组时更给力！

没有展开语法的时候，只能组合使用 `push`, `splice`, `concat` 等方法，来将已有数组元素变成新数组的一部分。有了展开语法，通过字面量方式，构造新数组会变得更简单、更优雅：

```js
var parts = ["shoulders", "knees"];
var lyrics = ["head", ...parts, "and", "toes"];
// ["head", "shoulders", "knees", "and", "toes"]
```

和参数列表的展开类似， `...` 在构造字面量数组时，可以在任意位置多次使用。

#### 数组拷贝 (copy)

```js
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);

// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响
```

**提示：** 实际上，展开语法和 `Object.assign()` 行为一致，执行的都是浅拷贝 (只遍历一层)。如果想对多维数组进行深拷贝，下面的示例就有些问题了。

```js
var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array a is affected as well: [[], [2], [3]]
```

#### 连接多个数组

`Array.concat` 函数常用于将一个数组连接到另一个数组的后面。如果不使用展开语法，代码可能是下面这样的：

```js
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中所有元素附加到 arr1 后面并返回
var arr3 = arr1.concat(arr2);
```

使用展开语法：

```js
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2];
```

`Array.unshift` 方法常用于在数组的开头插入新元素/数组。不使用展开语法，示例如下：

```js
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中的元素插入到 arr1 的开头
Array.prototype.unshift.apply(arr1, arr2); // arr1 现在是 [3, 4, 5, 0, 1, 2]
```

如果使用展开语法，代码如下：[请注意，这里使用展开语法创建了一个新的 `arr1` 数组， `Array.unshift` 方法则是修改了原本存在的 `arr1` 数组]:

```js
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1]; // arr1 现在为 [3, 4, 5, 0, 1, 2]
```

### 构造字面量对象时使用展开语法

Rest/Spread Properties for ECMAScript 提议 (stage 4) 对 字面量对象 增加了展开特性。其行为是，将已有对象的所有可枚举 (enumerable) 属性拷贝到新构造的对象中。

浅拷贝 (Shallow-cloning，不包含 prototype) 和对象合并，可以使用更简短的展开语法。而不必再使用 `Object.assign()` 方式。

```js
var obj1 = { foo: "bar", x: 42 };
var obj2 = { foo: "baz", y: 13 };

var clonedObj = { ...obj1 };
// 克隆后的对象：{ foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象：{ foo: "baz", x: 42, y: 13 }
```

**提示**: `Object.assign()` 函数会触发 setters，而展开语法则不会。

**提示**: 不能替换或者模拟 `Object.assign()` 函数：
```

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
const merge = ( ...objects ) => ( { ...objects } );

var mergedObj = merge ( obj1, obj2);
// Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }

var mergedObj = merge ( {}, obj1, obj2);
// Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
```

在这段代码中，展开操作符 (spread operator) 并没有按预期的方式执行：而是先将多个解构变为剩余参数 (rest parameter), 然后再将剩余参数展开为字面量对象。

### 只能用于可迭代对象

在数组或函数参数中使用展开语法时，该语法只能用于 可迭代对象：

```js
var obj = { key1: "value1" };
var array = [...obj]; // TypeError: obj is not iterable
```

### 展开多个值

在函数调用时使用展开语法，请注意不能超过 JavaScript 引擎限制的最大参数个数。更多详细信息，请参考：`apply()`。

剩余语法（剩余参数）
----------

剩余语法 (Rest syntax) 看起来和展开语法完全相同，不同点在于，剩余参数用于解构数组和对象。从某种意义上说，剩余语法与展开语法是相反的：展开语法将数组展开为其中的各个元素，而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。请参考：剩余参数。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-SpreadElement |
| ECMAScript Language Specification # prod-ArgumentList |
| ECMAScript Language Specification # prod-PropertyDefinition |

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

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

参见
--

* 剩余参数
* 剩余属性
* `Function.prototype.apply()`


左移 (<<) - JavaScript

左移 (<<)
=======

**左移操作符 (`<<`)** 将第一个操作数向左移动指定位数，左边超出的位数将会被清除，右边将会补零。

尝试一下
----

语法
--
```

a << b
```

描述
--

左移操作符将第一个操作数向左移动指定位数，左边超出的位数将会被清除，右边将会补零。

例如， `9 << 2` 得出 36：

```js
     9 (十进制): 00000000000000000000000000001001 (二进制)
                 --------------------------------

9 << 2 (十进制): 00000000000000000000000000100100 (二进制) = 36 (十进制)
```

 移动任意数字 `x` 至左边 `y` 位，得出 `x * 2 ** y`。
 所以例如：`9 << 3` 等价于 `9 * 2³ = 9 * 8 = 72`。

示例
--

### 使用左移

```js
9 << 3; // 72

// 9 \* 2³ = 9 \* 8 = 72
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-left-shift-operator |

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

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

参见
--

* Bitwise operators in the JS guide
* Left shift assignment operator


按位异或（^） - JavaScript

按位异或（^）
=======

**按位异或**（**`^`**）运算符在两个操作数有且仅有一个对应的二进制位为 `1` 时，该位的结果值为 `1`。

尝试一下
----

语法
--

```js
a ^ b
```

描述
--

操作数被转换为 32 位整数并由一系列二进制位（0 和 1）表示。超过 32 位的数字会丢弃其最高有效位。例如，以下超过 32 位的整数将被转换为 32 位整数：
```

Before: 11100110111110100000000000000110000000000001
After:              10100000000000000110000000000001
```

第一个操作数中的每个位都与第二个操作数中的相应位配对：*第一位*到*第一位*、*第二位*到*第二位*，以此类推。

运算符应用于每一对位，结果按位构造。

异或或运算的真值表为：

| a | b | a XOR b |
| --- | --- | --- |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
```

     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)
```

将任意数字 `x` 与 `0` 进行按位异或运算得到 `x`。

示例
--

### 使用按位异或

```js
// 9 (00000000000000000000000000001001)
// 14 (00000000000000000000000000001110)

14 ^ 9;
// 7 (00000000000000000000000000000111)
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-BitwiseXORExpression |

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

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

参见
--

* JS 指南中的位运算符
* 按位异或赋值运算符


一元加（+） - JavaScript

一元加（+）
======

**一元加**（**`+`**）运算符在其操作数之前并计算其操作数，但会尝试将其转换为数字，如果它还不是的话。

尝试一下
----

语法
--

```js
+x
```

描述
--

虽然一元减（`-`）也可以转换非数字，但一元加是将某些东西转换为数字的最快和首选方法，因为它不对数字执行任何其他操作。它可以转换整数和浮点数的字符串表示形式，以及非字符串值 `true`、`false` 和 `null`。支持十进制和十六进制（以 `0x` 为前缀）格式的整数。支持负数（但不适用于十六进制）。对 BigInt 值使用该运算符会引发 TypeError。如果它无法解析特定值，它将计算为 `NaN`。

示例
--

### 数字用法

```js
const x = 1;
const y = -1;

console.log(+x);
// 1
console.log(+y);
// -1
```

### 非数字用法

```js
+true  // 1
+false // 0
+null  // 0
+function (val) { return val; } // NaN
+1n    // throws TypeError: Cannot convert BigInt value to number
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-unary-plus-operator |

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

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

参见
--

* 加法运算符
* 减法运算符
* 除法运算符
* 乘法运算符
* 取余运算符
* 求幂运算符
* 自增运算符
* 自减运算符
* 一元减运算符


条件（三元）运算符 - JavaScript

条件（三元）运算符
=========

**条件（三元）运算符**是 JavaScript 唯一使用三个操作数的运算符：一个条件后跟一个问号（`?`），如果条件为真值，则执行冒号（`:`）前的表达式；若条件为假值，则执行最后的表达式。该运算符经常当作 `if...else` 语句的简捷形式来使用。

尝试一下
----

语法
--

```js
condition ? exprIfTrue : exprIfFalse
```

### 参数

`condition`

计算结果用作条件的表达式。

`exprIfTrue`

如果 `condition` 的计算结果为真值（等于或可以转换为 `true` 的值），则执行该表达式。

`exprIfFalse`

如果 `condition` 为假值（等于或可以转换为 `false` 的值）时执行的表达式。

描述
--

除了 `false`，可能的假值表达式还有：`null`、`NaN`、`0`、空字符串（`""`）和 `undefined`。如果 `condition` 是其中任何一个，那么条件表达式的结果就是 `exprIfFalse` 表达式执行的结果。

示例
--

### 简单的例子

```js
const age = 26;
const beverage = age >= 21 ? "Beer" : "Juice";
console.log(beverage); // "Beer"
```

### 处理 null 值

一个常见的用法是处理可能为 `null` 的值：

```js
const greeting = (person) => {
  const name = person ? person.name : "stranger";
  return `Howdy, ${name}`;
};

console.log(greeting({ name: "Alice" })); // "Howdy, Alice"
console.log(greeting(null)); // "Howdy, stranger"
```

### 条件链

三元运算符是右结合的，这意味着它可以按以下方式“链接”起来，类似于 `if … else if … else if … else` 链：

```js
function example() {
  return condition1
    ? value1
    : condition2
      ? value2
      : condition3
        ? value3
        : value4;
}
```

这等价于以下 `if...else` 链。

```js
function example() {
  if (condition1) {
    return value1;
  } else if (condition2) {
    return value2;
  } else if (condition3) {
    return value3;
  } else {
    return value4;
  }
}
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-conditional-operator |

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

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

参见
--

* if 语句
* 空值合并运算符（`??`）
* 可选链运算符（`?.`）
* 在代码中做决定——条件语句
* 表达式与运算符


逻辑与（&&） - JavaScript

逻辑与（&&）
=======

当且仅当所有操作数为 `true` 时，一组布尔操作数的**逻辑与**（**`&&`**，逻辑连接）运算结果为 `true`，否则为 `false`。

一般来说，当从左到右求值时，该操作符返回第一个假值操作数的值；如果它们都是真值，则返回最后一个操作数的值。

尝试一下
----

语法
--

```js
expr1 && expr2
```

描述
--

逻辑与（`&&`）运算符从左到右对操作数求值，遇到第一个假值操作数时立即返回；如果所有的操作数都是真值，则返回最后一个操作数的值。

能够转化为 `true` 的值叫做真值，能够转化为 `false` 的值叫做假值。

能够转化为 false 的表达式的示例如下：

* `false`；
* `null`；
* `NaN`；
* `0`；
* 空字符串（`""` 或 `''` 或 ````）；
* `undefined`。

与运算符会保留所有非布尔值，并原样返回它们：

```js
result = "" && "foo"; // 结果被赋值为 ""（空字符串）
result = 2 && 0; // 结果被赋值为 0
result = "foo" && 4; // 结果被赋值为 4
```

尽管 `&&` 运算符可以与非布尔操作数一起使用，但它仍然被认为是一个布尔运算符，因为它的返回值总是可以被转换为布尔基本类型。要明确地将其返回值（或任何一般的表达式）转换为相应的布尔值，请使用双`非运算符`或 `Boolean` 构造函数。

### 短路求值

逻辑与是一种短路运算符。当每个操作数被转换为布尔值时，如果发现一个转换的结果是 `false`，那么逻辑与运算符就会停止，并返回该假操作数的原始值；它**不会**对任何其余的操作数求值。

考虑如下伪代码。
```

(some falsy expression) && expr
```

`expr` 部分**永远不会被求值**，因为第一个操作数 `(some falsy expression)` 被求值为假值。如果 `expr` 是一个函数，它将不会被调用。查看如下示例：

```js
function A() {
  console.log("调用了 A");
  return false;
}
function B() {
  console.log("调用了 B");
  return true;
}
console.log(A() && B());
// 由于调用了 A 函数，故会输出“调用了 A”到控制台，
// && 求值结果为 false（A 函数返回 false），然后输出 false 到控制台；
// 与运算符在这里短路，忽略了 B 函数
```

### 运算符优先级

与运算符的优先级高于或运算符，这意味着 `&&` 运算符在 `||` 运算符前执行（参见运算符优先级）。

```js
true || (false && false); // true
true && (false || false); // false
2 === 3 || (4 < 0 && 1 === 1); // false
```

示例
--

### 使用与运算符

下列代码展示了 `&&`（逻辑与）运算符的用法。

```js
a1 = true && true; // t && t returns true
a2 = true && false; // t && f returns false
a3 = false && true; // f && t returns false
a4 = false && 3 === 4; // f && f returns false
a5 = "Cat" && "Dog"; // t && t returns "Dog"
a6 = false && "Cat"; // f && t returns false
a7 = "Cat" && false; // t && f returns false
a8 = "" && false; // f && f returns ""
a9 = false && ""; // f && f returns false
```

### 布尔值转化规则

#### 与运算符转化为或运算符

下述涉及到**布尔**运算符的运算：

```js
bCondition1 && bCondition2
```

总是与此表达式相同：

```js
!(!bCondition1 || !bCondition2)
```

#### 或运算符转化为与运算符

下述涉及到**布尔**运算符的运算：

```js
bCondition1 || bCondition2
```

总是与此表达式相同：

```js
!(!bCondition1 && !bCondition2)
```

### 去除嵌套的括号

由于逻辑表达式是从左到右进行求值的，只要遵循一定的规则，总是可以从一个复杂的表达式中删除括号。

下述涉及到**布尔**运算符的复杂运算：

```js
bCondition1 || (bCondition2 && bCondition3)
```

总是与此表达式相同：

```js
bCondition1 || bCondition2 && bCondition3
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-LogicalANDExpression |

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

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

参见
--

* `Boolean`
* 真值
* 假值


对象初始化器 - JavaScript

对象初始化器
======

**对象初始化器**是一个用大括号（`{}`）括起来的以逗号分隔的列表，包含了一个对象的零个或多个属性名称和相关值。可以通过 `Object.create()` 方法，或者使用 `new` 运算符调用其构造函数而初始化一个对象。

尝试一下
----

语法
--

```js
o = {
  a: "foo",
  b: 42,
  c: {},
  1: "number literal property",
  "foo:bar": "string literal property",

  shorthandProperty,

  method(parameters) {
    // …
  },

  get property() {},
  set property(value) {},

  [expression]: "computed property",

  \_\_proto\_\_: prototype,

  ...spreadProperty,
};
```

描述
--

对象初始化器是一个描述 `Object` 初始化的表达式。对象由用于描述对象的*属性*组成。对象属性的值可以包含基本数据类型或其他对象。

### 对象字面语法与 JSON 的对比

对象字面的语法与 **J**ava**S**cript **O**bject **N**otation（JSON）不一样。虽然它们看起来很相似，但它们之间还是有区别的：

* JSON *只*允许使用 `"property": value` 的语法进行属性定义。属性名必须是双引号，而且定义不能简写。计算属性名也是不允许的。
* JSON 对象的属性值只能是字符串、数字、`true`、`false`、`null`、数组或其他 JSON 对象。这意味着 JSON 不能表达方法或非普通对象，如 `Date` 或 `RegExp`。
* 在 JSON 中，`"__proto__"` 是一个普通的属性键。在对象字面中，它设置对象的原型。

JSON 是对象字面语法的一个*真*子集，意味着每一个有效的 JSON 文本都可以被解析为对象字面，并且不会引起语法错误。唯一的例外是，对象字面语法禁止重复的 `__proto__` 键，这不适用于 `JSON.parse()`。后者将 `__proto__` 像普通的属性一样对待，并将最后一次出现的对象作为该属性的值。它们所代表的对象值（也就是它们的语义）唯一不同的时候，也就是当源码包含 `__proto__` 键的时候——对于对象字面，它设置对象的原型；对于 JSON，它是一个普通的属性。

```js
console.log(JSON.parse('{ "\_\_proto\_\_": 0, "\_\_proto\_\_": 1 }')); // {\_\_proto\_\_: 1}
console.log({ "\_\_proto\_\_": 0, "\_\_proto\_\_": 1 }); // SyntaxError: Duplicate \_\_proto\_\_ fields are not allowed in object literals
console.log(JSON.parse('{ "\_\_proto\_\_": {} }')); // { \_\_proto\_\_: {} }
console.log({ "\_\_proto\_\_": {} }); // {}（其原型为 {}）
```

示例
--

### 创建对象

没有属性的空对象可以用以下方式创建：

```js
const object = {};
```

*字面初始化器*符号的优势在于能够快速创建带有大括号内属性的对象，可以快速记录下一个以逗号分隔的 `key: value` 对的列表。

如下代码创建了一个含三个属性的对象，键分别为 `"foo"`、`"age"` 和 `"baz"`。这些键对应的值，分别是字符串 `"bar"`，数字 `42` 和另一个对象。

```js
const object = {
  foo: "bar",
  age: 42,
  baz: { myProp: 12 },
};
```

### 访问属性

创建对象后，可以读取或者修改它。对象属性可以用下标小圆点标记或者方括号标记访问。参见属性访问器获取更多信息。

```js
object.foo; // "bar"
object["age"]; // 42
object.baz; // {myProp: 12}
object.baz.myProp; //12
```

### 属性定义

上面学习了如何用初始化语法对象属性。经常能遇到希望将代码中的变量放到对象中的情况。可能遇到如下代码：

```js
const a = "foo";
const b = 42;
const c = {};

const o = {
  a: a,
  b: b,
  c: c,
};
```

有更简短的标记可以实现同样的效果：

```js
const a = "foo";
const b = 42;
const c = {};

// 简写属性名
const o = { a, b, c };

// 也就是说，
console.log(o.a === { a }.a); // true
```

#### 重复属性名

属性使用了同样的名称时，后面的属性会覆盖前面的属性。

```js
const a = { x: 1, x: 2 };
console.log(a); // {x: 2}
```

在 ES2015 之后，任何地方都允许重复属性名出现，也包括严格模式。你也可以在类中使用重复的属性名。唯一的例外是私有属性，在整个类中它必须是唯一的。

### 方法定义

对象属性也可以是一个函数、getter 或 setter 方法。

```js
const o = {
  property: function (parameters) {},
  get property() {},
  set property(value) {},
};
```

存在一种更简短的写法，关键字 `function` 也可以省略。

```js
// 简写属性名
const o = {
  property(parameters) {},
};
```

也可以简短地定义生成器方法。

```js
const o = {
  \*generator() {
    // …
  },
};
```

这相当于这个类似 ES5 的符号（但注意 ECMAScript 5 没有生成器）：

```js
const o = {
  generator: function\* () {
    // …
  },
};
```

有关方法的更多信息和例子，请参见方法定义。

### 计算属性名

对象初始化器的语法也支持计算的属性名称。这允许你把一个表达式放在中括号 `[]` 中，它将被计算并作为属性名使用。这让人想起属性访问器语法中的括号符号，你可能已经用它来读取和设置属性。

现在你也可以在对象字面量上使用类似的语法：

```js
// 计算属性名
let i = 0;
const a = {
  [`foo${++i}`]: i,
  [`foo${++i}`]: i,
  [`foo${++i}`]: i,
};

console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3

const items = ["A", "B", "C"];
const obj = {
  [items]: "Hello",
};
console.log(obj); // A,B,C: "Hello"
console.log(obj["A,B,C"]); // "Hello"

const param = "size";
const config = {
  [param]: 12,
  [`mobile${param.charAt(0).toUpperCase()}${param.slice(1)}`]: 4,
};

console.log(config); // {size: 12, mobileSize: 4}
```

### 扩展属性

对象字面量支持扩展语法。它将自己提供的对象的枚举属性复制到一个新的对象上。

现在可以使用比 `Object.assign()` 更短的语法来实现浅拷贝（不包括 `prototype`）或合并对象。

```js
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };

const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }

const mergedObj = { ...obj1, ...obj2 };
// { foo: "baz", x: 42, y: 13 }
```

**警告：** 请注意，`Object.assign()` 会触发 setter，而展开语法不会！

### 原型 setter

一个形式为 `__proto__: value` 或 `"__proto__": value` 的属性定义并没有创建一个名称为 `__proto__` 的属性。相反，如果提供的值是一个对象或 `null` 值，它将创建对象的 `[[Prototype]]` 指向该值（如果该值不是一个对象或 `null`，该对象不会被改变）。

请注意，`__proto__` 键是标准化的语法，与非标准且性能不佳的 `Object.prototype.__proto__` 访问器不同。它在创建对象时设置了 `[[Prototype]]`，类似于 `Object.create`——而不是变更原型链。

```js
const obj1 = {};
console.log(Object.getPrototypeOf(obj1) === Object.prototype); // true

const obj2 = { \_\_proto\_\_: null };
console.log(Object.getPrototypeOf(obj2)); // null

const protoObj = {};
const obj3 = { "\_\_proto\_\_": protoObj };
console.log(Object.getPrototypeOf(obj3) === protoObj); // true

const obj4 = { \_\_proto\_\_: "not an object or null" };
console.log(Object.getPrototypeOf(obj4) === Object.prototype); // true
console.log(Object.hasOwn(obj4, "\_\_proto\_\_")); // false
```

在对象字面值中，仅允许有一个原型 setter；多个原型 setter 会被视为语法错误。

不使用“冒号”标记法的属性定义不是原型 setter。它们是属性定义，其行为与使用任何其他名称的类似定义相同。

```js
const __proto__ = "variable";

const obj1 = { __proto__ };
console.log(Object.getPrototypeOf(obj1) === Object.prototype); // true
console.log(Object.hasOwn(obj1, "\_\_proto\_\_")); // true
console.log(obj1.__proto__); // "variable"

const obj2 = { \_\_proto\_\_() { return "hello"; } };
console.log(obj2.\_\_proto\_\_()); // "hello"

const obj3 = { ["\_\_proto\_\_"]: 17 };
console.log(obj3.__proto__); // 17

// 将原型 setter 与正常的使用“\_\_proto\_\_”键的自有属性混合在一起
const obj4 = { ["\_\_proto\_\_"]: 17, \_\_proto\_\_: {} }; // {\_\_proto\_\_: 17} （原型为 {}）
const obj5 = {
  ["\_\_proto\_\_"]: 17,
  \_\_proto\_\_: {},
  \_\_proto\_\_: null, // SyntaxError: Duplicate \_\_proto\_\_ fields are not allowed in object literals
};

const obj6 = {
  ["\_\_proto\_\_"]: 17,
  ["\_\_proto\_\_"]: "hello",
  \_\_proto\_\_: null,
}; // {\_\_proto\_\_: "hello"}（原型为 null）
const obj7 =  {
  ["\_\_proto\_\_"]: 17,
  __proto__,
  \_\_proto\_\_: null,
}; // {\_\_proto\_\_: "variable"}（原型为 null）
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-object-initializer |

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

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

参见
--

* 属性访问器
* `get` / `set`
* 方法定义
* 词法和语法


按位异或赋值 (^=) - JavaScript

按位异或赋值 (^=)
===========

按位异或赋值操作符 (`^=`) 使用二进制表示操作数，进行一次按位异或操作并赋值。

尝试一下
----

语法
--
```

Operator: x ^= y
Meaning:  x  = x ^ y
```

示例
--

### 使用按位异或赋值

```js
let a = 5; // 00000000000000000000000000000101
a ^= 3; // 00000000000000000000000000000011

console.log(a); // 00000000000000000000000000000110
// 6

let b = 5; // 00000000000000000000000000000101
b ^= 0; // 00000000000000000000000000000000

console.log(b); // 00000000000000000000000000000101
// 5
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* Assignment operators in the JS guide
* Bitwise XOR operator


大于等于（>=） - JavaScript

大于等于（>=）
========

大于等于运算符（`>=`）在左操作数大于等于右操作数时返回 `true`，否则返回 `false`。

尝试一下
----

语法
--

```js
x >= y
```

描述
--

操作数比较使用与小于运算符相同的算法，除了两个操作数为相等的值（在尝试转换后）会返回 `true`。

示例
--

### 字符串与字符串比较

```js
console.log("a" >= "b"); // false
console.log("a" >= "a"); // true
console.log("a" >= "3"); // true
```

### 字符串与数值比较

```js
console.log("5" >= 3); // true
console.log("3" >= 3); // true
console.log("3" >= 5); // false

console.log("hello" >= 5); // false
console.log(5 >= "hello"); // false
```

### 数值与数值比较

```js
console.log(5 >= 3); // true
console.log(3 >= 3); // true
console.log(3 >= 5); // false
```

### 数值与大整型比较

```js
console.log(5n >= 3); // true
console.log(3 >= 3n); // true
console.log(3 >= 5n); // false
```

### 比较 Boolean、null、undefined 和 NaN

```js
console.log(true >= false); // true
console.log(true >= true); // true
console.log(false >= true); // false

console.log(true >= 0); // true
console.log(true >= 1); // true

console.log(null >= 0); // true
console.log(1 >= null); // true

console.log(undefined >= 3); // false
console.log(3 >= undefined); // false

console.log(3 >= NaN); // false
console.log(NaN >= 3); // false
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-relational-operators |

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

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

See also
--------

* 大于运算符
* 小于运算符
* 小于等于运算符


instanceof - JavaScript

instanceof
==========

**`instanceof`** **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。

尝试一下
----

语法
--
```

object instanceof constructor
```

### 参数

`object`

某个实例对象

`constructor`

某个构造函数

描述
--

`instanceof` 运算符用来检测 `constructor.prototype` 是否存在于参数 `object` 的原型链上。

```js
// 定义构造函数
function C() {}
function D() {}

var o = new C();

o instanceof C; // true，因为 Object.getPrototypeOf(o) === C.prototype

o instanceof D; // false，因为 D.prototype 不在 o 的原型链上

o instanceof Object; // true，因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object; // true，同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false，C.prototype 指向了一个空对象，这个空对象不在 o 的原型链上。

D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上
```

需要注意的是，如果表达式 `obj instanceof Foo` 返回 `true`，则并不意味着该表达式会永远返回 `true`，因为 `Foo.prototype` 属性的值有可能会改变，改变之后的值很有可能不存在于 `obj` 的原型链上，这时原表达式的值就会成为 `false`。另外一种情况下，原表达式的值也会改变，就是改变对象 `obj` 的原型链的情况，虽然在目前的 ES 规范中，我们只能读取对象的原型而不能改变它，但借助于非标准的 `__proto__` 伪属性，是可以实现的。比如执行 `obj.__proto__ = {}` 之后，`obj instanceof Foo` 就会返回 `false` 了。

### `instanceof` 和多全局对象 (例如：多个 frame 或多个 window 之间的交互)

在浏览器中，我们的脚本可能需要在多个窗口之间进行交互。多个窗口意味着多个全局环境，不同的全局环境拥有不同的全局对象，从而拥有不同的内置类型构造函数。这可能会引发一些问题。比如，表达式 `[] instanceof window.frames[0].Array` 会返回 `false`，因为 `Array.prototype !== window.frames[0].Array.prototype`，并且数组从前者继承。

起初，你会认为这样并没有意义，但是当你在你的脚本中开始处理多个 frame 或多个 window 以及通过函数将对象从一个窗口传到另一个窗口时，这就是一个有效而强大的话题。比如，实际上你可以通过使用`Array.isArray(myObj)` 或者`Object.prototype.toString.call(myObj) === "[object Array]"` 来安全的检测传过来的对象是否是一个数组。

比如检测一个 `Nodes` 在另一个窗口中是不是 `SVGElement`，你可以使用`myNode instanceof myNode.ownerDocument.defaultView.SVGElement`

**备注：** 在代码中使用 XPCOM `instanceof` 有特殊影响：如果查询接口成功执行后，`obj instanceof` *`xpcomInterface`* (e.g. `Components.interfaces.nsIFile`) 调用`obj.QueryInterface(xpcomInterface)` 并且返回 `true` 。这种调用的副作用是在一次成功的 `instanceof` 测试后，你可以在 `obj` 上使用`xpcomInterface` 的属性。这与标准的 `JavaScript` 全局变量不同，即使 `obj` 来自不同的作用域，`obj instanceof xpcomInterface` 也可以按预期产生作用。

示例
--

### 演示 `String` 对象和 `Date` 对象都属于 `Object` 类型和一些特殊情况

下面的代码使用了 `instanceof` 来证明：`String` 和 `Date` 对象同时也属于`Object` 类型（他们是由 `Object` 类派生出来的）。

但是，使用对象文字符号创建的对象在这里是一个例外：虽然原型未定义，但 `instanceof Object` 返回 `true`。

```js
var simpleStr = "This is a simple string";
var myString = new String();
var newStr = new String("String created with constructor");
var myDate = new Date();
var myObj = {};
var myNonObj = Object.create(null);

simpleStr instanceof String; // 返回 false，非对象实例，因此返回 false
myString instanceof String; // 返回 true
newStr instanceof String; // 返回 true
myString instanceof Object; // 返回 true

myObj instanceof Object; // 返回 true，尽管原型没有定义
({}) instanceof Object; // 返回 true，同上
myNonObj instanceof Object; // 返回 false，一种创建非 Object 实例的对象的方法

myString instanceof Date; //返回 false

myDate instanceof Date; // 返回 true
myDate instanceof Object; // 返回 true
myDate instanceof String; // 返回 false
```

### 演示 `mycar` 属于 `Car` 类型的同时又属于 `Object` 类型

下面的代码创建了一个类型 `Car`，以及该类型的对象实例 `mycar`. `instanceof` 运算符表明了这个 `mycar` 对象既属于 `Car` 类型，又属于 `Object` 类型。

```js
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car; // 返回 true
var b = mycar instanceof Object; // 返回 true
```

### 不是...的实例

要检测对象不是某个构造函数的实例时，你可以这样做

```js
if (!(mycar instanceof Car)) {
  // Do something, like mycar = new Car(mycar)
}
```

这和以下代码完全不同

```js
if (!mycar instanceof Car)
```

这段代码永远会得到 `false`（`!mycar` 将在 `instanceof` 之前被处理，所以你总是在验证一个布尔值是否是 `Car` 的一个实例）。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-relational-operators |

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

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

参见
--

* typeof
* `Symbol.hasInstance`
* `Object.prototype.isPrototypeOf`


逻辑或赋值（

逻辑或赋值（||=）
==========

逻辑或赋值（`x ||= y`）运算仅在 `x` 为假值时为其赋值。

尝试一下
----

语法
--

```js
expr1 ||= expr2
```

描述
--

逻辑或的*短路运算*意味着 `x ||= y` 与下式等价：

```js
x || (x = y);
```

如果左操作数不为假值，则由于逻辑或运算符的短路运算，不进行赋值操作。例如，由于 `x` 为 `const`（常量），以下式子不会抛出错误：

```js
const x = 1;
x ||= 2;
```

也不会触发 setter 函数：

```js
const x = {
  get value() {
    return 1;
  },
  set value(v) {
    console.log("调用了 setter");
  },
};

x.value ||= 2;
```

实际上，如果 `x` 为真值，则根本不会对 `y` 求值。

```js
const x = 1;
x ||= console.log("y 进行了求值");
// 什么都不会输出
```

示例
--

### 设定默认内容

当“lyrics”元素为空时，则显示默认值：

```js
document.getElementById("lyrics").textContent ||= "没有歌词。";
```

在这里，短路运算特别有用，因为元素不会产生不必要的更新，也不会引起诸如额外的解析、渲染、失去焦点等副作用。

注意：请注意检查 API 返回的值。如果返回的是空字符串（是假值），则必须使用 `||=`，以显示“没有歌词。”而不是空内容。然而，如果接口返回 `null` 或 `undefined`，则应该使用 `??=`（空值合并赋值）运算符代替空白内容。

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* 逻辑或（||）
* 空值合并运算符（`??`）
* 按位或赋值（`|=`）
* 真值
* 假值


相等（==） - JavaScript

相等（==）
======

相等运算符（`==`）检查其两个操作数是否相等，返回一个布尔值结果。与严格相等运算符（`===`）不同，它会比较不同类型的操作数，并尝试强制类型转换。

尝试一下
----

语法
--

```js
x == y
```

描述
--

相等运算符（`==` 和 `!=`）提供非严格相等语义。这可以大致总结如下：

1. 如果操作数具有相同的类型，则按如下方式进行比较：
    * 对象（Object）：仅当两个操作数引用同一个对象时返回 `true`。
    * 字符串（String）：只有当两个操作数具有相同的字符且顺序相同时才返回 `true`。
    * 数值（Number）：如果两个操作数的值相同，则返回 `true`。`+0` 和 `-0` 被视为相同的值。如果任何一个操作数是 `NaN`，返回 `false`；所以，`NaN` 永远不等于 `NaN`。
    * 布尔值（Boolean）：仅当操作数都为 `true` 或都为 `false` 时返回 `true`。
    * 大整形（BigInt）：仅当两个操作数值相同时返回 `true`。
    * 符号（Symbol）：仅当两个操作数引用相同的符号时返回 `true`。
2. 如果其中一个操作数为 `null` 或 `undefined`，另一个操作数也必须为 `null` 或 `undefined` 以返回 `true`。否则返回 `false`。
3. 如果其中一个操作数是对象，另一个是基本类型，按此顺序使用对象的 `@@toPrimitive()`（以 `"default"` 作为提示），`valueOf()` 和 `toString()` 方法将对象转换为基本类型。（这个基本类型转换与相加中使用的转换相同。）
4. 在这一步，两个操作数都被转换为基本类型（String、Number、Boolean、Symbol 和 BigInt 中的一个）。其余的转换是逐个进行的。
    * 如果是相同的类型，使用步骤 1 进行比较。
    * 如果其中一个操作数是 Symbol 而另一个不是，返回 `false`。
    * 如果其中一个操作数是布尔型而另一个不是，则将布尔型转换为数字：`true` 转换为 1，`false` 转换为 0。然后再次松散地比较两个操作数。
    * Number to String：使用与 `Number()` 构造函数相同的算法将字符串转换为数字。转换失败将导致 `NaN`，这将保证相等是 `false`。
    * Number to BigInt：按数值进行比较。如果数值为 ±∞ 或 `NaN`，返回 `false`。
    * String to BigInt：使用与 `BigInt()` 构造函数相同的算法将字符串转换为 BigInt。如果转换失败，返回 `false`。

宽松相等是*对称的*：`A == B` 对于 `A` 和 `B` 的任何值总是具有与 `B == A` 相同的语义（应用转换的顺序除外）。

该运算符与严格相等（`===`）运算符之间最显著的区别是，严格相等运算符不尝试类型转换。相反，严格相等运算符总是认为不同类型的操作数是不同的。严格相等运算符本质上只执行第 1 步，然后对所有其他情况返回 `false`。

上面的算法有一个“故意违反”：如果其中一个操作数是 `document.all`，则它被视为 `undefined`。这意味着 `document.all == null` 是 `true`，但 `document.all === undefined && document.all === null` 是 `false`。

示例
--

### 没有类型转换的比较

```js
1 == 1; // true
"hello" == "hello"; // true
```

### 有类型转换的比较

```js
"1" == 1; // true
1 == "1"; // true
0 == false; // true
0 == null; // false
0 == undefined; // false
0 == !!null; // true, 看看逻辑非运算符
0 == !!undefined; // true, 看看逻辑非运算符
null == undefined; // true

const number1 = new Number(3);
const number2 = new Number(3);
number1 == 3; // true
number1 == number2; // false
```

### 比较对象

```js
const object1 = {
  key: "value",
};

const object2 = {
  key: "value",
};

console.log(object1 == object2); // false
console.log(object1 == object1); // true
```

### 比较字符串和 String 对象

请注意，使用构造的字符串 `new String()` 是对象。如果将其中之一与字符串文字进行比较，则该 `String` 对象将被转换为字符串文字并对其内容进行比较。但是，如果两个操作数都是 `String` 对象，则将它们作为对象进行比较，并且必须引用相同的对象才能进行比较：

```js
const string1 = "hello";
const string2 = String("hello");
const string3 = new String("hello");
const string4 = new String("hello");

console.log(string1 == string2); // true
console.log(string1 == string3); // true
console.log(string2 == string3); // true
console.log(string3 == string4); // false
console.log(string4 == string4); // true
```

### 比较日期和字符串

```js
const d = new Date("December 17, 1995 03:24:00");
const s = d.toString(); // for example: "Sun Dec 17 1995 03:24:00 GMT-0800 (Pacific Standard Time)"
console.log(d == s); //true
```

### 比较数组和字符串

```js
const a = [1, 2, 3];
const b = "1,2,3";
a == b; // true, `a` converts to string

const c = [true, 0.5, "hey"];
const d = c.toString(); // "true,0.5,hey"
c == d; // true
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-equality-operators |

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

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

参见
--

* 不相等运算符
* 严格相等运算符
* 严格不相等运算符


取余（%） - JavaScript

取余（%）
=====

**取余**（**`%`**）运算符返回左侧操作数除以右侧操作数的余数。它总是与被除数的符号保持一致。

尝试一下
----

语法
--

```js
x % y
```

描述
--

对于运算：`n % d`，其中 `n` 被称为被除数，`d` 被称为除数。如果其中的任意一个操作数为 `NaN`，或 `n` 为正负无穷（±Infinity），又或者 `d` 为 ±0，则该运算返回 `NaN`。否则，如果 `d` 为正负无穷（±Infinity），或 `n` 为 ±0，则返回被除数 `n`。

如果两个操作数都非零且有限（非正负无穷）时，余数 `r` 将通过 `r := n - d * q` 计算获得，其中 `q` 是一个使得 `r` 与被除数 `n` 具有相同符号，并且会使 `r` 尽可能地接近于 0 的整数。

请注意，虽然在大多数语言中，`%` 是取余运算符，但在某些语言（例如：Python 和 Perl）中，它是取模运算符。取模运算被定义为 `k := n - d * q`，其中 `q` 是一个使得 `k` 与除数具有相同符号，并且会使 `k` 尽可能地接近于 0 的整数。对于同号的两根操作数，两者是等价的，但在操作数具有不同的符号时，取模运算的结果总是与*除数*同号，而取余运算则是余数与*被除数*同号，`d` 的符号即可使两者的结果不相同。为了在 JavaScript 中使用 `n % d` 实现取模运算，可以使用 `((n % d) + d) % d`。在 JavaScript 中，模运算（没有专用的运算符）用于规范化按位移位运算符（`<<`、`>>`，等等）的第二个操作数，以使得偏移量始终为一个正数。

示例
--

### 被除数为正数

```js
13 % 5; // 3
1 % -2; // 1
1 % 2; // 1
2 % 3; // 2
5.5 % 2; // 1.5
```

### 被除数为负数

```js
-13 % 5; // -3
-1 % 2; // -1
-4 % 2; // -0
```

### 被除数为 NaN

```js
NaN % 2; // NaN
```

### 被除数为 Infinity

```js
Infinity % 2; // NaN
Infinity % 0; // NaN
Infinity % Infinity; // NaN
2 % Infinity; // 2
0 % Infinity; // 0
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-multiplicative-operators |

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

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

参见
--

* 加法运算符
* 减法运算符
* 除法运算符
* 乘法运算符
* 幂运算符
* 自增运算符
* 自减运算符
* 一元减运算符
* 一元加运算符
* 取余运算和取模运算的区别


乘法赋值（*=） - JavaScript

乘法赋值（\*=）
=========

**乘法赋值**（**`*=`**）运算符将变量乘以右操作数的值，并将结果赋值给该变量。

尝试一下
----

语法
--

```js
x \*= y // x = x \* y
```

示例
--

### 使用乘法赋值

```js
let bar = 5;

bar \*= 2; // 10
bar \*= "foo"; // NaN
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # sec-assignment-operators |

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

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

参见
--

* JS 指南中的赋值操作符
* 乘法运算符


可选链运算符（?.） - JavaScript

可选链运算符（?.）
==========

**可选链运算符**（**`?.`**）允许读取位于连接对象链深处的属性的值，而不必明确验证链中的每个引用是否有效。`?.` 运算符的功能类似于 `.` 链式运算符，不同之处在于，在引用为空 (nullish ) (`null` 或者 `undefined`) 的情况下不会引起错误，该表达式短路返回值是 `undefined`。与函数调用一起使用时，如果给定的函数不存在，则返回 `undefined`。

当尝试访问可能不存在的对象属性时，可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时，如果不能确定哪些属性必定存在，可选链运算符也是很有帮助的。

尝试一下
----

语法
--

```js
obj.val?.prop
obj.val?.[expr]
obj.func?.(args)
```

描述
--

通过连接的对象的引用或函数可能是 `undefined` 或 `null` 时，可选链运算符提供了一种方法来简化被连接对象的值访问。

比如，思考一个存在嵌套结构的对象 `obj`。不使用可选链的话，查找一个深度嵌套的子属性时，需要验证之间的引用，例如：

```js
let nestedProp = obj.first && obj.first.second;
```

为了避免报错，在访问`obj.first.second`之前，要保证 `obj.first` 的值既不是 `null`，也不是 `undefined`。如果只是直接访问 `obj.first.second`，而不对 `obj.first` 进行校验，则有可能抛出错误。

有了可选链运算符（`?.`），在访问 `obj.first.second` 之前，不再需要明确地校验 `obj.first` 的状态，再并用短路计算获取最终结果：

```js
let nestedProp = obj.first?.second;
```

通过使用 `?.` 运算符取代 `.` 运算符，JavaScript 会在尝试访问 `obj.first.second` 之前，先隐式地检查并确定 `obj.first` 既不是 `null` 也不是 `undefined`。如果`obj.first` 是 `null` 或者 `undefined`，表达式将会短路计算直接返回 `undefined`。

这等价于以下表达式，但实际上没有创建临时变量：

```js
let temp = obj.first;
let nestedProp = temp === null || temp === undefined ? undefined : temp.second;
```

### 可选链与函数调用

当尝试调用一个可能不存在的方法时也可以使用可选链。这将是很有帮助的，比如，当使用一个 API 的方法可能不可用时，要么因为实现的版本问题要么因为当前用户的设备不支持该功能。

函数调用时如果被调用的方法不存在，使用可选链可以使表达式自动返回`undefined`而不是抛出一个异常。

```js
let result = someInterface.customMethod?.();
```

**备注：** 如果存在一个属性名且不是函数，使用 `?.` 仍然会产生一个 `TypeError` 异常 (`x.y is not a function`).

**备注：** 如果 `someInterface` 自身是 `null` 或者 `undefined` ，异常 `TypeError` 仍会被抛出 `someInterface is null` 如果你希望允许 `someInterface` 也为 `null` 或者 `undefined` ，那么你需要像这样写 `someInterface?.customMethod?.()`

#### 处理可选的回调函数或者事件处理器

如果使用解构赋值来解构的一个对象的回调函数或 fetch 方法，你可能得到不能当做函数直接调用的不存在的值，除非你已经校验了他们的存在性。使用`?.`的你可以忽略这些额外的校验：

```js
// ES2019 的写法
function doSomething(onContent, onError) {
  try {
    // ... do something with the data
  } catch (err) {
    if (onError) {
      // 校验 onError 是否真的存在
      onError(err.message);
    }
  }
}
```

```js
// 使用可选链进行函数调用
function doSomething(onContent, onError) {
  try {
    // ... do something with the data
  } catch (err) {
    onError?.(err.message); // 如果 onError 是 undefined 也不会有异常
  }
}
```

### 可选链和表达式

当使用方括号与属性名的形式来访问属性时，你也可以使用可选链运算符：

```js
let nestedProp = obj?.["prop" + "Name"];
```

### 可选链不能用于赋值
```

let object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment
```

### 可选链访问数组元素
```

let arrayItem = arr?.[42];
```

示例
--

### 基本例子

如下的例子在一个不含 `bar` 成员的 Map 中查找 `bar` 成员的 `name` 属性，因此结果是 `undefined`。

```js
let myMap = new Map();
myMap.set("foo", { name: "baz", desc: "inga" });

let nameBar = myMap.get("bar")?.name;
```

### 短路计算

当在表达式中使用可选链时，如果左操作数是 `null` 或 `undefined`，表达式将不会被计算，例如：

```js
let potentiallyNullObj = null;
let x = 0;
let prop = potentiallyNullObj?.[x++];

console.log(x); // x 将不会被递增，依旧输出 0
```

### 连用可选链运算符

可以连续使用可选链读取多层嵌套结构：

```js
let customer = {
  name: "Carl",
  details: {
    age: 82,
    location: "Paradise Falls", // details 的 address 属性未有定义
  },
};
let customerCity = customer.details?.address?.city;

// … 可选链也可以和函数调用一起使用
let duration = vacations.trip?.getTime?.();
```

### 使用空值合并运算符

`空值合并运算符`可以在使用可选链时设置一个默认值：

```js
let customer = {
  name: "Carl",
  details: { age: 82 },
};
let customerCity = customer?.city ?? "暗之城";
console.log(customerCity); // “暗之城”
```

规范
--

| Specification |
| --- |
| ECMAScript Language Specification # prod-OptionalExpression |

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

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

参见
--

* 空值合并运算符（??）


switch - JavaScript

switch
======

**`switch` 语句**评估一个表达式，将表达式的值与`case`子句匹配，并执行与该情况相关联的语句。

尝试一下
----

语法
--

```js
switch (expression) {
  case value1:
    // 当 expression 的结果与 value1 匹配时，执行此处语句
    [break;]
  case value2:
    // 当 expression 的结果与 value2 匹配时，执行此处语句
    [break;]
  ...
  case valueN:
    // 当 expression 的结果与 valueN 匹配时，执行此处语句
    [break;]
  [default:
    // 如果 expression 与上面的 value 值都不匹配，执行此处语句
    [break;]]
}
```

`expression`

一个用来与 case 子语句匹配的表达式。

`case valueN` 可选

用于匹配 `expression` 的 `case` 子句。如果 `expression` 与给定的 `valueN` 相匹配，则执行该 case 子句中的语句直到该 `switch` 语句结束或遇到一个 `break` 。

`default` 可选

一个 `default` 子句；如果给定，这条子句会在 `expression` 的值与任一 `case` 语句均不匹配时执行。

描述
--

一个 switch 语句首先会计算其 expression。然后，它将从第一个 case 子句开始直到寻找到一个其表达式值与所输入的 expression 的值所相等的子句（使用 严格运算符 (en-US)，`===`）并将控制权转给该子句，执行相关语句。（如果多个 case 与提供的值匹配，则选择匹配的第一个 case，即使这些 case 彼此间并不相等。）

如果没有 `case` 子句相匹配，程序则会寻找那个可选的 `default` 子句，如果找到了，将控制权交给它，执行相关语句。若没有 `default` 子句，程序将继续执行直到 `switch` 结束。按照惯例，`default` 子句是最后一个子句，不过也不需要这样做。

可选的 `break` 语句确保程序立即从相关的 case 子句中跳出 switch 并接着执行 switch 之后的语句。若 `break` 被省略，程序会继续执行 `switch` 语句中的下一条语句。

示例
--

### 使用 `switch`

下面的例子中，如果 `expr` 计算为 "Bananas"，程序就会匹配值为 "Bananas" 的 case 然后执行相关语句。当遇到 `break` 时，程序就跳出 `switch` 然后执行 `switch` 后的语句。若 `break` 被省略，值为 "Cherries" 的 case 中的语句就也将被执行。

```js
switch (expr) {
  case "Oranges":
    console.log("Oranges are $0.59 a pound.");
    break;
  case "Apples":
    console.log("Apples are $0.32 a pound.");
    break;
  case "Bananas":
    console.log("Bananas are $0.48 a pound.");
    break;
  case "Cherries":
    console.log("Cherries are $3.00 a pound.");
    break;
  case "Mangoes":
  case "Papayas":
    console.log("Mangoes and papayas are $2.79 a pound.");
    break;
  default:
    console.log("Sorry, we are out of " + expr + ".");
}

console.log("Is there anything else you'd like?");
```

### 如果忘记 break 会怎么样？

如果你忘记添加 break，那么代码将会从值所匹配的 case 语句开始运行，然后持续执行下一个 case 语句而不论值是否匹配。例子如下：

```js
var foo = 0;
switch (foo) {
  case -1:
    console.log("negative 1");
    break;
  case 0: // foo 的值为 0 所以匹配这里所以这一块会运行
    console.log(0);
  // 注意：那个没写的 break 原本在这儿
  case 1: // 'case 0:' 里没有 break 语句所以这个 case 也会运行
    console.log(1);
    break; // 遇到了 break，所以不会再继续进入 'case 2:' 了
  case 2:
    console.log(2);
    break;
  default:
    console.log("default");
}
```

### 我能把 default 放到 case 之间吗？

可以啊！JavaScript 会在它找不到匹配项时跳回到那个 default：

```js
var foo = 5;
switch (foo) {
  case 2:
    console.log(2);
    break; // 遇到 break，所以不会继续进入 'default:'
  default:
    console.log("default");
  // 掉到下面
  case 1:
    console.log("1");
}
```

即使你把 default 放到其他 case 之上，它仍有效。

### 使用多准则 case 的方法

这个技术来源于此：

Switch statement multiple cases in JavaScript (Stack Overflow)

#### 多 case - 单一操作

这种方法利用这样一个事实：如果 case 语句之下没有 break，它将继续执行下一个 case 语句，而不管 case 是否符合条件。请看“如果忘记 break 会怎么样？”部分。

这是一个单操作顺序的 switch 语句，其中四个不同值的执行结果完全一样。

```js
var Animal = "Giraffe";
switch (Animal) {
  case "Cow":
  case "Giraffe":
  case "Dog":
  case "Pig":
    console.log("This animal will go on Noah's Ark.");
    break;
  case "Dinosaur":
  default:
    console.log("This animal will not.");
}
```

#### 多 case - 关联操作

这是一个关联操作顺序的 switch 语句，其中，根据所输入的整数，你会得到不同的输出。这表示它将以你放置 case 语句的顺序遍历，并且不必是数字顺序的。在 JavaScript 中，你甚至可以将字符串定义到这些 case 语句里。

```js
var foo = 1;
var output = "Output: ";
switch (foo) {
  case 0:
    output += "So ";
  case 1:
    output += "What ";
    output += "Is ";
  case 2:
    output += "Your ";
  case 3:
    output += "Name";
  case 4:
    output += "?";
    console.log(output);
    break;
  case 5:
    output += "!";
    console.log(output);
    break;
  default:
    console.log("Please pick a number from 0 to 5!");
}
```

这个例子的输出：

| Value | Log text |
| --- | --- |
| foo is NaN or not 1, 2, 3, 4, 5 or 0 | Please pick a number from 0 to 5! |
| 0 | Output: So What Is Your Name? |
| 1 | Output: What Is Your Name? |
| 2 | Output: Your Name? |
| 3 | Output: Name? |
| 4 | Output: ? |
| 5 | Output: ! |

### `switch` 语句内的块级作用域

随着绝大多数现代浏览器已支持 ECMAScript 2015 (ES6)，在某些场景下你可能需要使用 let 和 const 语句，以在块级作用域内声明变量。

以这段代码为例：

```js
const action = "say\_hello";
switch (action) {
  case "say\_hello":
    let message = "hello";
    console.log("0 ~5");
    break;
  case "say\_hi":
    let message = "hi";
  case 6:
    console.log("6");
    break;
  default:
    console.log("Empty action received.");
    break;
}
```

这个示例会导致意想不到的错误 `Uncaught SyntaxError: Identifier 'message' has already been declared`.

这是因为第一个 `let message = 'hello';` 与第二个 `let message = 'hi';` 语句产生了冲突，虽然他们处于各自分隔的 case 语句中，即 `case 'say_hello':` 和 `case 'say_hi':`。导致这一问题的根本原因在于两个 `let` 语句处于同一个块级作用域，所以它们被认为是同一个变量名的重复声明。

通过把 case 语句包装到括号里面，我们就可以轻松解决这个问题。

```js
const action = "say\_hello";
switch (action) {
  case "say\_hello": {
    // added brackets
    let message = "hello";
    console.log(message);
    break;
  } // added brackets
  case "say\_hi": {
    // added brackets
    let message = "hi";
    console.log(message);
    break;
  } // added brackets
  default: {
    // added brackets
    console.log("Empty action received.");
    break;
  } // added brackets
}
```

此时，这段代码就会在控制台输出 `hello`，不会再有任何报错。

规范
--

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

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

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

参见
--

* `if...else`


for...of - JavaScript

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

**`for...of`语句**在可迭代对象（包括 `Array`，`Map`，`Set`，`String`，`TypedArray`，arguments 对象等等）上创建一个迭代循环，调用自定义迭代钩子，并为每个不同属性的值执行语句

尝试一下
----

语法
--
```

for (variable of iterable) {
    //statements
}
```

`variable`

在每次迭代中，将不同属性的值分配给变量。

`iterable`

被迭代枚举其属性的对象。

示例
--

### 迭代`Array`

```js
let iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}
// 11
// 21
// 31
```

如果你不想修改语句块中的变量 , 也可以使用`const`代替`let`。

```js
let iterable = [10, 20, 30];

for (const value of iterable) {
  console.log(value);
}
// 10
// 20
// 30
```

### 迭代`String`

```js
let iterable = "boo";

for (let value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"
```

### 迭代 `TypedArray`

```js
let iterable = new Uint8Array([0x00, 0xff]);

for (let value of iterable) {
  console.log(value);
}
// 0
// 255
```

### 迭代`Map`

```js
let iterable = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);

for (let entry of iterable) {
  console.log(entry);
}
// ["a", 1]
// ["b", 2]
// ["c", 3]

for (let [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3
```

### 迭代 `Set`

```js
let iterable = new Set([1, 1, 2, 2, 3, 3]);

for (let value of iterable) {
  console.log(value);
}
// 1
// 2
// 3
```

### 迭代 `arguments` 对象

```js
(function () {
  for (let argument of arguments) {
    console.log(argument);
  }
})(1, 2, 3);

// 1
// 2
// 3
```

### 迭代 DOM 集合

迭代 DOM 元素集合，比如一个`NodeList`对象：下面的例子演示给每一个 article 标签内的 p 标签添加一个 "`read`" 类。

```js
//注意：这只能在实现了 NodeList.prototype[Symbol.iterator] 的平台上运行
let articleParagraphs = document.querySelectorAll("article > p");

for (let paragraph of articleParagraphs) {
  paragraph.classList.add("read");
}
```

### 关闭迭代器

对于`for...of`的循环，可以由 `break`, `throw` 或 `return` 终止。在这些情况下，迭代器关闭。

```js
function\* foo() {
  yield 1;
  yield 2;
  yield 3;
}

for (let o of foo()) {
  console.log(o);
  break; // closes iterator, triggers return
}
```

### 迭代生成器

你还可以迭代一个生成器：

```js
function\* fibonacci() {
  // 一个生成器函数
  let [prev, curr] = [0, 1];
  for (;;) {
    // while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  console.log(n);
  // 当 n 大于 1000 时跳出循环
  if (n >= 1000) break;
}
```

#### 不要重用生成器

生成器不应该重用，即使`for...of`循环的提前终止，例如通过`break`关键字。在退出循环后，生成器关闭，并尝试再次迭代，不会产生任何进一步的结果。

```js
var gen = (function\* () {
  yield 1;
  yield 2;
  yield 3;
})();
for (let o of gen) {
  console.log(o);
  break; //关闭生成器
}

//生成器不应该重用，以下没有意义！
for (let o of gen) {
  console.log(o);
}
```

### 迭代其他可迭代对象

你还可以迭代显式实现可迭代协议的对象：

```js
var iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

for (var value of iterable) {
  console.log(value);
}
// 0
// 1
// 2
```

### `for...of`与`for...in`的区别

无论是`for...in`还是`for...of`语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

`for...in` 语句以任意顺序迭代对象的可枚举属性。

`for...of` 语句遍历可迭代对象定义要迭代的数据。

以下示例显示了与`Array`一起使用时，`for...of`循环和`for...in`循环之间的区别。

```js
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};

let iterable = [3, 5, 7];
iterable.foo = "hello";

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}
```

```js
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};

let iterable = [3, 5, 7];
iterable.foo = "hello";
```

每个对象将继承`objCustom`属性，并且作为`Array`的每个对象将继承`arrCustom`属性，因为将这些属性添加到`Object.prototype` (en-US)和`Array.prototype`。由于继承和原型链，对象`iterable`继承属性`objCustom`和`arrCustom`。

```js
for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}
```

此循环仅以原始插入顺序记录`iterable` 对象的可枚举属性。它不记录数组**元素** `3`, `5`, `7` 或`hello`，因为这些**不是**枚举属性。但是它记录了数组**索引**以及`arrCustom`和`objCustom`。如果你不知道为什么这些属性被迭代，`array iteration and for...in`中有更多解释。

```js
for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}
```

这个循环类似于第一个，但是它使用`hasOwnProperty()` 来检查，如果找到的枚举属性是对象自己的（不是继承的）。如果是，该属性被记录。记录的属性是`0`, `1`, `2`和`foo`，因为它们是自身的属性（**不是继承的**）。属性`arrCustom`和`objCustom`不会被记录，因为它们**是继承的**。

```js
for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}
```

该循环迭代并记录`iterable`作为可迭代对象定义的迭代值，这些是数组元素 `3`, `5`, `7`，而不是任何对象的**属性**。

规范
--

| 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.

参见
--

* `Array.prototype.forEach()`
* `Map.prototype.forEach()`
* `Object.entries()`


empty - JavaScript

empty
=====

**空语句**用来表明没有语句，尽管 JavaScript 语法希望有语句。

语法
--
```

;
```

描述
--

空语句是一个分号（;），表示不会执行任何语句，即使 JavaScript 语法需要一个语句。相反，当你需要多行语句，但 JavaScript 只允许一个时，可以使用语句块；语句块可以将多条语句合并为一个。

示例
--

空语句有时与循环语句一起使用。以下示例使用空循环体：

```js
var arr = [1, 2, 3];

// Assign all array values to 0
for (let i = 0; i < arr.length; arr[i++] = 0 /\* empty statement \*/);

console.log(arr);
// [0, 0, 0]
```

\*\*提示：\*\*在使用空语句的情况下专门写上注释是个不错的主意，因为不是很容易区分空语句和普通的分号。下面的示例可能不是故意加上分号的：

```js
if (condition); // Caution, this "if" does nothing!
  killTheUniverse() // So this gets always executed!!!
```

另一个例子：`if...else` 语句不带花括号（`{}`）。如果`three`为`true`, 不会发生任何事，`four`不会执行，同时`else`从句中的`launchRocket()`函数也不会执行。

```js
if (one)
  doOne();
else if (two)
  doTwo();
else if (three)
  ; // nothing here
else if (four)
  doFour();
else
  launchRocket();
```

规范
--

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

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

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

参见
--

* `块语句`


for - JavaScript

for
===

**`for` 语句**用于创建一个循环，它包含了三个可选的表达式，这三个表达式被包围在圆括号之中，使用分号分隔，后跟一个用于在循环中执行的语句（通常是一个块语句）。

尝试一下
----

语法
--
```

for ([initialization]; [condition]; [final-expression])
   statement
```

`initialization`

一个表达式 (包含赋值语句) 或者变量声明。典型地被用于初始化一个计数器。该表达式可以使用 `var` 或 `let` 关键字声明新的变量，使用 `var` 声明的变量不是该循环的局部变量，而是与 `for` 循环处在同样的作用域中。用 `let` 声明的变量是语句的局部变量。该表达式的结果无意义。

`condition`

一个条件表达式被用于确定每一次循环是否能被执行。如果该表达式的结果为 true，`statement` 将被执行。这个表达式是可选的。如果被忽略，那么就被认为永远为真。如果计算结果为假，那么执行流程将被跳到 `for` 语句结构后面的第一条语句。

`final-expression`

每次循环的最后都要执行的表达式。执行时机是在下一次 `condition` 的计算之前。通常被用于更新或者递增计数器变量。

`statement`

只要`condition`的结果为 true 就会被执行的语句。要在循环体内执行多条语句，使用一个块语句（`{ ... }`）来包含要执行的语句。没有任何语句要执行，使用一个空语句（`;`）。

示例
--

### 使用 `for`

以下例子声明了变量 `i` 并被初始赋值为 `0`，`for` 语句检查 `i` 的值是否小于 9，如果小于 9，则执行语句块内的语句，并且最后将 `i` 的值增加 1。

```js
for (var i = 0; i < 9; i++) {
  console.log(i);
  // more statements
}
```

### 可选的 `for` 表达式

`for` 语句头部圆括号中的所有三个表达式都是可选的。

例如，初始化块中的表达式没有被指定：

```js
var i = 0;
for (; i < 9; i++) {
  console.log(i);
  // more statements
}
```

像初始化块一样，条件块也是可选的。如果省略此表达式，则必须确保在循环体内跳出，以防创建死循环。

```js
for (var i = 0; ; i++) {
  console.log(i);
  if (i > 3) break;
  // more statements
}
```

你当然可以忽略所有的表达式。同样的，确保使用了 `break` >语句来跳出循环并且还要修改（增加）一个变量，使得 break 语句的条件在某个时候是真的。

```js
var i = 0;

for (;;) {
  if (i > 3) break;
  console.log(i);
  i++;
}
```

### 使用无语句的 `for`

以下 `for` 循环计算 *final-expression* 部分中节点的偏移位置，它不需要执行一个 `statement` 或者一组 `block statement` ，因此使用了空语句。

```js
function showOffsetPos(sId) {
  var nLeft = 0,
    nTop = 0;

  for (
    var oItNode = document.getElementById(sId) /\* initialization \*/;
    oItNode /\* condition \*/;
    nLeft += oItNode.offsetLeft,
      nTop += oItNode.offsetTop,
      oItNode = oItNode.offsetParent /\* final-expression \*/
  ); /\* 分号 semicolon \*/

  console.log(
    "Offset position of '" +
      sId +
      "' element:\n left: " +
      nLeft +
      "px;\n top: " +
      nTop +
      "px;",
  );
}

/\* Example call: \*/

showOffsetPos("content");

// Output:
// "Offset position of "content" element:
// left: 0px;
// top: 153px;"
```

**备注：** **这里的分号是强制性的**，是 JavaScript 中的少数几种强制分号的情况。如果没有分号，循环声明之后的行将被视为循环语句。

规范
--

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

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

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

参见
--

* empty statement
* `break`
* `continue`
* `while`
* `do...while`
* `for...in`
* `for...of`


async 函数 - JavaScript

async 函数
========

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

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

* Learn more
* See full compatibility
* Report feedback
async 函数是使用`async`关键字声明的函数。async 函数是 `AsyncFunction` 构造函数的实例，并且其中允许使用 `await` 关键字。`async` 和 `await` 关键字让我们可以用一种更简洁的方式写出基于 `Promise` 的异步行为，而无需刻意地链式调用 `promise`。

async 函数还可以被作为表达式来定义。

尝试一下
----

语法
--

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

### 参数

`name`

函数名称。

`param` 可选

要传递给函数的参数的名称。

`statements` 可选

包含函数主体的表达式。可以使用 `await` 机制。

### 返回值

一个 `Promise`，这个 promise 要么会通过一个由 async 函数返回的值被解决，要么会通过一个从 async 函数中抛出的（或其中没有被捕获到的）异常被拒绝。

描述
--

async 函数可能包含 0 个或者多个 `await` 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权，只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 `async`/`await` 关键字就可以在异步代码中使用普通的 `try`/`catch` 代码块。

**备注：** `await`关键字只在 async 函数内有效。如果你在 async 函数体之外使用它，就会抛出语法错误 `SyntaxError` 。

**备注：** `async`/`await`的目的为了简化使用基于 promise 的 API 时所需的语法。`async`/`await` 的行为就好像搭配使用了生成器和 promise。

async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise，那么它将会被隐式地包装在一个 promise 中。

例如，如下代码：

```js
async function foo() {
  return 1;
}
```

等价于：

```js
function foo() {
  return Promise.resolve(1);
}
```

async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到（并包括）第一个 await 表达式（如果有的话）都是同步运行的。这样的话，一个不含 await 表达式的 async 函数是会同步运行的。然而，如果函数体内有一个 await 表达式，async 函数就一定会异步执行。

例如：

```js
async function foo() {
  await 1;
}
```

等价于

```js
function foo() {
  return Promise.resolve(1).then(() => undefined);
}
```

在 await 表达式之后的代码可以被认为是存在在链式调用的 then 回调中，多个 await 表达式都将加入链式调用的 then 回调中，返回值将作为最后一个 then 回调的返回值。

在接下来的例子中，我们将使用 await 执行两次 promise，整个 `foo` 函数的执行将会被分为三个阶段。

1. `foo` 函数的第一行将会同步执行，await 将会等待 promise 的结束。然后暂停通过 `foo` 的进程，并将控制权交还给调用 `foo` 的函数。
2. 一段时间后，当第一个 promise 完结的时候，控制权将重新回到 foo 函数内。示例中将会将`1`（promise 状态为 fulfilled）作为结果返回给 await 表达式的左边即 `result1`。接下来函数会继续进行，到达第二个 await 区域，此时 `foo` 函数的进程将再次被暂停。
3. 一段时间后，同样当第二个 promise 完结的时候，`result2` 将被赋值为 `2`，之后函数将会正常同步执行，将默认返回`undefined` 。

```js
async function foo() {
  const result1 = await new Promise((resolve) =>
    setTimeout(() => resolve("1")),
  );
  const result2 = await new Promise((resolve) =>
    setTimeout(() => resolve("2")),
  );
}
foo();
```

注意：promise 链不是一次就构建好的，相反，promise 链是分阶段构造的，因此在处理异步函数时必须注意对错误函数的处理。

例如，在下面代码中，即使在 promise 链中进一步配置了 `.catch` 方法处理，也会抛出一个未处理的 promise 被拒绝的错误。这是因为 `p2` 直到控制从 `p1` 返回后才会连接到 promise 链。

```js
async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
  const p2 = new Promise((\_, reject) => setTimeout(() => reject("2"), 500));
  const results = [await p1, await p2]; // 不推荐使用这种方式，请使用 Promise.all 或者 Promise.allSettled
}
foo().catch(() => {}); // 捕捉所有的错误...
```

示例
--

### 简单例子

```js
function resolveAfter2Seconds() {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
}

async function sequentialStart() {
  console.log("==SEQUENTIAL START==");

  // 1. Execution gets here almost instantly
  const slow = await resolveAfter2Seconds();
  console.log(slow); // 2. this runs 2 seconds after 1.

  const fast = await resolveAfter1Second();
  console.log(fast); // 3. this runs 3 seconds after 1.
}

async function concurrentStart() {
  console.log("==CONCURRENT START with await==");
  const slow = resolveAfter2Seconds(); // starts timer immediately
  const fast = resolveAfter1Second(); // starts timer immediately

  // 1. Execution gets here almost instantly
  console.log(await slow); // 2. this runs 2 seconds after 1.
  console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

function concurrentPromise() {
  console.log("==CONCURRENT START with Promise.all==");
  return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
    (messages) => {
      console.log(messages[0]); // slow
      console.log(messages[1]); // fast
    },
  );
}

async function parallel() {
  console.log("==PARALLEL with await Promise.all==");

  // Start 2 "jobs" in parallel and wait for both of them to complete
  await Promise.all([
    (async () => console.log(await resolveAfter2Seconds()))(),
    (async () => console.log(await resolveAfter1Second()))(),
  ]);
}

sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"

// wait above to finish
setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"

// wait again
setTimeout(concurrentPromise, 7000); // same as concurrentStart

// wait again
setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"
```

#### await 和并行

在 `sequentialStart` 中，程序在第一个 `await` 停留了 2 秒，然后又在第二个 `await` 停留了 1 秒。直到第一个计时器结束后，第二个计时器才被创建。程序需要 3 秒执行完毕。

在 `concurrentStart` 中，两个计时器被同时创建，然后执行 `await`。这两个计时器同时运行，这意味着程序完成运行只需要 2 秒，而不是 3 秒，即最慢的计时器的时间。

但是 `await` 仍旧是顺序执行的，第二个 `await` 还是得等待第一个执行完。在这个例子中，这使得先运行结束的输出出现在最慢的输出之后。

如果你希望并行执行两个或更多的任务，你必须像在`parallel`中一样使用`await Promise.all([job1(), job2()])`。

#### async/await 和 Promise/then 对比以及错误处理

大多数 async 函数也可以使用 Promises 编写。但是，在错误处理方面，async 函数更容易捕获异常错误

上面例子中的`concurrentStart`函数和`concurrentPromise`函数在功能上都是等效的。在`concurrentStart`函数中，如果任一`await`ed 调用失败，它将自动捕获异常，async 函数执行中断，并通过隐式返回 Promise 将错误传递给调用者。

在 Promise 例子中这种情况同样会发生，该函数必须负责返回一个捕获函数完成的`Promise`。在`concurrentPromise`函数中，这意味着它从`Promise.all([]).then()`返回一个 Promise。事实上，在此示例的先前版本忘记了这样做！

但是，async 函数仍有可能错误地忽略错误。以 `parallel` async 函数为例。如果它没有等待 `await`（或返回）`Promise.all([])` 调用的结果，则不会传播任何错误。虽然 `parallelPromise` 函数示例看起来很简单，但它根本不会处理错误！这样做需要一个类似于 `return Promise.all([])` 处理方式。

### 使用 async 函数重写 promise 链

返回 `Promise`的 API 将会产生一个 promise 链，它将函数肢解成许多部分。例如下面的代码：

```js
function getProcessedData(url) {
  return downloadData(url) // 返回一个 promise 对象
    .catch((e) => {
      return downloadFallbackData(url); // 返回一个 promise 对象
    })
    .then((v) => {
      return processDataInWorker(v); // 返回一个 promise 对象
    });
}
```

可以重写为单个 async 函数：

```js
async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url);
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  return processDataInWorker(v);
}
```

注意，在上述示例中，`return` 语句中没有 `await` 运算符，因为 `async function` 的返回值将被隐式地传递给 `Promise.resolve`。

返回值隐式地传递给 `Promise.resolve`，并不意味着 `return await promiseValue;` 和 `return promiseValue;` 在功能上相同。

看下下面重写的上面代码，在 `processDataInWorker` 抛出异常时返回了 null：

```js
async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url);
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  try {
    return await processDataInWorker(v); // 注意 `return await` 和单独 `return` 的比较
  } catch (e) {
    return null;
  }
}
```

简单地写上`return processDataInworker(v);` 将导致在 `processDataInWorker(v)` 出错时 function 返回值为`Promise`而不是返回 `null`。`return foo;` 和 `return await foo;`，有一些细微的差异：`return foo;`不管`foo`是 promise 还是 rejects 都将会直接返回`foo`。相反地，如果`foo`是一个`Promise`，`return await foo;`将等待`foo`执行 (resolve) 或拒绝 (reject)，如果是拒绝，将会在返回前抛出异常。

规范
--

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

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

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

参见
--

* `async function expression`
* `AsyncFunction` object
* `await`
* "Decorating Async Javascript Functions" on "innolitics.com"


if...else - JavaScript

if...else
=========

当指定条件为真，**if 语句**会执行一段语句。如果条件为假，则执行另一段语句。

尝试一下
----

语法
--
```

if (condition)
   statement1
[else
   statement2]
```

`condition`

值为真或假的表达式

`statement1`

当`condition`为真时执行的语句。可为任意语句，包括更深层的内部`if`语句。要执行多条语句，使用块语句（{ ... }）将这些语句分组；若不想执行语句，则使用空语句。

`statement2`

如果`condition`为假且 `else`从句存在时执行的语句。可为任意语句，包括块语句和嵌套的`if`语句。

说明
--

多层 `if...else` 语句可使用 `else if` 从句。注意：在 Javascript 中没有 `elseif` （一个单词）关键字。
```

if (condition1)
   statement1
else if (condition2)
   statement2
else if (condition3)
   statement3
...
else
   statementN
```

要看看它如何工作，可以调整下嵌套的缩进：
```

if (condition1)
   statement1
else
   if (condition2)
      statement2
   else
      if (condition3)
...
```

要在一个从句中执行多条语句，可使用语句块（`{ ... }`）。通常情况下，一直使用语句块是个好习惯，特别是在涉及嵌套`if`语句的代码中：
```

if (condition) {
   statements1
} else {
   statements2
}
```

不要将原始布尔值的`true`和`false`与Boolean对象的真或假混淆。任何一个值，只要它不是 `undefined`、`null`、 `0`、`NaN`或空字符串（`""`），那么无论是任何对象，即使是值为假的 Boolean 对象，在条件语句中都为真。例如：

```js
var b = new Boolean(false);
if (b) //表达式的值为 true
```

示例
--

### 使用 `if...else`

```js
if (cipher_char === from_char) {
  result = result + to_char;
  x++;
} else {
  result = result + clear_char;
}
```

### 使用 `else if`

注意，Javascript 中没有`elseif`语句。但可以使用`else`和`if`中间有空格的语句：

```js
if (x > 5) {
  /\* do the right thing \*/
} else if (x > 50) {
  /\* do the right thing \*/
} else {
  /\* do the right thing \*/
}
```

### 条件表达式中的赋值运算

建议不要在条件表达式中单纯的使用赋值运算，因为粗看下赋值运算的代码很容易让人误认为是等性比较。比如，不要使用下面示例的代码：

```js
if (x = y) {
  /\* do the right thing \*/
}
```

如果你需要在条件表达式中使用赋值运算，用圆括号包裹赋值运算。例如：

```js
if ((x = y)) {
  /\* do the right thing \*/
}
```

规范
--

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

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

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

参见
--

* `block`
* `switch`
* 条件（三元）运算符


continue - JavaScript

continue
========

**continue 声明**终止当前循环或标记循环的当前迭代中的语句执行，并在下一次迭代时继续执行循环。

尝试一下
----

语法
--
```

continue [ label ];
```

`label`

标识标号关联的语句

描述
--

与 `break` 语句的区别在于，continue 并不会终止循环的迭代，而是：

* 在 `while` 循环中，控制流跳转回条件判断；
* 在 `for` 循环中，控制流跳转到更新语句。

`continue` 语句可以包含一个可选的标号以控制程序跳转到指定循环的下一次迭代，而非当前循环。此时要求 `continue` 语句在对应的循环内部。

示例
--

### 在 `while` 语句中使用 `continue`

下述例子展示了一个在`i` 为 3 时执行`continue` 语句的 `while` 循环。因此，`n` 的值在几次迭代后分别为 1, 3, 7 和 12 ．

```js
i = 0;
n = 0;
while (i < 5) {
  i++;
  if (i === 3) {
    continue;
  }
  n += i;
}
```

### 使用带 label 的 continue

在下面的例子中，被标记为 `checkiandj` 的语句包含一个被标记为 `checkj` 的语句。当遇到`continue` 语句时，程序回到 `checkj` 语句的开始继续执行。每次遇到 `continue` 时，再次执行 `checkj` ，直到条件判断返回 false。之后完成 `checkiandj` 语句剩下的部分。

但如果 `continue` 的标号被改为 `checkiandj` ，那程序将会从 `checkiandj` 语句的开始继续运行。

参考 `label` 。

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

checkiandj: while (i < 4) {
  console.log("i: " + i);
  i += 1;

  checkj: while (j > 4) {
    console.log("j: " + j);
    j -= 1;
    if (j % 2 == 0) continue checkj;
    console.log(j + " is odd.");
  }
  console.log("i = " + i);
  console.log("j = " + j);
}
```

输出：

```js
"i: 0";

// start checkj
"j: 8";
"7 is odd.";
"j: 7";
"j: 6";
"5 is odd.";
"j: 5";
// end checkj

"i = 1";
"j = 4";

"i: 1";
"i = 2";
"j = 4";

"i: 2";
"i = 3";
"j = 4";

"i: 3";
"i = 4";
"j = 4";
```

规范
--

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

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

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

See also
--------

* `break`
* `label`


return - JavaScript

return
======

**`return`** 语句终止函数的执行，并返回一个指定的值给函数调用者。

尝试一下
----

语法
--

```js
return [expression]
```

`expression`

要返回的值的表达式。如果忽略，则返回 `undefined`。

描述
--

当在函数体中使用 `return` 语句时，函数将会停止执行。如果指定一个值，则该值会被返回给函数调用者。例如，以下函数返回其参数 `x` 的平方，其中 `x` 是数字。

```js
function square(x) {
  return x \* x;
}
const demo = square(3);
// demo will equal 9
```

如果省略该值，则返回 `undefined`。

下面的 return 语句都会终止函数的执行：

```js
return;
return true;
return false;
return x;
return x + y / 3;
```

### 自动插入分号

自动插入分号（ASI）规则会影响 `return` 语句。在 `return` 关键字和被返回的表达式之间不允许使用换行符。

```js
return
a + b;
```

根据 ASI，被转换为：

```js
return;
a + b;
```

控制台会警告“unreachable code after return statement”。

**备注：** 从 Firefox 40 开始，如果在 `return` 语句后发现不可达的代码，控制台会显示一个警告。

为了避免这个问题（防止 ASI），你可以使用括号：

```js
return (
  a + b
);
```

示例
--

### 中断一个函数的执行

函数将会在 `return` 语句执行后立即中止。

```js
function counter() {
  for (let count = 1; ; count++) {
    // 无限循环
    console.log(`${count}A`); // 执行 5 次
    if (count === 5) {
      return;
    }
    console.log(`${count}B`); // 执行 4 次
  }
  console.log(`${count}C`); // 永远不会执行
}

counter();

// Output:
// 1A
// 1B
// 2A
// 2B
// 3A
// 3B
// 4A
// 4B
// 5A
```

### 返回一个函数

参见关于闭包的文章。

```js
function magic() {
  return function calc(x) {
    return x \* 42;
  };
}

const answer = magic();
answer(1337); // 56154
```

规范
--

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

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

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

参见
--

* 函数
* 闭包


with - JavaScript

with
====

**已弃用:** 不再推荐使用该特性。虽然一些浏览器仍然支持它，但也许已从相关的 web 标准中移除，也许正准备移除或出于兼容性而保留。请尽量不要使用该特性，并更新现有的代码；参见本页面底部的兼容性表格以指导你作出决定。请注意，该特性随时可能无法正常工作。

**备注：** 不建议使用 `with` 语句，因为它可能是混淆错误和兼容性问题的根源。有关详细信息，请参阅下面“描述”一节中的“语意不明的弊端”部分。

**`with`** 语句扩展一个语句的作用域链。

语法
--

```js
with (expression)
  statement
```

`expression`

将给定的表达式添加到在评估语句时使用的作用域链上。表达式周围的括号是必需的。

`statement`

任何语句。要执行多个语句，请使用一个块语句 (`{ ... }`) 对这些语句进行分组。

描述
--

JavaScript 查找某个未使用命名空间的变量时，会通过作用域链来查找，作用域链是跟执行代码的 context 或者包含这个变量的函数有关。'with'语句将某个对象添加到作用域链的顶部，如果在 statement 中有某个未使用命名空间的变量，跟作用域链中的某个属性同名，则这个变量将指向这个属性值。如果沒有同名的属性，则将拋出`ReferenceError`异常。

**备注：** 不推荐使用`with`，在 ECMAScript 5 严格模式中该标签已被禁止。推荐的替代方案是声明一个临时变量来承载你所需要的属性。

### 性能方面的利与弊

**利**：`with` 语句可以在不造成性能损失的情況下，减少变量的长度。其造成的附加计算量很少。使用 'with' 可以减少不必要的指针路径解析运算。需要注意的是，很多情況下，也可以不使用 with 语句，而是使用一个临时变量来保存指针，来达到同样的效果。

**弊**：`with` 语句使得程序在查找变量值时，都是先在指定的对象中查找。所以那些本来不是这个对象的属性的变量，查找起来将会很慢。如果是在对性能要求较高的场合，'with' 下面的 statement 语句中的变量，只应该包含这个指定对象的属性。

### 语义不明的弊端

**弊端**：`with` 语句使得代码不易阅读，同时使得 JavaScript 编译器难以在作用域链上查找某个变量，难以决定应该在哪个对象上来取值。请看下面的例子：

```js
function f(x, o) {
  with (o) print(x);
}
```

`f`被调用时，`x`有可能能取到值，也可能是`undefined`，如果能取到，有可能是在 o 上取的值，也可能是函数的第一个参数`x`的值（如果 o 中没有这个属性的话）。如果你忘记在作为第二个参数的对象 o 中定义`x`这个属性，程序并不会报错，只是取到另一个值而已。

**弊端**：使用 `with` 语句的代码，无法向前兼容，特別是在使用一些原生数据类型的时候。看下面的例子：

```js
function f(foo, values) {
  with (foo) {
    console.log(values);
  }
}
```

如果是在 ECMAScript 5 环境调用`f([1,2,3], obj)`，则`with`语句中变量`values`将指向函数的第二个参数`values`。但是，ECMAScript 6 标准给`Array.prototype`添加了一个新属性`values`，所有数组实例将继承这个属性。所以在 ECMAScript 6 环境中，`with`语句中变量`values`将指向`[1,2,3].values`。

示例
--

### 使用 with 语句

下面的`with`语句指定`Math`对象作为默认对象。`with`语句里面的变量，分別指向`Math`对象的`PI` 、`cos` 和 `sin` 函数，不用在前面添加命名空间。后续所有引用都指向`Math`对象。

```js
var a, x, y;
var r = 10;

with (Math) {
  a = PI \* r \* r;
  x = r \* cos(PI);
  y = r \* sin(PI / 2);
}
```

规范
--

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

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

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

参见
--

* `block`
* Strict mode (en-US)
* `Symbol.unscopables`
* `Array.prototype[@@unscopables]`