JavaScript类型转换

193 阅读8分钟

类型转换

JavaScript(以下用JS替代)的类型转换非常灵活,当JS期望使用一个布尔值的时候,你可以提供任意类型值,JS根据需要自行转换为真值跟假值。这在其他类型中同样适用:如果JS期望使用一个字符串,它就把给定的值转换成字符串。如果JS期望使用一个数字,它把给定的值转换成数字(无意义将转换成NaN),如下:

10 + "object"          // => "10 object" 数字10转换成"10"
"7" * "4"              // => 28 两个字符串均转换成数字
var n = 1 - "x"        // => NaN 字符串"x"无法转换成数字
n + " object"          // => "NaN object" NaN转换成字符串"NaN"

下表简要说明了JS中如何进行类型转换,表格来源于《JavaScript权威指南》

转换为字符串 数字 布尔值 对象
undefined "undefined" NaN false error
null "null" 0 false error
true "true" 1 new Boolean(true)
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(-0)
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](一个数字元素) "9" 9 true
['a'](其他数组) "a" NaN true
function(){}(任意函数) 参考下面 NaN true

隐式转换和相等性

"=="和"==="运算符用于比较两个值是否相等,"==="也称为严格相等运算符,它用来检测两个操作数是否严格相等。"=="运算符称为相等运算符,这里"相等"的定义非常宽松,可以允许进行”隐式转换“

严格相等运算符"==="首先计算其操作数的值,然后比较这两个值,比较过程没有任何类型转换:

  • 如果两个值类型不同,则它们不相等
  • 如果两个值都是null或者都是undefined,则它们相等
  • NaN !== NaN
  • 如果两个值为数字且数值相等,则它们相等。且0 === -0
  • 如果连个值为字符串且所含对应为上的16位数完全相等,则它们相等
  • 如果两个饮用值指向同一个对象、数组、或函数,则它们是相等的。

由于JS可以做灵活的类型转换,因此其“==”相等运算符也随相等的含义灵活多变做相应的隐式转换

  • 如果两个操作数的类型相同,则和上文所述的严格相等的比较规则一样。如果严格相等,那么比较结果为相等。如果它们不严格相等,则比较结果为不相等。
  • 如果两个操作数类型不同,"=="相等操作符也可能认为它们相等。检测相等将会遵守如下规则和类型转换
    • 如果一个值是null,另一个是undefined,则它们相等
    • 如果一个值是数字,另一个是字符串,则将字符串转换成数字,然后使用转换后的值进行比较
    • 如果其中一个值是true,则将其值转换为1再进行比较。如果其中一个值是false,则将其转换成0再进行比较
    • 如果一个值的对象,另一个值是数字或字符串,则先将对象转换为原始值(参考下面对象转换),然后再比较
    • 其他不同类型之间的比较均不相等

如下比较结果除特别说明,其他均为true

null == undefined  // 这两值被认为相同,具体是如何转换不清楚
"0" == 0           // 在比较前字符串转换成数字0
0 == false         // 在比较前布尔值转换成数字0
"0" == false       // 在比较前布尔值转换成数字0,字符串也转换成数字0
-0 == false        // 在比较前布尔值转换成数字0
"-0" == false      // 在比较前布尔值转换成数字0,字符串也转换成数字-0
[] == false        // 在比较前布尔值转换成数字0,空数组也转换成数字0
"" == false        // 在比较前布尔值转换成数字0,空字符串也转换成数字0
-1 == true         // 结果为false,因为比较前布尔值转换成数字1,-1 != 1
-1 == false        // 结果为false,因为比较前布尔值转换成数字0,-1 != 0

JS中还有其他运算符会做隐式转换,比如如果"+"运算符的一个操作数式字符串,它会将另外一个操作数转换为字符串。一元"+"运算符将其操作数转换成数字。同样,一元"!"运算符将其操作数转换为布尔值并取反。如下:

x + ""   // 等价于String(x)
+x       // 等价于Number(x), 也可以写成x-0
!!x      // 等价于Boolean(x)

显示类型转换

做显示类型转换最简单的方法就是使用Boolean(),Number(),String()或Object()函数,当不通过new 运算符调用这些函数时,它们会作为类型转换函数并按照上表所描述规则做类型转换,如下:

Numebr('3')     // => 3
String(false)   // => "false" 或使用false.toString()
Boolean([])     // => true
Object(3)       // => new Number(3)

需要注意,出了null和undefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法的返回结果一致。同样需要注意如果试图把null或undefined转换为对象,则会像表3-2所描述的那样抛出一个类型错误(TypeError)。Object()函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象。注意不同于Object.create(null)

对象转换为原始值

对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为true。对于包装对象也是如此:new Boolean(false)是一个对象而不是一个原始值,它将转换为true

对象到字符串和对象到数字的转换是通过调用待转换对象的一个方法来完成的,麻烦的是JS对象有两个不同的方法来执行转换,并且这里提到的转换只使用于本地对象(native object),宿主对象(例如web浏览器定义的对象)根据各自的算法可以转换成字符串或数字。

所有的对象继承了两个转换方法。第一个是toString(),它的作用是返回一个反映这个对象的字符串。默认的toString()方法是不会返回一个有趣的值的,如下:

({x: 1, y: 2}).toString()   // => "[object Object]"

很多类定义了更多特定版本的toString()方法,如下:

[1,2,3].toString()                  // => 1,2,3
(function(x) {f(x);}).toString()    // => 'function(x) {f(x);}'
/\d+/g.toString()                   // => '/\\d+/g'
new Date().toString()               // => 'Mon Jul 22 2019 15:51:39 GMT+0800 (中国标准时间)'

另外一个转换对象的函数是valueOf()。这个方法的任务并未详细定义:如果存在任意原始值,它默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单返回自身,而不是返回一个原始值。数组,函数和正则表达式简单地继承了这个磨人的方法,调用这些类型的实例的valueOf()方法只是简单返回本身对象。日期类定义的valueOf()方法会返回它的一个内部表示,如下

[1,2,3].valueOf()                  // => [1,2,3]
(function(x) {f(x);}).valueOf()    // => ƒ (x) {f(x);}
/\d+/g.valueOf()                   // => /\d+/g
new Date().valueOf()               // => 1563782897118

JS中对象到字符串的转换经过了如下这些步骤:

  • 如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JS将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果
  • 如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JS会调用valueOf()方法。如果存在这个方法,则JS调用它。如果返回的是原始值,JS将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
  • 如果上述方法都没有,则会抛出TypeError异常

在对象到数字的转换过程中,JS做了同样的事情,只是它首先会尝试使用valueOf()方法:

  • 如果对象具有valueOf()方法,后者会返回一个原始值,JS将这个值转换为数字(如果有需要),并返回这个数字。
  • 否则,如果具有toString()方法,后者返回一个原始值,则JS将其转换并返回
  • 如果上述方法都没有,则会抛出TypeError异常
10 + {}       // => '10[object Object]'
10 - {}       // => NaN 字符串[object Object]转换为数字为NaN
10 + []       // => '10'
10 - []       // => 10 空字符串转换为0

对象转换为数字的细节解析了为什么空数组会被转换为数字0以及为什么具有单个元素的数组会同样转换成一个数字。因为数组继承了默认的valueOf()方法,这个方法返回一个对象而不是一个原始值,因此,数组到数字的转换则调用了toString()的方法。空数组换成了空字符串,空字符串转换成了数字0。含有一个元素的数组转换为字符串的结果和这个元素转换字符串的结果一样。如果数组只包含一个数字,这个数字转换为字符串,再转换回数字。