本节书摘来自异步社区《JavaScript面向对象编程指南》一书中的第2章,第2.3节,作者: 【加】Stoyan Stefanov 译者: 凌杰 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.3 基本数据类型
我们在程序中所使用的任何值都是有类型的。在JavaScript中,主要包含以下几大基本数据类型。
1.数字——包括浮点数与整数,例如1、100、3.14。
2.字符串——一序列由任意数量字符组成的序列,例如"a"、"one"、"one 2 three"。
3.布尔值——true或false。
4.undefined——当我们试图访问一个不存在的变量时,就会得到一个特殊值: undefined。除此之外,使用一个未初始化的变量也会如此。因为JavaScript会自动将变量在初始化之前的值设定为undefined。
5.null——这是另一种只包含一个值的特殊数据类型。所谓的null值,通常是指没有值、空值,不代表任何东西。null与undefined最大的不同在于,被赋予null的变量通常被认为是已经定义了的,只不过它不代表任何东西。关于这一点,我们稍后会通过一些具体的示例来解释。
任何不属于上述五种基本类型的值都会被认为是一个对象。甚至有时候我们也会将null视为对象,这会使人有些尴尬——这是一个不代表任何东西的对象(东西)。我们将会在第4章中深入阐述对象的概念,现在我们只需要记住一点,JavaScript中的数据类型主要分为以下两个部分。
基本类型(上面列出的五种类型)。
非基本类型(即对象)。
2.3.1 查看类型操作符——typeof
如果我们想知道某个变量或值的数据类型,可以调用一种叫做typeof的特殊操作符。该操作符会返回一个代表数据类型的字符串,它的值包括:“number”、“string”、“boolean”、“undefined”、“object”和“function”。在接下来的几节中,我们将会逐一展示对五种基本数据类型使用typeof操作符时的情况。
2.3.2 数字
最简单的数字类型当然就是整数了。如果我们将一个变量赋值为1,并对其调用typeof操作符,控制台就会返回字符串“number”,请看下面的代码。此外要注意的是,当您第二次设置变量值时,就不需要再使用var语句了。
>>> var n = 1;
>>> typeof n;
"number"
>>> n = 1234;
>>> typeof n;
"number"
当然,这也同样适用于浮点数(即含小数部分的数字):
>>> var n2 = 1.23;
>>> typeof n;
"number"
1 除了对变量赋值以外,我们也可以直接对一个数值调用typeof。例如:
>>> typeof 123;
"number"
2.3.2.1 八进制与十六进制
当一个数字以0开头时,就表示这是一个八进制数。例如,八进制数0377所代表的就是十进制数255。
>>> var n3 = 0377;
>>> typeof n3;
"number"
>>> n3;
255
如您所见,例子中最后一行所输出的就是该八进制数的十进制表示形式。如果您对八进制数还不太熟悉,那么十六进制您一定不会感到陌生,毕竟,CSS样式表中的颜色值使用的都是十六进制。
在CSS中,我们定义颜色的方式有以下两种。
使用十进制数分别指定R(红)、G(绿)、B(蓝)的值2,取值范围都为0~255。例如rgb(0,0,0)代表黑色、rgb(255,0,0)代表红色(红值达到最大值,而绿和蓝都为0值)。
使用十六进制数,两个数位代表一种色值,依次是R、G、B。例如#000000代表黑色、#ff0000代表红色,因为十六进制的ff就等于255。
在JavaScript中,我们会用0x前缀来表示一个十六进制值(简称为hex)。
>>> var n4 = 0x00;
>>> typeof n4;
"number"
>>> n4;
0
>>> var n5 = 0xff;
>>> typeof n5;
"number"
>>> n5;
255
2.3.2.2 指数表示法
一个数字可以表示成1e1(或者1e+1、1E1、1E+1)这样的指数形式,意思是在数字1后面加1个0,也就是10。同理,2e+3的意思是在数字2后面加3个0,也就是2000。
>>> 1e1
10
>>> 1e+1
10
>>> 2e+3
2000
>>> typeof 2e+3;
"number"
此外,我们也可以将2e+3理解为将数字2的小数点向右移三位。依照同理,2e-3也就能被理解为是将数字2的小数点左移三位。
>>> 2e-3
0.002
>>> 123.456E-3
0.123456
>>> typeof 2e-3
"number"
2.3.2.3 Infinity
在JavaScript中,还有一种叫做Infinity的特殊值。它所代表的是超出了JavaScript处理范围的数值。但Infinity依然是一个数字,我们可以在控制台使用typeof来测试Infinity。当我们输入1e308时,一切正常,但一旦将后面的308改成309就出界了。经实践证明,JavaScript所能处理的最大值是1.7976931348623157e+308,而最小值为5e-324。
>>> Infinity
Infinity
>>> typeof Infinity
"number"
>>> 1e309
Infinity
>>> 1e308
1e+308
另外,任何数除0也为infinity:
>>> var a = 6 / 0;
>>> a
Infinity
Infinity表示的是最大数(或者比最大数还要大的数),那么最小数该如何表示呢?答案是在Infinity之前加一个负号:
>>> var i = -Infinity;
>>> i
-Infinity
>>> typeof i
"number"
但这是不是意味着我们可以得到双倍的Infinity呢?——毕竟我们可以从0加到Infinity,也可以从0减到-Infinity。好吧,这只是个玩笑。事实上这是不可能的,因为即便将正负Infinity相加,我们也不会得到0,而是会得到一个叫做NaN(Not A Number的缩写,即不是数字)的东西。
>>> Infinity - Infinity
NaN
>>> -Infinity + Infinity
NaN
而且,Infinity与其他的任何操作数执行任何算术运算的结果,都是Infinity。
>>> Infinity - 20
Infinity
>>> -Infinity * 3
-Infinity
>>> Infinity / 2
Infinity
>>> Infinity - 99999999999999999
Infinity
2.3.2.4 NaN
还记得之前见过的那个NaN吗?尽管该值的名字叫做“不是数字”,但事实上它依然属于数字,只不过是一种特殊的数字罢了。
>>> typeof NaN
"number"
>>> var a = NaN;
>>> a
NaN
如果我们在对一个假定的数字执行某个操作时失败了,就会得到一个NaN。例如,当我们试图将10与字符"f"相乘时,其结果就会为NaN,因为"f"显然是不支持乘法运算的。
>>> var a = 10 * "f";
>>> a
NaN
而且,NaN是具有传染性的,只要我们的算术运算中存在一个NaN,整个运算就会失败。
>>> 1 + 2 + NaN
NaN
2.3.3 字符串
字符串通常指的是一组用于表示文本的字符序列。在JavaScript中,一对双引号或单引号之间的任何值都会被视为一个字符串。也就是说,1是一个数字的话,"1"就是一个字符串了。在一个字符串上,typeof操作符会返回“string”。
>>> var s = "some characters";
>>> typeof s;
"string"
>>> var s = 'some characters and numbers 123 5.87';
>>> typeof s;
"string"
字符串中可以包含数字,例如:
>>> var s = '1';
>>> typeof s;
"string"
如果引号之间没有任何东西,它所表示的依然是一个字符串(即空字符串):
>>> var s = ""; typeof s;
"string"
之前,当我们在两个数字之间使用加号时,所执行的是加法运算。但在字符串中,这是一个字符串拼接操作,它返回的是两个字符串拼接之后的结果。例如:
>>> var s1 = "one"; var s2 = "two"; var s = s1 + s2; s;
"onetwo"
>>> typeof s;
"string"
像+这样的双功能操作符可能会带来一些错误。因此,我们如果想执行拼接操作的话,最好确保其所有的操作数都是字符串。同样的,在执行数字相加时,我们也要确保其所有的操作数都是数字。至于如何做到这一点,我们将会在后续章节中详细讨论。
2.3.3.1 字符串转换
当我们将一个数字字符串用于算术运算中的操作数时,该字符串会在运算中被当做数字类型来使用。(由于加法操作符的歧义性,这条规则不适用于加法运算。)
>>> var s = '1'; s = 3 * s; typeof s;
"number"
>>> s
3
>>> var s = '1'; s++; typeof s;
"number"
>>> s
2
于是,将数字字符串转换为数字就有了一种偷懒的方法:只需将该字符串与1相乘即可。(当然,更好的选择是调用parseInt函数,关于这点,我们将会在下一章中介绍。)
>>> var s = "100"; typeof s;
"string"
>>> s = s * 1;
100
>>> typeof s;
"number"
如果转换操作失败了,我们就会得到一个NaN值。
>>> var d = '101 dalmatians';
>>> d * 1
NaN
此外,将其他类型转换为字符串也有一种偷懒的方法,只需要将其与空字符串连接即可:
>>> var n = 1;
>>> typeof n;
"number"
>>> n = "" + n;
"1"
>>> typeof n;
"string"
2.3.3.2 特殊字符串
在表2-2中,我们列出了一些具有特殊含义的字符串。
除此之外,还有一些很少被使用的特殊字符,例如:b(退格符)、v(纵向制表符)、f(换页符)等。
2.3.4 布尔值
布尔类型中只有两种值:true和false。它们可用于引号以外的任何地方。
>>> var b = true; typeof b;
"boolean"
>>> var b = false; typeof b;
"boolean"
如果true或false在引号内,它就是一个字符串。
>>> var b = "true"; typeof b;
"string"
2.3.4.1 逻辑运算符
在JavaScript中,主要有三种逻辑运算符,它们都属于布尔运算。分别是:
!——逻辑非(取反)。
&&——逻辑与。
||——逻辑或。
在JavaScript中,如果我们想描述一些日常生活中非真即假的事物,就可以考虑使用逻辑非运算符:
>>> var b = !true;
>>> b;
false
如果在同一个值上执行两次逻辑非运算,其结果就等于原值3:
>>> var b = !!true;
>>> b;
true
如果在一个非布尔值上执行逻辑运算,该值会在计算期间被转换为布尔值:
>>> var b = "one";
>>> !b;
false
如您所见,上例中的字符串"one"是先被转换为布尔值true然后再取反的,结果为false。如果我们对它取反两次,结果就会为true。例如:
>>> var b = "one";
>>> !!b;
true
使用双重取反操作可以很容易地将任何值转换为等效的布尔值。虽然这种方法很少被用到,但从另一个角度也说明了将其他类型的值转换为布尔值的重要性。而事实上,除了下面所列出特定值以外(它们将被转换为false),其余大部分值在转换为布尔值时都为true。
空字符串""
null
undefined
数字0
数字NaN
布尔值false
这6个值有时也会被我们统称为falsy,而其他值则被称为truthy(包括字符串"0"、""、"false")。
接下来,让我们来看看另外两个操作符——逻辑与和逻辑或的使用示例。当我们使用逻辑与操作符时,当且仅当该操作所有操作数为true时,它才为true。而逻辑或操作则只需要至少一个操作数为true即可为true。
>>> var b1 = true; var b2 = false;
>>> b1 || b2
true
>>> b1 && b2
false
在表2-3中,我们列出了所有可能的情况及其相应结果。
当然,我们也能连续执行若干个逻辑操作。例如:
>>> true && true && false && true
false
>>> false || true || false
true
我们还可以在同一个表达式中混合使用&&和||。不过在这种情况下,我们最好用括号来明确一下操作顺序。例如:
>>> false && false || true && true
true
>>> false && (false || true) && true
false
2.3.4.2 操作符优先级
您可能会想知道,为什么上例中的第一个表达式(false && false || true && true)结果为true。答案在于操作符优先级。这看上去有点像数学,例如:
>>> 1 + 2 * 3
7
由于乘法运算的优先级高于加法,所以该表达式会先计算2*
3,这就相当于我们输入的表达式是:
> >>> 1 + (2 * 3)
7
逻辑运算符也一样,!的优先级最高,因此在没有括号限定的情况下它将会被最先执行。然后,接下来的优先顺序是先&&后||。也就是说:
>>> false && false || true && true
true
与下面表达式等效:
>>> (false && false) || (true && true)
true
最佳方法:
尽量使用括号,而不是依靠操作符优先级来设定代码的执行顺序,这样我们的代码才能有更好的可读性。
2.3.4.3 惰性求值
如果在一个连续的逻辑操作中,操作结果在最后一个操作完成之前就已经明确了的话,那么该操作往往就不必再继续执行了,因为这已经不会对最终结果产生任何影响。例如,在下面这种情况中:
>>> true || false || true || false || true
true
在这里,所有的逻辑或运算符优先级都是相同的,只要其中任何一个操作数为true,该表达式的结果就为true。因而当第一个操作数被求值之后,无论后面的值是什么,结果都已经被确定了。于是我们可以允许JavaScript引擎偷个懒(好吧,这也是为了提高效率),在不影响最终结果的情况下省略一些不必要的求值操作。为此,我们可以在控制台中做个实验:
>>> var b = 5;
>>> true || (b = 6)
true
>>> b
5
>>> true && (b = 6)
6
>>> b
6
除此之外,上面的例子还向我们显示了另一个有趣的事情——如果JavaScript引擎在一个逻辑表达式中遇到一个非布尔类型的操作数,那么该操作数的值就会成为该表达式所返回的结果。例如:
>>> true || "something"
true
>>> true && "something"
"something"
通常情况下,这种行为是应该尽量避免的,因为它会使我们的代码变得难以理解。但在某些时候这样做也是有用的。例如,当我们不能确定某个变量是否已经被定义时,就可以像下面这样,即如果变量mynumber已经被定义了,就保留其原有值,否则就将它初始化为10。
var mynumber = mynumber || 10;
这种做法简单而优雅,但是请注意,这也不是绝对安全的。如果这里的mynumber之前被初始化为0(或者是那6个falsy值中的任何一个),这段代码就不太可能如我们所愿了。
2.3.4.4 比较运算符
在JavaScript中,还有另外一组以布尔值为返回值类型的操作符,即比较操作符。下面让我们通过表2-4来了解一下它们以及相关的示例。
还有一件有趣的事情要提醒读者注意:NaN不等于任何东西,包括它自己。
>>> NaN == NaN
false
2.3.5 undefined与null
通常情况下,当我们试图访问某个不存在的或者未经赋值的变量时,就会得到一个undefined值。JavaScript会自动将声明时没有进行初始化的变量设为undefined。
当我们试图使用一个不存在的变量时,就会得到这样的错误信息:
>>> foo
foo is not defined
这时候,如果我们在该变量上调用typeof操作符,就会得到字符串“undefined”:
>>> typeof foo
"undefined"
如果我们声明一个变量时没有对其进行赋值,调用该变量时并不会出错,但typeof操作符依然会返回“undefined”。
>>> var somevar;
>>> somevar
>>> typeof somevar
"undefined"
而null值就完全是另一回事了。它不能通过JavaScript来自动赋值,只能通过我们的代码来完成。
>>> var somevar = null
null
>>> somevar
null
>>> typeof somevar
"object"
尽管undefined和null之间的差别微乎其微,但有时候也很重要。例如,当我们对其分别执行某种算术运算时,结果就会截然不同:
>>> var i = 1 + undefined; i;
NaN
>>> var i = 1 + null; i;
1
这是因为null和undefined在被转换为其他基本类型时,方法存在一定的区别,下面我们给出一些可能的转换类型。
转换成数字:
>>> 1*undefined
NaN
>>> 1*null
0
转换成布尔值:
>>> !!undefined
false
>>> !!null
false
转换成字符串:
>>> "" + null
"null"
>>> "" + undefined
"undefined"