将值从一种类型转换为另一种类型通常称为类型转换,这是显示的情况;隐式的情况称为强制类型转换。
JS中的强制类型转换总是返回 标量基本类型值,如字符串、数字和布尔值,不会返回对象和函数。
也可以这样区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
var a = 42
var b = a + '' // 隐式强制类型转换
var c = String(a) // 显式强制类型转换
对普通对象来说,除非自行定义,否则toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如“[object object]”。
数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用 “,” 连接起来:
var a = [1,2,3]
a.toString() // "1,2,3"
JSON字符串化
工具函数JSON.stringify()在将JSON对象序列化为字符串时也用到了toString。
JSON.stringify()在对象中遇到 undefined、function和symbol时会自动将其忽略。在数组中则会返回null(以保证单元位置不变)。
JSON.stringify(undefined) // undefined
JSON.stringify(function(){}) // undefined
JSON.stringify([1,undefined,function(){},4]) // "[1,null,null,4]"
JSON.stringify({a:2, b:function(){}}) // "{"a":2}"
对包含循环引用的对象执行 JSON.stringify() 会出错。
介绍几个不太为人所知却很有用的功能:
(1)可以向 JSON.stringify() 传递一个可选参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪 些应该被排除。
如果 replacer 是一个数组,那么它必须是一个字符串数组,其中包含序列化要处理的对象的属性名称,除此之外其他的属性则被忽略。
如果 replacer 是一个函数,它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数,键和值。如果要忽略某个键就返回 undefined,否则返回指定的值。
var a = {
b: 42,
c: "42",
d: [1,2,3]
}
JSON.stringify(a, ['b','c']) // "{"b":42,"c":"42"}"
JSON.stringify(a, function(k,v) {
if (k !== 'c') return v
})
// "{"b":42,"d":[1,2,3]}"
(2)JSON.stringify() 还有一个可选参数 space,用来指定输出的缩进格式。space 为正整数时是指定每一级缩进的字符数,它还可以是字符串,此时最前面的十个字符被用于每一级的缩进。
var a = {
b: 42,
c: "42",
d: [1,2,3]
}
JSON.stringify(a, null, 3)
"{
"b": 42,
"c": "42",
"d": [
1,
2,
3
]
}"
JSON.stringify(a, null, '-----')
"{
-----"b": 42,
-----"c": "42",
-----"d": [
----------1,
----------2,
----------3
-----]
}"
JSON.stringify()并不是强制类型转换。
toNumber 将 true 转换为 1,false 转换为 0,undefined转换为 NaN,null转换为0。
toNumber对字符串的处理基本遵循数字常量的相关规则。处理失败时返回NaN。不同之处是 toNumber 对以0开头的十六进制数并不按十六进制处理。
对象会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
假值有:
字符串和数字之间的转换时通过 String() 和 Number() 这两个内建函数来实现的。
var a = 42
var b = String(a) //"42"
var c = "3.14"
var d = Number(c) //3.14
除了 String() 和 Number() 以外,还有其他方法可以实现字符串和数字之间的显示转换。
var a = 42
var b = a.toString() //"42"
var c = "3.14"
var d = +c //3.14
a.toString() 是显式的,不过其中涉及隐式转换。因为 toString() 对42这样的基本类型值不适用,所以JS引擎会自动为42创建一个封装对象,然后对该对象调用toString()。
+运算符显式的将c转换为数字,而非数字加法运算。
一元运算符 - 和 + 一样,并且它还会反转数字的符号位。由于 -- 会被当做递减运算符来处理,所以我们要在中间加一个空格 - -"3.14",即得到 3.14。
日期显式转换为数字
一元运算符 + 的另一个常见用途是将日期对象强制类型转换为数字,返回结果为时间戳:
var a = new Date() // Thu Dec 13 2018 17:14:21 GMT+0800 (中国标准时间)
+a // 1544692461546
所以我们能用下面的方法获取当前的时间戳:
var timestamp = +new Date()
JS有一处奇特的语法,即构造函数没有参数时可以不用带()。于是可以有下面这种写法:
var timestamp = +new Date
不过最好还是使用ES5的 Date.now() 获取当前时间戳:
var timestamp = Date.now()
~ 运算符
~ 运算符,即字位操作“非”。
~x 大致等同于 -(x+1):
~42 // -43
~和indexOf() 一起可以将结果强制类型转换为布尔值:
var a = "Hello World"
~a.indexOf('lo') // -4
Boolean(~a.indexOf('lo')) // true
Boolean(~a.indexOf('ol')) // false
如果 indexOf() 返回 -1,~将其转换为假值0,其他情况一律转换为真值。
字位截取
~~ 可以用来截取数字位的小数部分,但与 Math.floor() 的效果不同。
~~ 中的第一个 ~ 执行 ToInt32 并反转字位,然后第二个 ~ 再进行一次字位反转,即将所有的字位反转回原值,最后得到的仍然是 ToInt32 的结果。
~~ 只适用于32未数字,且对负数的处理与 Math.floor() 不同。
Math.floor(-45.9) // -46
~~-45.9 // -45
解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但解析和转换两者之间还是有明显的差别。
var a = '42'
var b = '42px'
Number(a) // 42
parseInt(a) // 42
Number(b) // NaN
parseInt(b) // 42
解析允许字符串中含有非数字字符,解析从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
parseInt() 针对的是字符串值。向 parseInt() 传递数字和其他类型的参数是没用的。
非字符串参数会首先被强制类型转换为字符串,应该避免向 parseInt() 传递非字符串。
解析非字符串
下面这种是怎么回事呢?
parseInt(1/0, 19) // 18
首先1/0 得到 Infinity,再回到基数29,但在实际的js代码中不会用到基数19.它的有效数字字符范围是0-9和a-i。
parseInt(1/0, 19) 实际上是 parseInt("Infinity", 19)。第一个字符是“I”,以19为基数时的值为18.第二个字符“n”不是一个有效的数字字符,解析到此为止。 所以最后结果为 18,而非Infinity或者报错。
现在以下例子我们应该能看懂:
parseInt(0.000008) // 0 ('0'来自于'0.000008')
parseInt(0.0000008) // 8 ('8'来自于'8e-7')
parseInt(false, 16) // 250 ('fa'来自于'false')
parseInt(parseInt, 16) // 15 ('f'来自于'function')
parseInt("0x10") // 16 (十六进制数转十进制)
parseInt("103", 2) // 2 (转二进制,3不是有效位)
其他类型和布尔类型之间的相等比较
var a = "42"
var b = true
a == b // false
这里a和b怎么不相等呢,首先规范上是这样说的:
所以这里 ToNumber(y) 将 true 强制类型转换为1,变成了 "42" == 1,所以结果为 false
对象和非对象之间的相等比较
var a = 'abc'
var b = Object(a)
a == b // true
b通过 ToPrimitive 进行强制类型转换,并返回标量基本类型值“abc”,与a相等。
但是 null 、 undefined 和 NaN 比较特殊。 null 、 undefined不能被封装。NaN能够被封装为数字封装对象,但拆封后 NaN == NaN返回false。