# 隐式类型转换
# 前言
在javascript中,除了显示的转换数据类型如调用 Number()
Boolean()
String()
等方法外,有时候一些写法能够直接起到隐式类型转换,例如我们常写的1 + '2' === '12'
这其中 +
的运算就发生了一个隐式类型转换。除此之外,再思考一下下面的答案,后面会讲讲隐式转换的过程自然而然这些问题就能解决了。
[] + [];
{} + {};
[] + {};
{} + [];
1 == true;
0 == '';
null == undefined;
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world!'); // 输出hello world
}
# 隐式类型转换
# ToPrimitive(input, PreferredType?: 'Number' | 'String')
PreferredType
只是一个转换标志,实际最后的转换结果并不一定就是该类型,但是转换的结果一定要是个原始值。(否则会报错), 在javascript
中,默认设置为 Number
,除了 input
为 Date
类型,则设置为 String
。
PreferredType
为Number
时:
- 输入值已经是原始值,返回它。
- 调用输入值的
valueOf
方法,如果结果是原始值,返回它。 - 调用输入值的
toString
方法,如果结果是原始值,返回它。 - 最后的结果还不是原始值,抛出
TypeError
异常。
PreferredType为String时:
- 输入值已经是原始值,返回它。
- 调用输入值的
toString
方法,如果结果是原始值,返回它。 - 调用输入值的
valueOf
方法,如果结果是原始值,返回它。 - 最后的结果还不是原始值,抛出
TypeError
异常。
# 常见对象的valueO, toString方法结果
valueOf
和toString
方法一定都有实现,有些类型自己的原型有实现,最后顶层原型Object.prototype
都有这两个方法的实现。
先看 valueOf
方法:
Number
,String
,Boolean
这三种基本类型的构造函数,通过valueOf
转换后会变成对应的原始值。Date
类型调用valueOf
方法返回时间戳。
var num = new Number('123');
num.valueOf(); // 123
var str = new String('12df');
str.valueOf(); // '12df'
var bool = new Boolean('fd');
bool.valueOf(); // true
var date = new Date();
date.valueOf(); // 1622535486108
- 其它类型,调用
valueOf
都返回this
,即自己本身。
var arr = [1, 2, 3];
arr.valueOf() === arr; // true
var obj = {a: 1};
obj.valueOf() === obj; // true
var sym = Symbol();
sym.valueOf() === sym; // true
var bigint = 1n;
bigint.valueOf() === bigint; // true
再看看 toString
方法,下面是一些例子,都是一股脑的转成字符串。
var num = 1;
var num1 = new Number('123as');
var bool = true;
var str = 'bowlofnoodles';
var sym = Symbol();
var bigint = 1n;
var arr = [1, 2, 3];
var obj = {};
var date = new Date();
var func = function () {};
var reg = /reg/g;
// 注 都是返回字符串形式
console.log(
num.toString(), // 1
num1.toString(), // NaN
bool.toString(), // true
str.toString(), // bowlofnoodles
sym.toString(), // 'Symbol()'
bigint.toString(), // 1
arr.toString(), // [1,2,3]
obj.toString(), // [object Object]
date.toString(), // Tue Jun 01 2021 16:26:10 GMT+0800 (中国标准时间)
func.toString(), // function func() {}
reg.toString() // /reg/g
);
# ToNumber
就是显示类型转换 Number()
的调用结果。
undefined
->NaN
null
->0
- 布尔值 ->
true
为1
,false
为0
- 字符串 -> 纯数字的字符串解析为对应数字,否则为NaN。例如
'123' -> 123
,'qwe123' -> NaN
- 数字 -> 该数字
- 对象 -> 先进行
ToPrimive(input, Number)
转化到原始值,在进行ToNumber
转化为数字 - 其它不包含在上列的,一般都转化为
NaN
# ToString
就是显示类型转换String()
的调用结果。
undefined
->'undefined'
null
->'null'
- 布尔值 ->
true
为'true'
,false
为'false'
- 字符串 -> 该字符串
- 数字 ->
1
->'1'
- 对象 -> 先进行
ToPrimive(input, String)
转化到原始值,在进行ToString
转化为字符串 - 其它不包含在上列的,一般都转化为对应的字符串,例如
NaN
->'NaN'
# + 和 ==
# + 运算符
- 运算符的左右两边存在不为原始类型的值,则都调用
ToPrimive()
转换为原型值 - 运算符左右两边都为原始值之后,如果存在一个是字符串,则调用
ToString()
,双方转换为字符串,然后作字符串拼接。 - 运算符左右两边都为原始值之后,如果两边都不是字符串,则双方调用
ToNumber()
转化为数字后进行正常的加法运算。
现在我们来回看一下上面问题:
[] + []
:都不是原始值,转换为原始值,默认ToPrimitive(input, Number)
,则为'' + '' = ''
;{} + {}
: 都不是原始值,转换为原始值,默认ToPrimitive(input, Number)
,则为'[object Object]' + '[object Object]' = '\[object Object\]\[object Object\]'
;[] + {}
: 都不是原始值,转换为原始值,...,则为 '' + '[object Object]' = '[object Object]';{} + []
: 注意这里跟[] + {}
本质应该一样,但是{}
放前面被识别成了代码段,所以实际相同于+[]
,相当于Number(\[\])
的操作,得到0
。如果我们希望得到正确的结果,可以这样写({}) + [] => '[object Object]'
# == 运算符
比较 x == y
,主要区分x
、y
类型相同时,和类型不相同时。
类型相同时,没有类型转换,主要注意NaN
不与任何值相等,包括它自己,即NaN !== NaN
。
类型不相同时,
x
,y
为null
、undefined
两者中一个,返回true
。x
,y
为Number
和String
类型时,则转换为Number
类型比较。有
Boolean
类型时,Boolean
转化为Number
类型比较。一个
Object
类型,一个String
或Number
类型,将Object
类型进行原始转换后,按上面流程进行原始值比较。
详细流程和规则如下:
1、若 Type(x) 与 Type(y) 相同, 则
1* 若 Type(x) 为 Undefined, 返回 true。
2* 若 Type(x) 为 Null, 返回 true。
3* 若 Type(x) 为 Number, 则
(1)、若 x 为 NaN, 返回 false。
(2)、若 y 为 NaN, 返回 false。
(3)、若 x 与 y 为相等数值, 返回 true。
(4)、若 x 为 +0 且 y 为 −0, 返回 true。
(5)、若 x 为 −0 且 y 为 +0, 返回 true。
(6)、返回 false。
4* 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。 否则, 返回 false。
5* 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。 否则, 返回 false。
6* 当 x 和 y 为引用同一对象时返回 true。否则,返回 false。
2、若 x 为 null 且 y 为 undefined, 返回 true。
3、若 x 为 undefined 且 y 为 null, 返回 true。
4、若 Type(x) 为 Number 且 Type(y) 为 String,返回比较 x == ToNumber(y) 的结果。
5、若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。
6、若 Type(x) 为 Boolean, 返回比较 ToNumber(x) == y 的结果。
7、若 Type(y) 为 Boolean, 返回比较 x == ToNumber(y) 的结果。
8、若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。
9、若 Type(x) 为 Object 且 Type(y) 为 String 或 Number, 返回比较 ToPrimitive(x) == y 的结果。
10、返回 false。
最后,我们看一下开头的那道题:
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world!'); // 输出hello world
}
当执行
a == 1 && a == 2 && a == 3
时,会从左到右一步一步解析,首先a == 1
,会进行上面第9步转换。ToPrimitive(a, Number) == 1
。ToPrimitive(a, Number)
,按照上面原始类型转换规则,会先调用valueOf
方法,a
的valueOf
方法继承自Object.prototype
。返回a本身,而非原始类型,故会调用toString
方法。因为
toString
被重写,所以会调用重写的toString
方法,故返回1
,注意这里是i++
,而不是++i
,它会先返回i
,再将i+1
。故ToPrimitive(a, Number) = 1
。也就是1 == 1
, 此时i = 1 + 1 = 2
。执行完
a == 1
返回true
,会执行a == 2
,同理,会调用ToPrimitive(a, Number)
,同上先调用valueOf
方法,再调用toString
方法,由于第一步,i = 2
此时,ToPrimitive(a, Number) = 2
, 也就是2 == 2
, 此时i = 2 + 1
。同上可以推导
a == 3
也返回true
。故最终结果a == 1 && a == 2 && a == 3
返回true