1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
wizardforcel 827274fc54 1.
2018-04-29 11:51:06 +08:00

19 KiB
Raw Blame History

第1章 值、类型和运算符

Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below.

Master Yuan-Ma, The Book of Programming

在计算机的世界当中只有数据。你可以读取、修改以及新建数据数据可以用来表示任何信息。所有的数据都用类似的方式进行存储长位bit序列。

我们通常使用二值0和1来表示位。在计算机中位可以用一个高电荷或者一个低电荷一个强信号或者一个弱信号抑或是光盘表面的凹凸点来表示。任何离散信息都可以简化成由0与1组成的序列因此这些信息都可以表示成位序列。

举例来说用位序列来表示数字13。我们可以仿照写十进制数字的方法来写位序列只是在写位序列的时候用的不是十个不同的数字而是两个。其中数字的权值以2为因子从右到左依次递增。以下是用位序列来表示的数字13其中每一位数字下方标注了该位的权值

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

因此二进制数00001101或者说是8+4+1即等于13。

1.1 值

让我们设想一下包含大量位序列的情况在一台标准现代计算机的易失性数据存储设备中就会包含超过300亿位的数据。而非易失性存储设备硬盘之类的存储设备则可以存储更多的数据。

为了能够顺利操作如此多的位数据而又不引起数据丢失我们可以将这些位划分成表示不同信息的块。在JavaScript中我们将这些数据块称为值。虽然所有的值都是由位序列构成的但是它们的功能却各不相同。每个值的类型决定了其功能定义。在JavaScript中包含6种基本的值类型数字number、字符串string、布尔值boolean、对象object、函数function和未定义类型undefined

The Ocean of Bits

创建值的时候,只需要调用其名称即可,非常方便。我们不需要预先了解新创建的值的类型或者为其预先开辟存储空间,只需调用某个值,即可立刻获取到它。当然,这些值也不会凭空产生,每个值总要有一个地方来进行存储,如果你想在同一时间存储海量的值数据,可能就会耗尽内存空间。不过幸运的是,只有你同时需要这么多数据的情况下,才会出现这种问题。如果你不再需要使用某个值了,这个值所对应的数据就会被清理和回收,供其他值来使用。

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

1.2 数字

数字number类型的值即数字值。在JavaScript中写成如下形式

13

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

JavaScript使用固定长度为64的位序列来存储数字值。我们只能使用64位存储序列产生一定数量的组合因此可以表示的数字个数也是有限的。对于十进制数来说如果长度有N位那么我们可以用其表示10<sup class="calibre3">N</sup>个数字。同理对于二进制数来说如果长度有64位则可以表示2<sup class="calibre3">64</sup>个数字大约是1800亿亿18后面跟18个0个数字这个数量已经相当大了。

在过去计算机的内存容量要比现在小得多因此人们一般会使用长度为8或16的位序列来表示数字。在这么小的范围内数字很容易溢出即给定的位序列无法存储超过这个范围的数字。而现在即便是个人计算机也会配置大量内存因此我们使用64位数据块来存储数字也不是什么问题也就是说在处理超大数字的时候也无需担心溢出。

但是在JavaScript中并不可以使用所有小于1800亿亿的数字。这块长度为64的位序列还要能够表示负数所以需要有一位符号位用来表示数字的正负。还有一个需要我们关注的问题是如何表示非整数为了实现该功能还需要使用一些位来存储小数点的位置。因此在JavaScript中实际可存储的数字范围是1900万亿9后面跟15个0这依旧是一个很大的数字。

使用小数点来表示分数。

9.81

对于过大或过小的数字来说可以使用带e即exponent指数的科学技术法来表示并在e的后面紧跟该数的指数。

2.998e8

即2.998×10<sup class="calibre3">8</sup>=299800000。

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

1.2.1 算术

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

100 + 4 * 11

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

在上述示例当中是“4加100再将加的结果乘以11”还是先做乘法再做加法如你所想这里会先做乘法。但在数学运算中我们可以将加法用括号括起来改变运算次序。

(100 + 4) * 11

“–”运算符表示减法,“/”运算符则表示除法。

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

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

“%”符号表示余数运算读者可能对这个算术运算符不太熟悉。X%Y表示求X除以Y后所得的的余数。比如314%100的结果是14而144%12的结果是0。求余运算符的优先级和乘除法相同。该运算符常常被称为模数运算符但准确来说还是应该称其为求余运算符。

1.2.2 特殊数字

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

前两个是Infinity和-Infinity它们分别表示正无穷大和负无穷大。Infinity1的结果仍然是Infinity以此类推。不要过于依赖无穷运算的结果因为这类运算并不真正属于数学运算由此我们可以引出下一个特殊数字NaN。

虽然NaN是数字类型的值但我们用其表示“非数值”。举例来说在计算0/00除以0、InfinityInfinity或当数值运算中产生不精确或毫无意义的结果时就用NaN来表示。

1.3 字符串

另一个基本数据类型是字符串,我们使用字符串来表示文本信息。使用引号将内容括起来。

