显式隐式类型转化

显示/隐式类型转换

显示类型转换

💄Number() 把值转为数字

  • 如果参数无法被转为数字,则返回 NaN
  • ‘’、’ ‘, ‘0’、false、null、[] 、 等返回 0
  • {}、undefined 被转为 NaN
  • 数字打头,但包含其他字符的,也返回 NaN
  • 特点:
    • 相比 parseInt 更宽松,false,true能转
    • 但类似 ‘1a’ 这种,会 NaN
1
2
3
4
5
var a = '123';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-123

a = '3.14';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-3.14
1
2
3
4
5
6
7
8
9
10
11
a = 'true';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-NaN
// hint: 非数

a = 'a';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-NaN
// hint: 非数

a = '1a';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-NaN
// hint: 非数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Number("0"); // 0
Number(""); // 0
Number(" "); // 0
Number(null); // 0
Number(false); // 0
Number([]); // 0
Number("\n"); // 0
Number("\t"); // 0

Number(true); // 1
Number('true'); // NaN

Number(undefined); // NaN
Number({}); // NaN
Number("x"); // NaN

💄parseInt 把值转为一个整数

  • string 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用  [ToString](https://www.ecma-international.org/ecma-262/6.0/#sec-tostring) 抽象操作)。字符串开头的空白符将会被忽略。
  • radix 从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。
  • 当radix不填写时默认当做十进制处理,但注意10并不是默认值,当字符串以0x开头是会直接处理为16进制
    • parseInt("123", 16) === parseInt("0x123") 
  • true、false、’’、null、undefined、NaN、[]、{} 都转为 NaN
  • 如果数字和字符串混合,字符串在前,转为NaN;数字在前,取数字,直到不是数字为止
  • 特点:
    • 相比 Number,更严格,false, true都会 NaN
    • 但 ‘1a’ 这种却能处理,变成 1(也就是解析到不能解析为止)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var a = '123';
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-123

a = true;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN
// hint: 只管把数字转为整型,非数都是 NaN

a = null;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN
// hint: 同上

a = undefined;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN

a = NaN;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN

a = '3.14'
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-3
// hint: 直接把小数点扔掉

a = 'abc123';
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN
// hint: 非数

a = '123abc';
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-123
// hint: 如果数字和字符串混合,字符串在前,转为非数;数字在前,转到下一个不是数字为止的数字

a = '123.22abc';
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-123
1
2
3
4
5
6
7
8
9
10
var a = '10';
console.log(parseInt(a, 16)); // 16
// hint: 第二个参数是基底,把xx进制的a转为10进制 xx范围 2~36
// 也就是按照数 16 次进 1 的说法,算成十进制的数 10 就应该是 16
// 也就是16进制的16才等于10进制的10

var a = 'b';
console.log(parseInt(a, 16));
// hint: 0123456789abcdef
// 第 0 位:b * 16 ^ 0 = 11 * 1 = 11
1
2
parseInt('123', 5); // 把 '123' 看做5进制的数,返回十进制的数38
// => 1 * 5 ^ 2 + 2 * 5 ^ 1 + 3 * 5 ^ 0 = 25+10+3 = 38
1
2
3
4
5
6
7
8
parseInt(123)
123
parseInt("123")
123
parseInt("12a") //a不能解析成整数了
12
parseInt("a12a") //第一个位置就不能解析了
NaN
1
2
3
4
5
6
7
8
9
10
11
12
parseInt(12,0) //12 10进制为12
12
parseInt(12,5) //12 5进制为7
7
parseInt(12,8) //12 8进制为10
10
parseInt(112,2) //112 2进制,前两位是可以解析的,第三位不能解析,2进制只有0 1
3
parseInt(112,1) // 1进制只有0,第一位就不能解析
NaN
parseInt(123,3) // 123 中 12 可以解析,3进制为5
5

parseFloat 把值转为浮点数

1
2
3
4
5
6
7
8
var a = '3.1415926';
console.log(typeof(parseFloat(a)) + '-' + parseFloat(a)); // number-3.1415926

a = '3';
console.log(typeof(parseFloat(a)) + '-' + parseFloat(a)); // number-3

a = '3.14aab';
console.log(typeof(parseFloat(a)) + '-' + parseFloat(a)); // number-3.14
1
2
3
4
5
var num = parseFloat('3.1415926'); // 3.1415926
console.log(num.toFixed(2)) // 3.14

console.log(3.14621.toFixed(2)) // 3.15
// hint: 四舍五入

💄Boolean 转布尔值

  • false、’’、0、undefined、null、NaN
1
2
3
4
Boolean(null) // false

// 0 "" undefined null NaN false 都转为 false
// 其余都是 true

isNaN 是否为NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(isNaN(NaN)); // true
console.log(isNaN(123)); // false

console.log(isNaN('a')); // true
// hint: 'a'是非数,转不了number,就是NaN

console.log(isNaN('NaN'));
// true 全局isNaN 字符串非数就是NaN
console.log(Number.isNaN("NaN"));
// false Number下的isNaN,不会隐式转化


console.log(isNaN(null)); // false
// hint: Number(null) -> 0, 0是数字,不是NaN

console.log(isNaN(undefined)); // true
// hint: Number(undefined) -> NaN

隐式类型转换

前置知识

💄JS 8种内置类型

  • Number
  • String
  • Boolean
  • Undefined
  • null
  • Object(函数、数组都是Object的子类型)
  • Symbol (ES2015)
  • BigInt(ESNext stage 4)

类型分为 基本类型 和 复合类型 ,除了对象,其他都是基本类型。

ToPrimitive - 将任意值转为原始值

转换规则:

  1. 如果是基本类型,则不处理
  2. 调用 valueOf() ,并确保返回值是基本类型
  3. 如果没有 valueOf 这个方法或者 valueOf 返回的类型不是基本类型,那么对象会继续调用 toString() 方法
  4. 如果同时没有 valueOf 和 toString() 方法,或者返回的都不是基本类型,那么直接抛出 TypeError 异常

注意: 如果 preferedType=string,那么 2,3 顺序调换

转换实现:

对象 valueOf() toString() 默认 preferedType
Object 原值 “[object Object]” Number
Function 原值 “function xyz() {…}” Number
Array 原值 “x,y,z” Number
Date 数字(
存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数
)e.g.
1628864272562 “Sat May 22 2021…” String
  1. 数组的 toString() 可以等效为 join(",") ,遇到 null、undefined 都被忽略,遇到 symbol 直接报错,遇到无法 ToPrimitive 的对象也报错
  2. 使用 模板字符串 或者使用 String(...) 包装时,preferedType=string,即优先调用 .toString() 
  3. 使用 减法 或者 Number(...) 包装时,preferedType=number,即优先调用 .valueOf() 
1
2
3
4
5
[1, null, undefined, 2].toString(); // "1,,,2"

[1, Symbol('x')].toString(); // TypeError: Cannot convert a Symbol value to a string

[1, Object.create(null)].toString(); // VM3251:1 Uncaught TypeError: Cannot convert object to primitive value

加减法 +、-

加减法运算中遵循了一些隐式转换规则:

遇到对象先执行ToPrimitive转换为基本类型

  • 加法(+)运算,preferedType是默认值
  • 减法(-)运算,preferedType是Number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 + {}; // 1 + ({}).toString() => 1 + "[object Object]" => "1[object Object]"

1 + [2, 3];
// 1 + ([2, 3]).toString(); => 1 + [2, 3].join(",") => 1 + "2,3" => "12,3"

[1] + [2, 3];
// "1" + [2, 3].join(",") => "1" + "2,3" => "12,3"

function test() {};
10 + test;
// 10 + (test).toString() => 10 + "function test() {}" => "10function test() {}"

10 - undefined;
// 10 - Number(undefined) => 10 - NaN => NaN

字符串 + 任意值(基本类型),会被处理为字符串的拼接

这里的任意值都是指基本类型,因为对象会先执行ToPrimitive变成基础类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 + "1"; 																									// (1).toString() + "1" => "1" + "1" => "11"

1 + 1; // 2

"1" + 1; // "1" + (1).toString() => "1" + "1" => "11"

"1" + "1"; // "11"

1 + "1" + 1; // (1).toString() + "1" + (1).toString() => "1" + "1" + "1" => "111"

"1" + false; // "1" + "false" => "1false"

"1" + null; // "1" + "null" => "1null"

"X" + undefined + false; // "X" + "undefined" + "false" => "Xundefinedfalse"

"X" + {}; // "X" + ({}).toString() => "X" + "[object Object]" => "X[object Object]"

非字符串(基本类型) + 非字符串(基本类型),两边都会先 ToNumber

这里的非字符串都是指基本类型,因为对象会先执行ToPrimitive变成基础类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1 + true; 																// 1 + Number(true) => 1 + 1 => 2

1 + false; // 1 + Number(false) => 1 + 0 => 1

1 + null; // 1 + Number(null) => 1 + 0 = 1

1 + null + false + 1; // 1 + Number(null) + Number(false) => 1 + 0 + 0 + 1 => 2

1 + undefined; // 1 + Number(undefined) => NaN

1 + undefined + false; // 1 + Number(undefined) + Number(false) => 1 + NaN + 0 => NaN

🍒 1 + undefined + [1]; // 1 + Number(undefined) + ([1]).toString() => 1 + NaN + "1" => "NaN1"

🍒 1 + undefined + "1"; // 1 + Number(undefined) + "1" => 1 + NaN + "1" => "NaN1"

null + null; // Number(null) + Number(null) => 0 + 0 => 0

1 + ![]; // 1 + Number(!Boolean([])) => 1 + Number(!true) => 1 + 0 => 1

1 + !{}; // 1 + Number(!Boolean({})) => 1 + Number(!true) => 1 + 0 => 1

!{} + !{};
// Number(!Boolean({})) =>
// Number(!Boolean({})) =>
// Number(!true) + Number(!true) =>
// 0 + 0 => 0

任意值 - 任意值,一律执行 ToNumber,进行数字运算

此时的 preferedType === number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
3 - 1; 																										// 2

3 - '1'; // 3 - Number('1') => 3 - 1 => 2

'3' - 1; // Number('3') - 1 => 3 - 1 => 2

'3' - '1' - '2'; // Number('3') - Number('1') - Number('2') => 0

3 - []; // 3 - ([]).toString() => 3 - "" => 3 - Number("") => 3 - 0 => 3

3 - {};
// 3 - ({}).toString() =>
// 3 - "[object Object]" =>
// 3 - Number("[object Object]") =>
// 3 - NaN => NaN

var date = new Date();
date.toString(); // "Sun Aug 15 2021 16:05:17 GMT+0800 (China Standard Time)"
date.valueOf(); // 1629014717125

date + 1; // "Sun Aug 15 2021 16:05:17 GMT+0800 (China Standard Time)1"
date - 1; // 1629014717124

+ x 和 一元运算 +x 是等效的(以及 -x),都会强制 ToNumber

  • +x,x在后面才Number,x在前面则不会强转Number
  • a++,会当数字算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
+ 0; // 0
- 0; // 0

+ new Date();
// Number(Sun Aug 15 2021 16:09:47 GMT+0800 (China Standard Time)) =>
// 1629014965054

1 + + "1"; // 1 + Number("1") => 1 + 1 => 2

"1" + + "1"; // "1" + Number("1") => "1" + 1 => "11"

1 + + + + ["1"]; // 1 + (+(+(+["1"]))) => 1 + Number(["1"]) => 1 + 1 => 2

1 + - + - [1]; // 1 + (-(+(-[1]))) => 1 + (-(+(-Number([1])))) => 1 + 1 => 2

1 - + - + 1; // 1 - (+(-(+1))) => 1 - (-1) => 2

1 - + - + - 1; // 1 - (+(-(+(-1)))) => 1 - (1) => 0

💄1 + + [""]; // 1 + Number([""]) => 1

1 + + ["1", "2"];
// 1 + Number((["1", "2"].toString())) =>
// 1 + Number("1,2") =>
// 1 + NaN =>
// NaN
1
2
3
4
5
6
7
8
var a = '1';
a++;
console.log(a); // 2


var a = '1';
a += 1;
console.log(a); // '11'

💄经典问题 [] + {}

1
2
[] + {};
// ([]).toString() + ({}).toString() => "" + "[object Object]" => "[object Object]"

{} 在最前面时可能不再是对象

1
2
3
{} + []; // 0

{ a: 2 } + []; // 0

第一个,这时 {} 其实代表的是代码块,最后就变成了 +[],根据前面的原则,数组先被转换成字符串””,接着因为+x的运算,字符串被转成数字0。
第二个,此时a不是代表对象属性,而是被当成了标签(label),标签这东西IE6就已经有了。所以如果我们写成对象是会报错的,逗号要改成分号才能通过编译。

1
2
3
4
5
// Uncaught SyntaxError: Unexpected token ':'
{ a: 2, b: 3 } + []

// 分号OK
{ a: 2; b: 3 } + [] === 0;

⚠️注意:在 Node >= 13.10.0 的版本,{}被优先解释为空对象,仅在非对象结构的情况才会被认为是代码块。

1
2
3
4
5
6
7
8
9
10
11
12
13
// nodeJs >= 13.10.0 的运行结果

{} + []; // "[object Object]";
{ a: 2 } + []; // "[object Object]";
{ a: 2, b: 3 } + []; // "[object Object]";

// 注意是分号,当成代码块
{ a: 2; b: 3 } + []; // 0
// 有JS语句或者表达式,当成代码块
{ var a = 1; } + []; // 0
{ ; } + []; // 0
{ 123 } + []; // 0
{ 1 + 2 } + []; // 0

定论还是下的太早了,我们还是有办法让引擎优先处理成代码块:

1
2
3
4
5
6
7
// 所有node版本

;{} + [] === 0;
;{ a: 2 } + [] === 0;

// Uncaught SyntaxError: Unexpected token ':'
;{ a: 2, b: 3 } + [];

symbol不能加减

如果在表达式中有symbol类型,那么就会直接报错。比如1 + Symbol("x")报错如下:

1
Uncaught TypeError: Cannot convert a Symbol value to a number

💄宽松相等

相对于全等都需要对类型进行判断,当类型不一致时,宽松相等会触发隐式转换。下面介绍规则:

对象 == 对象,类型一致则不做转换

1
2
3
4
5
{} != {} // true

[] != {} // true

[] != [] // true

对象 == 基本值,对象先执行ToPrimitive转换为基本类型

1
2
3
4
5
// 小心代码块
"[object Object]" == {}; // ({}).toString() => "[object Object]"
[] == ""; // ([]).toString() => ""
[1] == "1"; // ([1]).toString(); => "1"
[1,2] == "1,2"; // ([1,2]).toString() => "1,2"

💄布尔值 == 非布尔值,布尔值先转换成数字,再按数字规则操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[] == false;
// [] => ([]).toString() => "" => Number("") => 0
// false => Number(false) => 0



// [1] => "1" => 1
// true => 1
[1] == true;
// [1] => ([1]).toString() => "1" => Number("1") => 1
// true => Number(true) => 1



[1,2] != true;
// [1,2] => ([1,2]).toString() => "1,2" => Number("1,2") => NaN
// true => Number(true) => 1



"0" == false;
// "0" => Number("0") => 0
// false => Number(false) => 0

"" == false;
// "" => Number("") => 0
// false => Number(false) => 0

💄数字 == 字符串,字符串ToNumber转换成数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"2" == 2;
// "2" => Number("2") => 2

"" == 0;
// Number("") => 0

[] == 0;
// [] => ([]).toStirng() => "" => Number("") => 0

[1] == 1;
// [1] => ([1]).toString() => "1" => Number("1") => 1

[1,2] != 1;
// ([1,2]).toString() => "1,2" => Number("1,2") => NaN

💄null、undefined、symbol

null、undefined与任何非自身的值对比结果都是false,但是 null == undefined 是一个特例。

1
2
3
4
5
6
7
8
9
10
11
null == null;
undefined == undefined;
null == undefined;

null != 0;
null != false;

undefined != 0;
undefined != false;

Symbol('x') != Symbol('x');

比较 < >

对比不像相等,可以严格相等(===)防止类型转换,对比一定会存在隐式类型转换。

对象总是先执行ToPrimitive为基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[] < []; // false
[] == []; // false
[] <= []; // true
[] >= []; // true
// ([]).toString() => ""

[] <= {}; // true
[] >= {}; // false
[] == {}; // false
[] > {}; // false
[] < {}; // true

// [] => ([]).toString() => ""
// {} => ({}).toString() => "[object Object]"
// "" <= "[object Object]"

{} < {}; // false
{} == {}; // false
{} > {}; // false
{} >= {}; // true
{} <= {}; // true
// {} => ({}).toString() => "[object Object]"

💄两边都是字符串时才比较UNICode,否则一律转换成数字做对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ["06"] => "06" => 6
["06"] < 2 // false

["06"] < "2" // true
["06"] > 2 // true

5 > null // true
-1 < null // true
0 <= null // true

0 <= false // true
0 < false // false

// undefined => Number(...) => NaN
5 > undefined // false

两边都是字符串,或两边都最终转为了字符串

  • 两边都是字符串,则比较字符串对应的 UNICode编码
    • charCodeAt(): 可以拿到字符的编码
    • 可以传入一个参数,来获取字符串的第n位的编码
  • 多个字符串的比较,从左到右依次比较
    • 对应字符不相等,直接比较出结果
    • 对应字符相等,比较下一位

💄总结

对象都需要先ToPrimitive转成基本类型,除非是宽松相等(==)时两个对象做对比。

    • 没有字符串的时候,全转数字
    • 全转数字,preferedType === Number
  • == 同类型不转,数字优先,布尔全转数字,null、undefined、symbol不转
  • < > 除非两边都是字符串,否则全部转为数字再比较
  • ++/–自增自减,算术运算符(- * / %)中,字符串转number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
123 + '123'; // '123123'
123 + null; // 123 + Number(null) => 123 + 0 => 123
123 + true; // 123 + Number(true) => 123 + 1 => 124
1 + NaN; // NaN, 只要有一个NaN就为NaN

'你的岁数是:'+10+10; // '你的岁数是:1010'
'你的岁数是'+(10+10); // '你的岁数是:20'
10+10+'是你的岁数'; // 从左到右计算, '20是你的岁数'

123 + {}; // 对象先转基本类型
// 123 + ({}).toString() => 123 + "[object Object]" => "123[object Object]"


var a = '123';
a++;
console.log(a); // 124
// a++ =>
// a = a + 1; =>
// a = '123' + 1; =>
// a = Number('123') + 1 =>
// a = 124

var a = 'a';
a++;
console.log(a); // NaN
// a = Number('a') + 1 => NaN + 1 => NaN

var a = '3' * 2;
console.log(a); // 6
// Number('3') * 2 => 6

var a = 'b' * 2;
console.log(a); // NaN
// Number('b') * 2 => NaN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
console.log('1' > 2); // false
console.log(1 > '2'); // false
// hint: 都转为 number

console.log('a' > 'b'); // false
// hint: 两个字符串比较,用ascii比较
// > < = >= <= 都要经过 number 转换,除了两个字符串

console.log(1 == '1') // true
console.log(1 != '2') // true
// hint: 都得转为数字

console.log(1 === '1'); // false
// hint: 全等不转换,还得检查数据类型

console.log(NaN == NaN); // false
// hint: NaN不等于任何东西

console.log(2 > 1 > 3); // false
// hint: 2 > 1 -> 返回 true -> true > 3 -> false
console.log(2 > 1 == 1); // true
// hint: 2 > 1 -> 返回 true -> true == 1 -> 隐式转换 true = 1


var num = '123';
console.log(typeof(- num) + ':' + - num); // number:-123

console.log(typeof(+ num) + ':' + + num); // number:123

num = 'abc';
console.log(typeof(+ num) + ':' + + num); // number:NaN

资源

关于JS隐式类型转换的完整总结