## 一、值，类型和运算符

> 原文：[Values, Types, and Operators](http://eloquentjavascript.net/01_values.html)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
> 
> 部分参考了[《JavaScript 编程精解（第 2 版）》](https://book.douban.com/subject/26707144/)

> 在机器的表面之下，程序在运转。 它不费力就可以扩大和缩小。 在和谐的关系中，电子散开并重新聚合。 监视器上的表格只是水面上的涟漪。 本质隐藏在下面。
> 
> Master Yuan-Ma，《The Book of Programming》

![](img/1-0.jpg)

计算机世界里只有数据。 你可以读取数据，修改数据，创建新数据 - 但不能提及不是数据的东西。 所有这些数据都以位的长序列存储，因此基本相似。

位是任何类型的二值的东西，通常描述为零和一。 在计算机内部，他们有一些形式，例如高电荷或低电荷，强信号或弱信号，或 CD 表面上的亮斑点或暗斑点。 任何一段离散信息都可以简化为零和一的序列，从而以位表示。

例如，我们可以用位来表示数字 13。 它的原理与十进制数字相同，但不是 10 个不同的数字，而只有 2 个，每个数字的权重从右到左增加 2 倍。 以下是组成数字 13 的位，下方显示数字的权重：

```
   0   0   0   0   1   1   0   1
 128  64  32  16   8   4   2   1
```

因此，这就是二进制数`00001101`，或者`8+4+1`，即 13。

## 值

想象一下位之海 - 一片它们的海洋。 典型的现代计算机的易失性数据存储器（工作存储器）中，有超过 300 亿位。非易失性存储（硬盘或等价物）往往还有几个数量级。

为了能够在不丢失的情况下，处理这些数量的数据，我们必须将它们分成代表信息片段的块。 在 JavaScript 环境中，这些块称为值。 虽然所有值都是由位构成的，但他们起到不同的作用，每个值都有一个决定其作用的类型。 有些值是数字，有些值是文本片段，有些值是函数，等等。

要创建一个值，你只需要调用它的名字。 这很方便。 你不必为你的值收集建筑材料或为其付费。 你只需要调用它，然后刷的一下，你就有了它。 当然，它们并不是真正凭空创造的。 每个值都必须存储在某个地方，如果你想同时使用大量的值，则可能会耗尽内存。 幸运的是，只有同时需要它们时，这才是一个问题。 只要你不再使用值，它就会消失，留下它的一部分作为下一代值的建筑材料。

本章将会介绍 JavaScript 程序当中的基本元素，包括简单的值类型以及值运算符。

## 数字

数字（`Number`）类型的值即数字值。在 JavaScript 中写成如下形式：

```js
13
```

在程序中使用这个值的时候，就会将数字 13 以位序列的方式存放在计算机的内存当中。

JavaScript使用固定数量的位（64 位）来存储单个数字值。 你可以用 64 位创造很多模式，这意味着可以表示的不同数值是有限的。 对于`N`个十进制数字，可以表示的数值数量是`10^N`。 与之类似，给定 64 个二进制数字，你可以表示`2^64`个不同的数字，大约 18 亿亿（18 后面有 18 个零）。太多了。

过去计算机内存很小，人们倾向于使用一组 8 位或 16 位来表示他们的数字。 这么小的数字很容易意外地溢出，最终得到的数字不能放在给定的位数中。 今天，即使是装在口袋里的电脑也有足够的内存，所以你可以自由使用 64 位的块，只有在处理真正的天文数字时才需要担心溢出。

不过，并非所有 18 亿亿以下的整数都能放在 JavaScript 数值中。 这些位也存储负数，所以一位用于表示数字的符号。 一个更大的问题是，也必须表示非整数。 为此，一些位用于存储小数点的位置。 可以存储的实际最大整数更多地在 9 亿亿（15 个零）的范围内 - 这仍然相当多。

使用小数点来表示分数。

```js
9.81
```

对于非常大或非常小的数字，你也可以通过输入`e`（表示指数），后面跟着指数来使用科学记数法：

```js
2.998e8
```

即`2.998 * 10^8 = 299,800,000`。

当计算小于前文当中提到的 9000 万亿的整数时，其计算结果会十分精确，不过在计算小数的时候精度却不高。正如（`pi`）无法使用有限个数的十进制数字表示一样，在使用 64 位来存储分数时也同样会丢失一些精度。虽说如此，但这类丢失精度只会在一些特殊情况下才会出现问题。因此我们需要注意在处理分数时，将其视为近似值，而非精确值。

### 算术

与数字密切相关的就是算术。比如，加法或者乘法之类的算术运算会使用两个数值，并产生一个新的数字。JavaScript 中的算术运算如下所示：

```js
100 + 4 * 11
```

我们把`+`和`*`符号称为运算符。第一个符号表示加法，第二个符号表示乘法。将一个运算符放在两个值之间，该运算符将会使用其旁边的两个值产生一个新值。

但是这个例子的意思是“将 4 和 100 相加，并将结果乘 11”，还是是在加法之前计算乘法？ 正如你可能猜到的那样，乘法首先计算。 但是和数学一样，你可以通过将加法包在圆括号中来改变它：

```js
(100 + 4) * 11
```

`–`运算符表示减法，`/`运算符则表示除法。

在运算符同时出现，并且没有括号的情况下，其运算顺序根据运算符优先级确定。示例中的乘法运算符优先级高于加法。而`/`运算符和`*`运算符优先级相同，`+`运算符和`–`运算符优先级也相同。当多个具有相同优先级的运算符相邻出现时，运算从左向右执行，比如`1–2+1`的运算顺序是`(1–2)+1`。

你无需担心这些运算符的优先级规则，不确定的时候只需要添加括号即可。

还有一个算术运算符，你可能无法立即认出。 `%`符号用于表示取余操作。 `X % Y`是`Y`除`X`的余数。 例如，`314 % 100`产生`14`，`144 % 12`产生`0`。 余数的优先级与乘法和除法的优先级相同。 你还经常会看到这个运算符被称为模运算符。

### 特殊数字

在 JavaScript 中有三个特殊的值，它们虽然是数字，但看起来却跟一般的数字不太一样。

前两个是`Infinity`和`-Infinity`，它们代表正无穷和负无穷。 “无穷减一”仍然是“无穷”，依此类推。 尽管如此，不要过分信任基于无穷大的计算。 它在数学上不合理，并且很快导致我们的下一个特殊数字：`NaN`。

`NaN`代表“不是数字”，即使它是数字类型的值。 例如，当你尝试计算`0/0`（零除零），`Infinity - Infinity`或任何其他数字操作，它不会产生有意义的结果时，你将得到此结果。

## 字符串

下一个基本数据类型是字符串（`String`）。 字符串用于表示文本。 它们是用引号括起来的：

```js
`Down on the sea`
"Lie on the ocean"
'Float on the ocean'
```

只要字符串开头和结尾的引号匹配，就可以使用单引号，双引号或反引号来标记字符串。

几乎所有的东西都可以放在引号之间，并且 JavaScript 会从中提取字符串值。 但少数字符更难。 你可能难以想象，如何在引号之间加引号。 当使用反引号（`` ` ``）引用字符串时，换行符（当你按回车键时获得的字符）可能会被包含，而无需转义。

若要将这些字符存入字符串，需要使用下列规则：当反斜杠（`\`）出现在引号之间的文本中时，表示紧跟在其后的字符具有特殊含义，我们将其称之为转义符。当引号紧跟在反斜杠后时，并不意味着字符串结束，而表示这个引号是字符串的一部分。当字符`n`出现在反斜杠后时，JavaScript 将其解释成换行符。以此类推，`\t`表示制表符，我们来看看下面这个字符串：

```js
"This is the first line\nAnd this is the second"
```

该字符串实际表示的文本是：

```
This is the first line
And this is the second
```

当然，在某些情况下，你希望字符串中的反斜杠只是反斜杠，而不是特殊代码。 如果两个反斜杠写在一起，它们将合并，并且只有一个将留在结果字符串值中。 这就是字符串“`A newline character is written like "\n".`”的表示方式：

```js
"A newline character is written like \"\\n\"."
```

字符串也必须建模为一系列位，以便能够存在于计算机内部。 JavaScript 执行此操作的方式基于 Unicode 标准。 该标准为你几乎需要的每个字符分配一个数字，包括来自希腊语，阿拉伯语，日语，亚美尼亚语，以及其他的字符。 如果我们为每个字符分配一个数字，则可以用一系列数字来描述一个字符串。

这就是 JavaScript 所做的。 但是有一个复杂的问题：JavaScript 的表示为每个字符串元素使用 16 位，它可以描述多达 2 的 16 次方个不同的字符。 但是，Unicode 定义的字符多于此 - 大约是此处的两倍。 所以有些字符，比如许多 emoji，在 JavaScript 字符串中占据了两个“字符位置”。 我们将在第 5 章中回来讨论。

我们不能将除法，乘法或减法运算符用于字符串，但是`+`运算符却可以。这种情况下，运算符并不表示加法，而是连接操作：将两个字符串连接到一起。以下语句可以产生字符串`"concatenate"`：

```js
"con" + "cat" + "e" + "nate"
```

字符串值有许多相关的函数（方法），可用于对它们执行其他操作。 我们将在第 4 章中回来讨论。

用单引号或双引号编写的字符串的行为非常相似 - 唯一的区别是需要在其中转义哪种类型的引号。 反引号字符串，通常称为模板字面值，可以实现更多的技巧。 除了能够跨越行之外，它们还可以嵌入其他值。

```js
`half of 100 is ${100 / 2}`
```

当你在模板字面值中的`$ {}`中写入内容时，将计算其结果，转换为字符串并包含在该位置。 这个例子产生`"half of 100 is 50"`。

## 一元运算符

并非所有的运算符都是用符号来表示，还有一些运算符是用单词表示的。比如`typeof`运算符，会产生一个字符串的值，内容是给定值的具体类型。

```js
console.log(typeof 4.5)
// → number
console.log(typeof "x")
// → string
```

我们将在示例代码中使用`console.log`，来表示我们希望看到求值结果。更多内容请见下一章。

我们所见过的绝大多数运算符都使用两个值进行操作，而`typeof`仅接受一个值进行操作。使用两个值的运算符称为二元运算符，而使用一个值的则称为一元运算符。减号运算符既可用作一元运算符，也可用作二元运算符。

```js
console.log(- (10 - 2))
// → -8
```

## 布尔值

拥有一个值，它能区分两种可能性，通常是有用的，例如“是”和“否”或“开”和“关”。 为此，JavaScript 拥有布尔（`Boolean`）类型，它有两个值：`true`和`false`，它们就写成这些单词。

### 比较

一种产生布尔值的方法如下所示：

```js
console.log(3 > 2)
// → true
console.log(3 < 2)
// → false
```

`>`和`<`符号分别表示“大于”和“小于”。这两个符号是二元运算符，通过该运算符返回的结果是一个布尔值，表示其运算是否为真。

我们可以使用相同的方法比较字符串。

```js
console.log("Aardvark" < "Zoroaster")
// → true
```

字符串排序的方式大致是字典序，但不真正是你期望从字典中看到的那样：大写字母总是比小写字母“小”，所以`"Z"<"A"`，非字母字符（`!`，`-`等）也包含在排序中。 比较字符串时，JavaScript 从左向右遍历字符，逐个比较 Unicode 代码。

其他类似的运算符则包括`>=`（大于等于），`<=`（小于等于），`==`（等于）和`!=`（不等于）。

```js
console.log("Apple" == "Orange")
// → false
```

在 JavaScript 中，只有一个值不等于其自身，那就是`NaN`（Not a Number，非数值）。

```js
console.log(NaN == NaN)
// → false
```

`NaN`用于表示非法运算的结果，正因如此，不同的非法运算结果也不会相等。

### 逻辑运算符

还有一些运算符可以应用于布尔值上。JavaScript 支持三种逻辑运算符：与（and），或（or）和非（not）。这些运算符可以用于推理布尔值。

`&&`运算符表示逻辑与，该运算符是二元运算符，只有当赋给它的两个值均为`true`时其结果才是真。

```js
console.log(true && false)
// → false
console.log(true && true)
// → true
```

`||`运算符表示逻辑或。当两个值中任意一个为`true`时，结果就为真。

```js
console.log(false || true)
// → true
console.log(false || false)
// → false
```

感叹号（`!`）表示逻辑非，该运算符是一元运算符，用于反转给定的值，比如`!true`的结果是`false`，而`!false`结果是`true`。

在混合使用布尔运算符和其他运算符的情况下，总是很难确定什么时候需要使用括号。实际上，只要熟悉了目前为止我们介绍的运算符，这个问题就不难解决了。`||`优先级最低，其次是`&&`，接着是比较运算符（`>`，`==`等），最后是其他运算符。基于这些优先级顺序，我们在一般情况下最好还是尽量少用括号，比如说：

```js
1 + 1 == 2 && 10 * 10 > 50
```

现在我们来讨论最后一个逻辑运算符，它既不属于一元运算符，也不属于二元运算符，而是三元运算符（同时操作三个值）。该运算符由一个问号和冒号组成，如下所示。

```js
console.log(true ? 1 : 2);
// → 1
console.log(false ? 1 : 2);
// → 2
```

这个被称为条件运算符（或者有时候只是三元运算符，因为它是该语言中唯一的这样的运算符）。 问号左侧的值“挑选”另外两个值中的一个。 当它为真，它选择中间的值，当它为假，则是右边的值。

## 空值

有两个特殊值，写成`null`和`undefined`，用于表示不存在有意义的值。 它们本身就是值，但它们没有任何信息。

在 JavaScript 语言中，有许多操作都会产生无意义的值（我们会在后面的内容中看到实例），这些操作会得到`undefined`的结果仅仅只是因为每个操作都必须产生一个值。

`undefined`和`null`之间的意义差异是 JavaScript 设计的一个意外，大多数时候它并不重要。 在你实际上不得不关注这些值的情况下，我建议将它们视为几乎可互换的。

## 自动类型转换

在引言中，我提到 JavaScript 会尽可能接受几乎所有你给他的程序，甚至是那些做些奇怪事情的程序。 以下表达式很好地证明了这一点：

```js
console.log(8 * null)
// → 0
console.log("5" - 1)
// → 4
console.log("5" + 1)
// → 51
console.log("five" * 2)
// → NaN
console.log(false == 0)
// → true
```

当运算符应用于类型“错误”的值时，JavaScript 会悄悄地将该值转换为所需的类型，并使用一组通常不是你想要或期望的规则。 这称为类型转换。 第一个表达式中的`null`变为`0`，第二个表达式中的`"5"`变为`5`（从字符串到数字）。 然而在第三个表达式中，`+`在数字加法之前尝试字符串连接，所以`1`被转换为`"1"`（从数字到字符串）。

当某些不能明显映射为数字的东西（如`"five"`或`undefined`）转换为数字时，你会得到值`NaN`。 `NaN`进一步的算术运算会产生`NaN`，所以如果你发现自己在一个意想不到的地方得到了它，需要寻找意外的类型转换。

当相同类型的值之间使用`==`符号进行比较时，其运算结果很好预测：除了`NaN`这种情况，只要两个值相同，则返回`true`。但如果类型不同，JavaScript 则会使用一套复杂难懂的规则来确定输出结果。在绝大多数情况下，JavaScript 只是将其中一个值转换成另一个值的类型。但如果运算符两侧存在`null`或`undefined`，那么只有两侧均为`null`或`undefined`时结果才为`true`。

```js
console.log(null == undefined);
// → true
console.log(null == 0);
// → false
```

这种行为通常很有用。 当你想测试一个值是否具有真值而不是`null`或`undefined`时，你可以用`==`（或`!=`）运算符将它与`null`进行比较。

但是如果你想测试某些东西是否严格为“false”呢？ 字符串和数字转换为布尔值的规则表明，`0`，`NaN`和空字符串（`""`）计为`false`，而其他所有值都计为`true`。 因此，像`'0 == false'`和`"" == false`这样的表达式也是真的。 当你不希望发生自动类型转换时，还有两个额外的运算符：`===`和`!==`。 第一个测试是否严格等于另一个值，第二个测试它是否不严格相等。 所以`"" === false`如预期那样是错误的。

我建议使用三字符比较运算符来防止意外类型转换的发生，避免作茧自缚。但如果比较运算符两侧的值类型是相同的，那么使用较短的运算符也没有问题。

### 逻辑运算符的短路特性

逻辑运算符`&&`和`||`以一种特殊的方式处理不同类型的值。 他们会将其左侧的值转换为布尔型，来决定要做什么，但根据运算符和转换结果，它们将返回原始的左侧值或右侧值。

例如，当左侧值可以转换为`true`时，`||`运算符会返回它，否则返回右侧值。 当值为布尔值时，这具有预期的效果，并且对其他类型的值做类似的操作。

```js
console.log(null || "user")
// → user
console.log("Agnes" || "user")
// → Agnes
```

我们可以此功能用作回落到默认值的方式。 如果你的一个值可能是空的，你可以把`||`和备选值放在它之后。 如果初始值可以转换为`false`，那么你将得到备选值。

`&&`运算符工作方式与其相似但不相同。当左侧的值可以被转换成`false`时，`&&`运算符会返回左侧值，否则返回右侧值。

这两个运算符的另一个重要特性是，只在必要时求解其右侧的部分。 在`true || X`的情况下，不管`X`是什么 - 即使它是一个执行某些恶意操作的程序片段，结果都是`true`，并且`X`永远不会求值。 `false && X`也是一样，它是`false`的，并且忽略`X`。 这称为短路求值。

条件运算符以类似的方式工作。 在第二个和第三个值中，只有被选中的值才会求值。

## 本章小结

在本章中，我们介绍了 JavaScript 的四种类型的值：数字，字符串，布尔值和未定义值。

通过输入值的名称（`true`，`null`）或值（`13`，`"abc"`）就可以创建它们。你还可以通过运算符来对值进行合并和转换操作。本章已经介绍了算术二元运算符（`+`，`–`，`*`，`/`和`%`），字符串连接符（`+`），比较运算符（`==`，`!=`，`===`，`!==`，`<`，`>`，`<=`和`>=`），逻辑运算符（`&&`和`||`）和一些一元运算符（`–`表示负数，`!`表示逻辑非，`typeof`用于查询值的类型）。

这为你提供了足够的信息，将 JavaScript 用作便携式计算器，但并不多。 下一章将开始将这些表达式绑定到基本程序中。