"Patch my boat with chewing gum"
'Monkeys wave goodbye'

其中,单引号和双引号都可以用来标记字符串,只要保证字符串前后引号一致即可。

我们几乎可以在引号中填写任何字符JavaScript会使用这些字符来生成字符串。但填写有些字符会稍微复杂一些。比如将引号放在引号中就比较麻烦。另外由于字符串只能放在一行里所以换行符输入回车键所产生的字符也无法放在引号之间。

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

"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"”可以写成:

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

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

"con" + "cat" + "e" + "nate"

还有很多其他方式来操作字符串我们会在第4章中进行讨论。

1.4 一元运算符

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

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

我们会在示例代码中使用console.log命令来打印并表示我们需要查看的一些运算结果。当你执行了这段代码后屏幕上就会显示出运算产生的值而如何显示结果则取决于执行程序所使用的JavaScript环境。

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

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

1.5 布尔值

我们常常需要用一个值来简单地区分两种可能性比如说“是”和“否”以及“开”和“关”。JavaScript使用布尔类型来表示这种情况该类型的值只有两种取值true和false就用这两个英文单词来表示

1.5.1 比较

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

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

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

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

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

字符串的比较是按照字母顺序来进行比较的,大写字母总是“小于”小写字母,因此"Z"<"a"的结果为真另外字符顺序中也包括了非字母字符比如。实际上字符的比较是基于Unicode标准实现的。该标准为你需要的每个字符赋予了一个数字包括希腊文、阿拉伯文、日文和泰米尔文等。使用这些数字有助于将字符串存储在计算机中因为这样我们就可以将字符与数字一一对应并将字符串存储成数字的序列了。在比较字符串时JavaScript从左向右逐个比较每个字符对应的数字编码。

其他类似的运算符则包括>=(大于等于)、<=(小于等于)、==(等于)和!=(不等于)。

console.log("Itchy" != "Scratchy")
// → true

在JavaScript中只有一个值不等于其自身那就是NaNNot a Number非数值

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

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

1.5.2 逻辑运算符

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

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

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

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

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

感叹号表示逻辑非该运算符是一元运算符用于反转给定的值比如true的结果是falsefalse结果是true。

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

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

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

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

该运算符被称为条件运算符或者是三元运算符因为JavaScript语言中只有这唯一的一个三元运算符。条件运算符根据问号左侧条件的真伪从其余两个值中挑选出一个作为结果。当条件为真时选择中间的值当条件为假时选择右侧的值。

1.6 未定义值

这里有两个特殊的值分别是null和undefined用于表示无意义的值。它们各自表示其自身含义除此之外不包含任何信息。

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

JavaScript语言设计上的问题导致了undefined和null含义存在些许不同在绝大部分情况下这种区别无关痛痒。如果你遇到了不得不区分这两个值的特殊情况那么我的建议是将这两个值视作可互换的值稍后对其进行详细介绍

1.7 自动类型转换

在本书的开篇我曾提到JavaScript可以处理任何程序即便程序的行为让人难以捉摸。我们可以通过以下表达式来看出这一点

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结果请检查一下是否发生了错误的类型转换。

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

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

上文提及的最后一点其实非常有用。当我们想确定一个值是否为真而非null或undefined时直接使用“==”(或“!=”)运算符来进行比较即可。

但当你想要测试一个值是否严格等于false时会发生什么呢字符串与数字的布尔类型转换规则是JavaScript会将0、NaN和空字符串""视为false其他值视为true。因此诸如0==false和""==false之类的表达式都是true。在这种情况下如果我们不希望在比较的时候进行任何自动类型转换可以使用另外两个运算符===和!==。第一个运算符用于检测两个值是否严格相等,第二个运算符用于测试是否严格不等。所以""===false的结果是false正如我们预期。

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

逻辑运算符的短路特性

逻辑运算符&&和||可以使用一种特殊方式来处理不同类型的值。这两个运算符会将左侧的值转换成布尔类型,以决定如何进行后续操作,但返回左侧值还是返回右侧值,则取决于运算符和左侧转换结果。

举例来说当左侧值可以被转换成true时||运算符会直接返回左侧的值,否则会返回右侧的值。当你希望以布尔值的方式来处理其他类型的值时,这种转换就派上用场了。

console.log(null || "user")
// → user
console.log("Karl" || "user")
// → Karl

||运算符的这种功能可用于返回默认值。如果左侧表达式可能产生空值,那么右侧的表达式可以在左侧表达式为空时作为替代。

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

这两种运算符的另一个重要特性是只有必要时才会计算右侧的表达式。以true||X为例无论X是什么即使X会进行一些可怕操作其结果都是true而且永远都不会计算X。false&&X这种情况也是一样只要左侧是falseX就会被忽略。这被称为短路计算。

条件运算符也会以类似方式工作。第一个表达式总会被计算,但第二个或第三个表达式只有在被选择时才会被计算。

1.8 本章小结

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

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

利用这些知识,你完全可以编写出一个迷你计算器了,但仅限于此。在下一章中,我们准备将这些表达式应用在基本的程序当中。