先试试下面六道题目
let a = [0];
if ([0]) {
console.log(a == true);
} else {
console.log("wut");
}
//log false
复制代码
'5' + 3; // log "53"
'5' - 3; // log "3"
复制代码
let a = [1,2];
let b = [3,4];
a + b; // "1,23,4"
复制代码
2 == [[[2]]]
// log true
复制代码
[] == []
// log false
[] == ![]
// log true
复制代码
let a = [1, 2, 3];
let b = [1, 2, 3];
let c = [1, 2, 4];
a == b;
a === b;
a > c;
a < c;
// log false, false, false, true
复制代码
细心的你一定看出来了,上述五道题目都与 JS 的类型相关,再细一点的话,应该是与类型转换相关。
引用 You Don't Know JS 的结论,我们可以将类型转换分为:隐式转换(Implicit coercion)、显式转换(Explicit coercion)。
显式转换
我们主动的将一些变量按照我们的期望进行转变。那么有几种方法呢?
ToNumer
其他类型转换为 number
类型的几种方法
输入类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 如果参数是 true,结果为 1。如果参数是 false,此结果为 +0。 |
Number | 结果等于输入的参数(不转换)。 |
String | string ---> number |
Object | object ---> number |
-
string
--->number
这个过程类似于判断字符串是否为一个字符串数值常量,例如
let a = '1' let b = 'hello world' console.log(Number(a)) // 1 console.log(Number(b)) // NaN console.log(+a) // 1 console.log(+b) // NaN 复制代码
其中,在开源的 JS 社区中一般将
+
视为一个 明确的 强制转换形式。同时,这个转换过程中也包含着一些细节,类似于
Number("") // 0
等,具体可见 www.ecma-international.org/ecma-262/5.…
Number(a) 可以的话,那么 new Number(a) 可以么?可见 juejin.im/post/5d37ad…
-
object
--->number
这个过程可以分为两部,
object
-->ToPrimitive(object)
-->number
。ToPrimitive
运算符把其值参数转换为非对象类型,也就是调用其内部的[[DefaultValue]]
方法。[[DefaultValue]] (hint)
中hint
为期望类型(很关键):- 如果
hint
为number
:先Object.prototype.valueOf
,如果结果为原始值,则返回,否则调用Object.prototype.toString
; - 如果
hint
为string
:先Object.prototype.toString
,如果结果为原始值,则返回,否则调用Object.prototype.valueOf
。
var a = { valueOf: function() { return 42; }, toString: function() { return 4; } }; console.log(Number(a)) // 42 console.log(String(a)) // 4 复制代码
- 如果
ToString
输入类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | 如果参数是 true,那么结果为 "true"。
如果参数是 false,那么结果为 "false"。 |
String | 结果等于输入的参数(不转换)。 |
Number | 见 number --->string |
Object | 见上文。 |
-
number
--->string
与
string
--->number
类似,大体上是将字符串数值常量转为字符串,其中有些科学计数法的转化细节String(1e-1) // "0.1" 复制代码
还有一些
NaN
的细节,见参考。
ToBoolean
相对于前两个,ToBoolean 的规则较为明确
输入类型 | 结果 |
---|---|
Undefined | false |
Null | false |
Boolean | 结果等于输入的参数(不转换)。 |
Number | 如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。 |
String | 如果参数参数是空字符串(其长度为零),结果为 false,否则结果为 true。 |
Object | true |
除了几个特殊的 undefined
,null
,0
,""
,NaN
,其他的一律转为 true
。主要的转换方法,Boolean
以及 !!
。
let a = "0";
let b = [];
let c = {};
let d = "";
let e = 0;
let f = null;
let g;
Boolean( a ); // true
!!a; // true
Boolean( b ); // true
!!a; // true
Boolean( c ); // true
!!c; // true
Boolean( d ); // false
!!d; // false
Boolean( e ); // false
!!e; // false
Boolean( f ); // false
!!f; // false
Boolean( g ); // false
!!g; // false
复制代码
隐式转换(一般伴随着表达式、操作符)
当我们在使用条件表达式或者一些运算、相等操作符时,如果不注意,就会发生隐式的类型转换。
判断语句
主要有一下五种情况。
- 在一个
if (..)
语句中的条件表达式; - 在一个
for ( .. ; .. ; .. )
中间的条件表达式; - 在
while (..)
和do..while(..)
循环中的条件表达式; - 在
? :
三元表达式中的条件表达式(第一个子句); ||
(“逻辑或”)和&&
(“逻辑与”)操作符左手边的操作数。
在日常的开发中,我们通常不会将判断语语句中的 condition
的结果强制转换为 boolean
,例如
let a = 42
let b = {}
if (a) {
console.log( "yep" ); // yep
}
while (b) {
console.log( "forever running" );
}
复制代码
这时我们已经默认使用隐式转换来简化代码。而在没有默认参数的 ES5,我们也会使用,来为函数添加默认参数
let a;
let b = 42;
a || b; // 42
复制代码
或者利用 &&
来简化 if
语句
let a = 42;
let b = "abc";
a && b; // "abc"
复制代码
所以在第一道题目中
let a = [0];
if ([0]) {
console.log(a == true);
}
// [0] 会转化为 true
复制代码
+
、-
运算符
根据ES5语言规范,如果两个操作数之一已经是一个 string
,或者下列步骤产生一个 string
表达形式,+
将会进行连接。
所以题目中
'5' + 3; // log "53"
复制代码
但,如果当 +
的两个操作数之一收到一个object(包括array)时,它首先在这个值上调用 ToPrimitive
,例如
'5' + [1,2,3] // "51,2,3"
复制代码
会将 [1,2,3]
转为了 string
调用 toString
,至于原因见上文。
所以第三题目中,a
和 b
分别被转化为 "1,2"
以及 "3,4"
let a = [1,2];
let b = [3,4];
a + b; // "1,23,4"
复制代码
而一元 -
运算符将其操作数转换为 number
类型,所以
'5' - 3; // log 2
复制代码
==
以及 ===
相等操作符
最为常考,开发用的少,面试用的多的 ==
来了,这里引用一句You Don't Know JS 中的一句话
"
==
allows coercion in the equality comparison and===
disallows coercion."
==
与 ===
的本质区别在与 coercion
也就是说本质在于是否发生了强制类型转换。那么 ==
是如何转化类型的呢?
以 x == y
为例(篇幅所限省略,x、y 类型相同的规则,见参考地址)
x
为null
且y
为undefined
, 返回true
;x
为undefined
且y
为null
,返回true
;- 若 Type(x) 为
number
且 Type(y) 为string
,返回x == ToNumber(y)
的结果; - Type(x) 为
string
且 Type(y) 为number
,返回ToNumber(x) == y
的结果; - Type(x) 为
boolean
,返回ToNumber(x) == y
的结果; - Type(y) 为
boolean
,返回x == ToNumber(y)
的结果; - Type(x) 为
string
或number
,且Type(y)
为object
,返回x == ToPrimitive(y)
的结果; - Type(x) 为
object
且 Type(y) 为string
或number
, 返回比较ToPrimitive(x) == y
的结果。
看似很多,其实本质上满足两条:
- 如果 x,y 的类型为非引用类型,将其转化为
number
进行对比; - 如果 x,y 的类型为引用类,将调用
ToPrimitive
转化为基础值进行对比。
2 == '2' // true
1 == true // true
0 == false // true
复制代码
而题目中,[[[2]]]
需要获取 ToPrimitive
的结果
2 == [[[2]]]
// [[[2]]] 调用 valueOf() 得到 [Array(1)] 非基础类型
// [[[2]]] 调用 toString() 得到 2
// log true
复制代码
[] == []
// [] 与 [] 类型都为 object,所以比对是否引用同一个对象
// false
[] == ![]
// ![] 强制转化为 boolean,false,
// ![] 转化为 boolean,所以 [] 需要利用 ToPrimitive 得到 ""
// 原式变为 "" == false,false 转化为 0,而 "" 也变为 0
// log true
复制代码
同样还有 >
,<
let a = [1, 2, 3];
let b = [1, 2, 3];
let c = [1, 2, 4];
a == b; // 非同一引用
a === b; // 非同一引用
a > c; // a 转化为 "1,2,3",c 转化为 "1,2,4"
a < c;
// log false, false, false, true
复制代码