类型转换
js中存在强制类型转换和隐式类型转换, 有意识地去调用转换方法或者构造函数去转换称为强制类型转换(implicit coercion), 无意识地转换(语言机制自发完成)称为隐式类型转换(explicit coercion).1
2
3const a = 42
const b = a + "" // implicit coercion
const c = String( a ) // explicit coercion
这里稍后会讨论的”==”问题, 涉及到的也是隐式类型转换.
转换的目标只能是string, number or boolean. 不可能经过隐式类型转换, 转换出一个复杂类型的数据(Object, Array, Function …). 现在来看看各个类型的ToString,ToNumber, ToBoolean, 或者说是ToPrimitive.
ToString
原生简单对象转化规则
null: 转化为字符串”null”;
undefined: 转化为”undefined”;
true/false: 转化为”true”/“false”;
Number: 大部分情况如预测地那样, 2转化为”2”, 0转化为”0”, 100转化为”100”. 但是事情没有这么简单. 不是10进制的数字, 首先会转化为十进制, 然后再转化为字符串, 并不是数字直接加上引号就行了.1
2(0x23).toString() // "35"
0x23 == "35" // true绝对值很大的数值或者绝对值很小的数值, 首先会转化为科学计数法, 然后再进行转化.
1
2
3(0.0000001).toString() // "1e-7"
(1000000000000000000000).toString() // "1e+21"
0.0000001 == "1e-7" // true复杂对象的转化规则
Object: 如果没有指定自己的toString()方法, 就会调用Object.prototype.toString(). 这个函数会返回对象类型字符串, 在这里是”[object Object]”. 如果指定了自己的toString()函数, 会执行这个函数, 使用返回值.1
2
3
4
5
6
7
8
9const obj = {
value: 42,
}
const obj42 = {
value: 42,
toString: () => {return "ultimate"}
}
obj == "[object Object]" // true
obj42 == "ultimate" // trueArray: Array类型”重载”了Object.prototype.toString(), toString方法返回有一个以”,”隔开的数组元素拼接的字符串.
1
2
3
4
5
6
7const arr = ['a','b','c','d','e','f']
console.log(arr) // "a,b,c,d,e,f"
// 修改数组的默认toString方法 别这样做
Array.prototype.toString = function() {
return this.split('-')
}
['a','b','c','d','e','f'] == "a-b-c-d-e-f" // trueFunction: Function类型也重载了Object.prototype.toString(), 个性化的toString返回函数的字符串形式.
1
(function(){var s = 2;return s}) == "function(){var s = 2;return s}"
ToNumber
基本类型转化规则
null: 转化为0!;
undefined: 转化为NaN;
true/false: 转化为1/0;
String: 字符串会尝试使用Number()构造函数(误)去转化结果, 转化失败不会报错, 会返回特殊的数字类型值NaN. 在这种操作中可以正确辨识以0x(0X)为起始符号的16进制的数字字符串, 但是会忽略以”0”起始的部分.1
2
3
4
5
610 - null // 10
isNaN(undefined) // true
1 + true // 2
2 - false // 2
20 - "0xb" // 9
20 - "013" // 7还有一种特殊情况是, 如果是合法的科学计数法数字字符串, 能正常转化为10进制的数字
1
"1e+10" == 10000000000 // true
复杂对象的转化规则
首先复杂对象会调用内部的ToPrimitive方法, 尝试转化成基础类型值, 如果基础类型值不是number, 则再进行转化. 调用ToPrimitive可以想成首先尝试调用对象的valueOf()方法, 如果有这个方法并且返回的是基础类型值则使用返回值, 否则就尝试调用toString()方法. 如果这两个方法都不存在或者返回值都不是基础类型值, 会抛出TypeError错误.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const a = {
valueOf: () => {return "1"}
}
10 - a // 9
const b = {
toString: () => {return "2"}
}
10 - b // 8
const c = {
valueOf: () => {return "3"}, // 首先调用
toString: () => {return "4"},
}
10 - c // 7
const d = {
valueOf: () => {return {}}, // 首先调用
toString: () => {return "5"}, // 调用valueOf结果不对, 调用toString
}
10 - d // 5
const e = Object.create(null)
10 - e // TypeError
ToBoolean
先来看一定是false的几个值
- undefined
- null
- false
- +0, -0, and NaN
“”
这个列表外的值, 都是true(误, 有例外). 复杂类型的值都是true(误, 有例外)
1
2!!new Boolean( false ) // true
!!new Boolean( 0 ) // true在史前时代, 人们判断是不是IE浏览器, 往往用这样的代码:
1
if (document.all) { /* it's IE */ }
结果慢慢地, 别的浏览器也开始有这个API了. 可是旧代码已经沉淀下来成了地层中的岩石, 挖出来修改的成本太高了, 干脆在非IE浏览器中document.all是falsy算了, 所以导致了这个对象的奇葩行为.
1
2!!document.all // true 在IE11以下版本
!!document.all // false 在IE11以上版本或非IE环境
“==” VS. “===”
两者的区别是: “==”比较的时候, 允许隐式类型转换, “===”不允许隐式类型转换.
稍微提下”===”的两个奇葩行为:
- NaN === NaN // false
- -0 === +0 // true
“==”规则
1 | 42 === '42' // false |
问题来了, 42 == ‘42’到底隐式转换成了什么? 是42 == 42还是’42’==’42’? 接下来就详细介绍下转换的规则, 了解这些规则后, “==”很多诡异的行为都变得有理有据, 再也不用视为”糟粕”不敢用了.
String VS. Number
If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
如果”==”两边是字符串和数字, 那么字符串转化为数字去比较.
字符串转化为数字的规则, 上边有介绍.
Anything VS. Boolean
If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
如果”==”两边是Boolean值和其他值, 那么第一步会将Boolean值转化为数字, 转化的结果只能是0或1. 然后再用0或1去和其他值比较, 如果其他值是复杂类型的值, 再进行其他转换, 如果是字符串, 参考上一条.
1
2true == '1' // 1 == '1' -> 1 == 1 true
true == '42' // 1 == '42' -> 1 == 42 false
null VS. undefined
If x is null and y is undefined, return true.
If x is undefined and y is null, return true.
如果是null和undefined作比较, 返回true. 这俩哥们和其他的任何值作比较, 都返回false.
1
2
3
4
5
6
7
8
9
10
11const a = null
const b = undefined
a == b // true
a == null // true
b == null // true
a == false // false
b == false // false
a == "" // false
b == "" // false
a == 0 // false
b == 0 // false
Objects VS. non-Objects
If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
当复杂类型与基本类型作比较的时候, 复杂类型值首先要转换成基本类型的值, 转化规则前边有介绍.
1
2['42'] == 42 // true
Object(10) == '10' // true
两点需要注意, 构造函数的参数是null或者undefined, 会返回一个”空”对象, 所以下边的结果是有道理的.
1
2
3
4const a = Object( null ) // {}
a == null // false
const b = Object( undefined ) // {}
b == undefined // false
最后练习
1 | [] == ![] // true [] == false -> [] == 0 -> "" == 0 -> 0 == 0 |