3.8 类型转换
JavaScript中的取值类型非常灵活,我们已经从布尔值看到了这一点:当 JavaScript 期望使用一个布尔值的时候,你可以提供任意类型值,JavaScript 将根据需要自行转换类型。一些值(真值)转换为true,其他值(假值)转换为 false。这在其他类型中同样适用:如果 JavaScript 期望使用一个字符串,它把给定的值将转换为字符串。如果 JavaScript 期望使用一个数字,它把给定的值将转换为数字(如果转换结果无意义的话将返回 NaN),一些例子如下:
10 + " objects" // => "10 objects". 数字10转换成字符串
"7" * "4" // => 28:两个字符串均转换为数字
var n = 1 - "x"; // => NaN: 字符串“x”无法转换为数字
n + " objects" // => "NaN objects": NaN转换为字符串"NaN”
10 + " objects" // => "10 objects". 数字10转换成字符串
"7" * "4" // => 28:两个字符串均转换为数字
var n = 1 - "x"; // => NaN: 字符串“x”无法转换为数字
n + " objects" // => "NaN objects": NaN转换为字符串"NaN”
console.log("输出以下类型转换的结果:");
console.log(10 + " objects");
console.log("7" * "4");
console.log(n + " objects");
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出以下类型转换的结果:
10 objects
28
NaN objects
[Done] exited with code=0 in 0.499 seconds
下表简要说明了在JavaScript中如何进行类型转换。表中的粗体部分突出显示了那些让你倍感意外的类型转换。空单元格表示不必要也没有执行转换。
JavaScript类型转换
值 | 转换为字符串 | 数字 | 布尔值 | 对象 |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throws TypeError |
null | "null" | 0 | throws TypeError | |
true | "true" | 1 | ||
false | "false" | 0 | new Boolean(false) | |
" "(空字符串) | 0 | false | new String(" ") | |
"1.2"(非空,数字) | 1.2 | true | new String("1.2") | |
"one"(非空,非数字) | NaN | true | new String("one") | |
0 | "0" | false | new Number(0) | |
-0 | "0" | false | new Number(-o) | |
NaN | "NaN" | false | new Number(NaN) | |
Infinity | "Infinity" | true | new Number(Infinity) | |
-Infinity | "-Infinity" | true | new Number(-Infinity) | |
1 (无穷大,非零) | "1" | true | new Number(1) | |
{ } (任意对象) | true | |||
[ ] (任意数组) | 0 | true | ||
[ 9 ] (1个数字元素) | " 9 " | 9 | true | |
[ ' a ' ] (其他数组) | 使用join( )方法 | NaN | true | |
function( ){ }(任意函数) | NaN | true |
上表中提到的原始值到原始值的转换相对简单。所有原始值转换为字符串的情形也已经明确定义。转换为数字的情形比较微妙。那些以数字表示的字符串可以直接转换为数字,也允许在开始和结尾处带有空格。但在开始和结尾处的任意非空格字符都不会被当成数字直接量的一部分,进而造成字符串转换为数字的结果为NaN。有一些数字转换看起来让人奇怪:true转换为1,false、空字符串" "转换为0。
原始值到对象的转换也非常简单,原始值通过调用
String( )
、Number( )
或Boolean( )
构造函数,转换为它们各自的包装对象。
null和undefined属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError)异常,而不会执行正常的转换。
对象到原始值的转换多少有些复杂。
3.8.1 转换和相等性
由于JavaScript可以做灵活的类型转换,因此其“==”相等运算符也随相等的含义灵活多变。例如,如下这些比较结果均是true:
null == undefined //这两值被认为相等
"0" == 0 //在比较之前字符串转换成数宇
0 == false //在比较之前布尔值转换成数字
"0" == false //在比较之前宇符串和布尔值都转换成数字
console.log("判断null和undefined是否相等:");
console.log( null == undefined);
console.log("");
console.log("判断字符串0和数值0是否相等:");
console.log( "0" == 0 );
console.log("");
console.log("判断数值0和false是否相等:");
console.log( 0 == false );
console.log("");
console.log("判断字符串0和false是否相等:");
console.log( "0" == false );
[Running] node "e:\HMV\JavaScript\JavaScript.js"
判断null和undefined是否相等:
true
判断字符串0和数值0是否相等:
true
判断数值0和false是否相等:
true
判断字符串0和false是否相等:
true
[Done] exited with code=0 in 0.437 seconds
需要特别注意的是,一个值转换为另一个值并不意味着两个值相等。比如,如果在期望使用布尔值的地方使用了undefined,它将会转换为false,但这并不表明undefined == false。JavaScript运算符和语句期望使用多样化的数据类型,并可以相互转换。if语句将undefined转换为false,但"==”运算符从不试图将其操作数转换为布尔值。
3.8.2 显式类型转换
尽管 JavaScript 可以自动做许多类型转换,但有时仍需要做显式转换,或者为了使代码变得清晰易读而做显式转换。
做显式类型转换最简单的方法就是使用
Boolean( )
、Number( )
、String( )
或Object( )
函数。当不通过 new 运算符调用这些函数时,它们会作为类型转换函数并按照表( JavaScript 类型转换)所描述的规则做类型转换:
Number("3") // => 3
String(false) // => "false"或使用 false.toString()
Boolean([]) // => true
Object(3) // => new Number(3)
console.log(Number("3"));
console.log(String(false));
console.log(Boolean([]));
console.log(Object(3));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
3
false
true
[Number: 3]
[Done] exited with code=0 in 0.377 seconds
需要注意的是,除了 null 或 undefined 之外的任何值都具有
toString( )
方法,这个方法的执行结果通常和String( )
方法的返回结果一致。同样需要注意的是,如果试图把 null 或 undefined 转换为对象,则会像表( JavaScript 类型转换)所描述的那样抛出一个类型错误( TypeError )。Object( )
函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象。
JavaScript 中的某些运算符会做隐式的类型转换,有时用于类型转换。如果“+”运算符的一个操作数是字符串,它将会把另外一个操作数转换为字符串。一元“+”运算符将 其操作数转换为数字。同样,一元“!”运算符将其操作数转换为布尔值并取反。在代码中会经常见到这种类型转换的惯用法:
x + "" // 等价于String(x)
+x //等价于Number(x).也可以写成x-0
!!x //等价于Boolean(x).注意是双叹号
var x = "黄子涵";
var hzh1 = x + "";
var hzh2 = String(x);
console.log("判断hzh1和hzh2是否严格相等:");
console.log(hzh1 === hzh2);
console.log("");
var y = 123;
var hzh3 = +y;
var hzh4 = Number(y);
console.log("判断hzh3和hzh4是否严格相等:");
console.log(hzh3 === hzh4);
console.log("");
var hzh5 = !!x;
var hzh6 = Boolean(x);
console.log("判断hzh5和hzh6是否严格相等:");
console.log(hzh5 === hzh6);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
判断hzh1和hzh2是否严格相等:
true
判断hzh3和hzh4是否严格相等:
true
判断hzh5和hzh6是否严格相等:
true
[Done] exited with code=0 in 0.178 seconds
在计算机程序中数字的解析和格式化是非常普通的工作,JavaScript中提供了专门的函数和方法用来做更加精确的数字到字符串(number-to-string)和字符串到数字 (string-to-number)的转换。
Number类定义的toString( )方法可以接收表示转换基数(radix)的可选参数,如果不指定此参数,转换规则将是基于十进制。同样,亦可以将数字转换为其他进制数(范围在2~36之间),例如:
var n = 17;
binary_string = n.toString(2); // 转换为 "10001"
octal_string = "O" + n.toString(8); // 转换为 "021"
hex_string = "ox" + n.toString(16); // 转换为 "0x11"
注
这里的转换基数是指二进制、八进制、十六进制等。
当处理财务或科学数据的时候,在做数字到字符串的转换过程中,你期望自己控制输出中小数点位置和有效数字位数,或者决定是否需要指数记数法。Number类为这种数字到字符串的类型转换场景定义了三个方法。
toFixed( )
根据小数点后的指定位数将数字转换为字符串,它从不使用指数记数法。toExponential( )
使用指数记数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数则由参数指定(也就是说有效数字位数比指定的位数要多一位),toPrecision( )
根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字整数部分的位数,则转换成指数形式。我们注意到,所有三个方法都会适当地进行四舍五入或填充0。看一下下面几个例子:
var n = 123456.789;
n.toFixed(0); // "123457”
n.toFixed(2); // "123456.79”
n.toFixed(5); // "123456.78900”
n.toExponential(l); // "1.2e+5"
n.toExponential(3); // "1.235e+5"
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); // "123456.8"
n.toPrecision(10); // "123456.7890"
var n = 123456.789;
console.log(n.toFixed(0));
console.log(n.toFixed(2));
console.log(n.toFixed(5));
console.log(n.toExponential(1));
console.log(n.toExponential(3));
console.log(n.toPrecision(4));
console.log(n.toPrecision(7));
console.log(n.toPrecision(10));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
123457
123456.79
123456.78900
1.2e+5
1.235e+5
1.235e+5
123456.8
123456.7890
[Done] exited with code=0 in 0.318 seconds
注
如果指定的参数为3,有效数字位数为4位。
如果通过
Number( )
转换函数传入一个字符串,它会试图将其转换为一个整数或浮点数直接量,这个方法只能基于十进制数进行转换,并且不能出现非法的尾随字符。parselnt( )
函数和parseFloat( )
函数(它们是全局函数,不从属于任何类的方法)更加灵活。parselnt( )
只解析整数,而parseFloat( )
则可以解析整数和浮点数。如果字解释为十六进制数,parselnt( )
和parseFloat( )
都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回NaN:
parseInt("3 blind mice") // => 3
parseFloat(" 3.14 meters") // => 3.14
parseInt("-12.34") // => -12
parseInt("OxFF") // => 255
parseInt("Oxff") // => 255
parseInt("-OXFF") // => -255
parseFloat(".1") // => 0.1
parseInt("0.1") // => 0
parseInt(".1") // => NaN: 整数不能以"."开始
parseFloat("$72.47"); // => NaN: 数字不能以"$"开始
console.log(parseInt("3 blind mice"));
console.log(parseFloat(" 3.14 meters"));
console.log(parseInt("-12.34"));
console.log(parseInt("0xFF"));
console.log(parseInt("0xff"));
console.log(parseInt("-0XFF"));
console.log(parseFloat(".1"));
console.log(parseInt("0.1") );
console.log(parseInt(".1"));
console.log(parseFloat("$72.47"));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
3
3.14
-12
255
255
-255
0.1
0
NaN
NaN
[Done] exited with code=0 in 0.193 seconds
parseInt( )
可以接收第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36,例如:
parseInt("11", 2); // => 3 (1*2 + 1)
parseInt("ff", 16); // => 255 (15*16 + 15)
parseInt("zz", 36); // => 1295 (35*36 + 35)
parseInt("077", 8); // => 63 (7*8 + 7)
parseInt("077", 10); // => 77 (7*10 + 7)
console.log(parseInt("11", 2));
console.log(parseInt("ff", 16));
console.log(parseInt("zz", 36));
console.log(parseInt("077", 8));
console.log(parseInt("077", 10));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
3
255
1295
63
77
[Done] exited with code=0 in 0.34 seconds
3.8.3 对象转换为原始值
对象到布尔值的转换非常简单:所有的对象(包括数组和函数)都转换为true。对于包装对象亦是如此:new Boolean (false)是一个对象而不是原始值,它将转换为true。
对象到字符串(object-to-string)和对象到数字(object-to-number)的转换是通过调用待转换对象的一个方法来完成的。一个麻烦的事实是,JavaScript 对象有两个不同的方法来执行转换,并且接下来要讨论的一些特殊场景更加复杂。值得注意的是,这里提到的字符串和数字的转换规则只适用于本地对象(native object)。宿主对象(例如,由Web浏览器定义的对象)根据各自的算法可以转换成字符串和数字。
所有的对象继承了两个转换方法。第一个是toString( ),它的作用是返回一个反映这个对象的字符串。默认的toString( )方法并不会返回一个有趣的值:
({x:1, y:2}).toString() // => "[object Object]"
console.log(({x:1, y:2}).toString());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
[object Object]
[Done] exited with code=0 in 0.411 seconds
注
在 ECMAScript 3中,
parseInt()
可以对前缀为“0”(不能是“0x”或“0X”)的数字做八进制转换。由于其细节没有详细说明,你并无法直接使用parseInt()
来对前缀为0的值进行解析,除非你明确指出所使用的转换基敎!在ECMAScript 5中,parseInt()
只有在明确传入第二个参数8时才会解析八进制数。
很多类定义了更多特定版本的
toString( )
方法。例如,数组类(Array class)的toString()
方法将每个数组元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串。函数类(Function class)的toString( )
方法返回这个函数的实现定义的表示方式。实际上,这里的实现方式是通常是将用户定义的函数转换为 JavaScript 源代码字符串。日期类(Date class)定义的toString( )
方法返回了一个可读的(可被 JavaScript 解析的)日期和时间字符串。RegExp类(RegExp class)定义的toString( )
方法将RegExp对象转换为表示正则表达式直接量的字符串:
[1,2,3].toString() // => "1,2,3"
(function(x) { f(x); }).toString() // => "function(x) {\n f(x);\n}"
/\d+/g.toString() // => "/\\d+/g"
new Date(2010,0,1).toString() // => "Fri Jan 01 2010 00:00:00 GMT-0800 (PST)"
console.log([1,2,3].toString());
console.log((function(x) { f(x); }).toString());
console.log(/\d+/g.toString());
console.log(new Date(2010,0,1).toString());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
1,2,3
function(x) { f(x); }
/\d+/g
Fri Jan 01 2010 00:00:00 GMT+0800 (GMT+08:00)
[Done] exited with code=0 in 0.197 seconds
另一个转换对象的函数是
valueOf()
。这个方法的任务并未详细定义:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()
方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()
方法只是简单返回对象本身。日期类定义的valueOf()
方法会返回它的一个内部表示:1970年1月1日以来的毫秒数。
var d = new Date(2010,0,1); // 2010年1月1日(太平洋时间)
d.valueOf() // => 1262332800000
var d = new Date(2022, 5, 24); // 2010年1月1日(太平洋时间)
console.log(d.valueOf()); // => 1262332800000
[Running] node "e:\HMV\JavaScript\JavaScript.js"
1656000000000
[Done] exited with code=0 in 0.177 seconds
通过使用我们刚刚讲解过的
toString()
和valueOf()
方法,就可以做到对象到字符串和对象到数字的转换了。但需要注意的是,在某些特殊的场景中,JavaScript 执行了完全不同的对象到原始值的转换。
JavaScript 中对象到字符串的转换经过了如下这些步骤:
- 如果对象具有
toString()
方法,则调用这个方法。如果它返回一个原始值,JavaScript 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。需要注意的是,原始值到字符串的转换在表(JavaScript 类型转换)中已经有了详尽的说明。- 如果对象没有
toString()
方法,或者这个方法并不返回一个原始值,那么 JavaScript 会调用valueOf()
方法。如果存在这个方法,则 JavaScript 调用它。如果返回值是原始值,JavaScript 将这个值转换为字符串(如果本身不是字符串的话), 并返回这个字符串结果。- 否则,JavaScript 无法从
toString()
或valueOf( )
获得一个原始值,因此这时它将抛出一个类型错误异常。
在对象到数字的转换过程中,JavaScript 做了同样的事情,只是它会首先尝试使用
valueOf()
方法:
- 如果对象具有
valueOf()
方法,后者返回一个原始值,则 JavaScript 将这个原始值转换为数字(如果需要的话)并返回这个数字。- 否则,如果对象具有
toString()
方法,后者返回一个原始值,则 JavaScript 将其转换并返回。- 否则,JavaScript 抛出一个类型错误异常。
注
对象的
toString()
方法返回一个字符串直接量,JavaScript 将这个字符串转换为数字类型,并返回这个数字。
对象转换为数字的细节解释了为什么空数组会被转换为数字。以及为什么具有单个元素的数组同样会转换成一个数字。数组继承了默认的
valueOf()
方法,这个方法返回一个对象而不是一个原始值,因此,数组到数字的转换则调用toString()
方法。空数组转换成为空字符串,空字符串转换成为数字0。含有一个元素的数组转换为字符串的结果和这个元素转换字符串的结果一样。如果数组只包含一个数字元素,这个数字转换为字符串,再转换回数字。
JavaScript 中的"+”运算符可以进行数学加法和字符串连接操作。如果它的其中一个操作数是对象,则JavaScript 将使用特殊的方法将对象转换为原始值,而不是使用其他算术运算符的方法执行对象到数字的转换,"==”相等运算符与此类似。如果将对象和一个原始值比较,则转换将会遵照对象到原始值的转换方式进行。
“+”和“==”应用的对象到原始值的转换包含日期对象的一种特殊情形。日期类是 JavaScript 语言核心中唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。对于所有非日期的对象来说,对象到原始值的转换基本上是对象到数字的转换(首先调用
valueOf()
),日期对象则使用对象到字符串的转换模式,然而,这里的转换和上文讲述的并不完全一致:通过valueOf()
或toString()
。返回的原始值将被直接使用,而不会被强制转换为数字或字符串。
和“==” 一样,“<”运算符以及其他关系运算符也会做对象到原始值的转换,但要除去日期对象的特殊情形:任何对象都会首先尝试调用
valueOf()
,然后调用toString()
。不管得到的原始值是否直接使用,它都不会进一步被转换为数字或字符串。
“
+
”、“==
”、"!=
”和关系运算符是唯一执行这种特殊的字符串到原始值的转换方式的运算符。其他运算符到特定类型的转换都很明确,而且对日期对象来讲也没有特殊情况。例如“-”(减号)运算符把它的两个操作数都转换为数字。下面的代码展示了日期对象和“+”、“-
”、“==
”以及“>”的运行结果:
var now = new Date(); // 创建一个日期对象
typeof (now + 1) // => "string": "+"将日期转换为字符串
typeof (now - 1) // => "number": "-"使用对象到数字的转换
now == now.toString() // => true: 隐式的和显式的字符串转换
now > (now -1) // => true: ">"将日期转换为数字
var now = new Date(); // 创建一个日期对象
console.log(typeof (now + 1)); // => "string": "+"将日期转换为字符串
console.log(typeof (now - 1)); // => "number": "-"使用对象到数字的转换
console.log(now == now.toString()); // => true: 隐式的和显式的字符串转换
console.log(now > (now -1)); // => true: ">"将日期转换为数字
[Running] node "e:\HMV\JavaScript\JavaScript.js"
string
number
true
true
[Done] exited with code=0 in 0.29 seconds