1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
wizardforcel 0f0a0bca26 1
2018-04-29 18:30:49 +08:00

334 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 一、值,类型和运算符
> 原文:[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
```
``运算符表示减法,`/`运算符则表示除法。
在运算符同时出现,并且没有括号的情况下,其运算顺序根据运算符优先级确定。示例中的乘法运算符优先级高于加法。而`/`运算符和`*`运算符优先级相同,`+`运算符和``运算符优先级也相同。当多个具有相同优先级的运算符相邻出现时,运算从左向右执行,比如`12+1`的运算顺序是`(12)+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 支持三种逻辑运算符andor和非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 用作便携式计算器,但并不多。 下一章将开始将这些表达式绑定到基本程序中。