导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

显示类型转换

⭐️ Number() 把值转为数字

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
a = 'true';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-NaN
// 说明: 非数

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

a = '1a';
console.log(typeof(Number(a)) + '-' + Number(a)); // number-NaN
// 说明: 非数
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 把值转为一个整数

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

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

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

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
// 说明: 直接把小数点扔掉

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

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

a = '123.22abc';
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-123

a = '23.927'
// parseInt(a) → 23
var a = '10';
console.log(parseInt(a, 16)); // 16
// 说明: 第二个参数是基底,把xx进制的a转为10进制 xx范围 2~36
// 16 进制的 10 转 十进制 => 1*16^1 + 0*16^0 = 16 + 0 = 16

var a = 'b';
console.log(parseInt(a, 16));
// 说明: 0123456789abcdef
// 第 0 位:b * 16 ^ 0 = 11 * 1 = 11
parseInt('123', 5); // 把 '123' 看做5进制的数,返回十进制的数38
// => 1 * 5 ^ 2 + 2 * 5 ^ 1 + 3 * 5 ^ 0 = 25+10+3 = 38
parseInt(123)
123
parseInt("123")
123
parseInt("12a") // a不能解析成整数了
12
parseInt("a12a") // 第一个位置就不能解析了
NaN
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 把值转为浮点数

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
var num = parseFloat('3.1415926'); // 3.1415926
console.log(num.toFixed(2)) // 3.14

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

⭐️ Boolean 转布尔值

Boolean(null) // false

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

⭐️ isNaN 是否为 NaN

console.log(isNaN(NaN)); // true
console.log(isNaN(123)); // false

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

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

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

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

隐式类型转换

⭐️ ToPrimitive - 将任意值转为原始值

转换规则:

  1. 如果是基本类型,则不处理
  2. 调用 valueOf() ,并确保返回值是基本类型
  3. 如果没有 valueOf 这个方法或者 valueOf 返回的类型不是基本类型,那么对象会继续调用 toString() 方法
  4. 如果同时没有 valueOftoString() 方法,或者返回的都不是基本类型,那么直接抛出 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, 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
console.log([].valueOf()); // []
console.log([].toString()); // ''
console.log({}.valueOf()); // {}
console.log({}.toString()); // [object Object]
console.log((function test (){ var a }).toString()); // function test (){ var a }

加减法 +、-

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

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

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"].join(",") + [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 + "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]"

⭐️ [] + '';																									// ([]).toString() + '' => '' + '' => ''
[null] + '';																									// ([null]).toString() + '' => '' + '' => ''
[undefined] + '';																									// ([undefined]).toString() + '' => '' + '' => ''
[1] + '';																									// ([1]).toString() + '' => '1' + '' => '1'

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

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

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 + Number(!Boolean([])),先把 ![] 转化成 !Boolean([]) ,再变成 !true, 再变成 false, 就变成了 1 + false, 对于基本类型,加法中用 Number 转非数字类型,变成了 1+Number(false),最后成了 1 + 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

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

+ 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
var a = '1';
a++; // 当执行 a++ 时,JavaScript 首先会将字符串 '1' 转换为数字 1。
console.log(a); // 2

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

⭐️ 经典问题 [] + {}

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

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

{} + []; // 0

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

第一个,这时 {} 其实代表的是代码块,最后就变成了 +[],根据前面的原则,数组先被转换成字符串"",接着因为+x的运算,字符串被转成数字0。

第二个,此时a不是代表对象属性,而是被当成了标签(label),标签这东西IE6就已经有了。所以如果我们写成对象是会报错的,逗号要改成分号才能通过编译。

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

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

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

// 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

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

// 所有node版本

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

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

symbol不能加减

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

Uncaught TypeError: Cannot convert a Symbol value to a number

⭐️⭐️ 加减法总结 ⭐️⭐️

加法运算

  1. 类型转换
  2. 对象转换
  3. 运算细节

减法运算

  1. 类型转换
  2. 对象转换
  3. 运算细节

核心点

加法和减法的唯一不同就在:在最终把所有非基本类型通过 ToPrimitive 转成基本类型后:

⭐️ 宽松相等

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

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

console.log({} != {}); // true

console.log([] != {}); // true

console.log([] != []); // true

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

parseInt(string, Radix)