JavaScript中很多情况下都会进行类型转换,针对不同情况有不同转换规则,看的比较杂乱,对该知识点仍不是很明确,特此整理一波。
JavaScript中主要有Undefined、Null、Boolean、Number、BigInt、String、Symbol、Object八种基本数据类型,但是类型转换只有三种情况,分别是:
- 转换成布尔值(Boolean)
- 转换为数值(Number)
- 转化为字符串(String)
类型转换主要发生在以下几种情况中:
- 使用转型函数 Boolean() Number() String() 等进行强制转换
- 使用比较运算符 > 、 < 、 == 时会进行隐式类型转换
- 使用四则运算符 + - * 计算不同类型数据时会进行隐式类型转
等等……
一、基本转换规则
1、转换为布尔(Boolean)类型
布尔类型只有 true 和 false 两个字面量,转换规则主要如下:
| 数据类型 | 转换为 true | 转换为 false |
|---|---|---|
| Boolean | true | false |
| Null | 不存在 | null |
| Undefined | 不存在 | undefined |
| String | 非空字符串 | "" (空字符串) |
| Number | 非 0 数值(包含Infinity) | 0、+0、-0、NaN |
| BigInt | 非 0n 值 | 0n 、-0n |
| Symbol | 任意符号类型变量 | 不存在 |
| Object | 任意对象(包括Boolean(false)) | 不存在 |
// Undefined
console.log(Boolean(undefined)); // false
// Null
console.log(Boolean(null)); // false
// String类型
console.log(Boolean('str')); // true
console.log(Boolean('')); // false
// Number类型
console.log(Boolean(1)); // true
console.log(Boolean(-1)); // true
console.log(Boolean(Infinity)); // true
console.log(Boolean(-Infinity)); // true
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(+0)); // false
console.log(Boolean(NaN)); // false
// BigInt类型
console.log(Boolean(1n)); // true
console.log(Boolean(-1n)); // true
console.log(Boolean(0n)); // false
console.log(Boolean(-0n)); // false
// Symbol类型
console.log(Boolean(Symbol(1))); // true
// Object类型
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(/1/)); // true
console.log(Boolean(Math)); // true
// 即使是值为false的布尔包装类型,也会转换为true
console.log(Boolean(new Boolean(false))) // true
2、转换成数值(Number)类型
将非数值转换成数值,主要有三个函数:Number()、parseInt()、parseFloat()。
- Number()转型函数可用于任何数据类型
- parseInt()、parseFloat()主要用于字符串转换成数值
(1)Number() 转换规则
对以上规则个人补充几点:
- Symbol类型的数据不能转换成数值类型,会抛出 typeError
Number(Symbol(1))
// Uncaught TypeError: Cannot convert a Symbol value to a number
- BigInt类型转换成数值类型,去除 n 即可。
console.log(Number(1n)) // 1
console.log(Number(BigInt(1))) // 1
- 有关对象转换成数值类型,详见后文“对象转换为原始数据类型”中个人的一些理解。
(2)相关示例
a、Undefined转换为数值
console.log(Number(undefined)); // NaN
b、Null转换成数值
console.log(Number(null)); // 0
c、布尔值(Boolean)转换成数值
console.log(Number(true)); // 1
console.log(Number(false)); // 0
d、字符串(String)转换成数值
- 一般情况
// 空字符串转换成0
console.log(Number("")); // 0
// 只包含空格、\n、\t、\r、\f等的字符串也转换为0
console.log(Number(' \n\t\r\f')) // 0
console.log(Number("123")); // 123
console.log(Number("+123")); // 123
console.log(Number("-123")); // -123
console.log(Number("00123")); // 123
console.log(Number("12.3")); // 12.3
console.log(Number(".123")); // 0.123
console.log(Number("0xf")); // 15
- 如果字符串中包含 数值字符、小数点、正号、负号、"0x" 以外的字符时,一定转换为NaN
console.log(Number("a")); // NaN
console.log(Number("1a")); // NaN
console.log(Number("12/")); // NaN
- 正号、负号 以及 "0x" 必须在字符串的头部,否则也会转换为 NaN。且在其之前的字符‘0’不会被忽略。
console.log(Number("1-2")); // NaN
console.log(Number("1+2")); // NaN
console.log(Number("0-12")); // -12
console.log(Number("10x2")); // NaN
console.log(Number("00x12")); // NaN
- 正号、负号 和 “0x" 同时出现在字符串头部时,也会转换成 NaN
console.log(Number("+0x2")); // NaN
console.log(Number("-0x2")); // NaN
- 字符串中只能包含一个 小数点 , 否则也会转换为 NaN
console.log(Number("2.3.2")); // NaN
- 有些情况下字符串中头部0字符会被忽略。
console.log(Number("0000012")); // 12
console.log(Number("001.2")); // 1.2
console.log(Number("-0012")); // -12
总之,字符串转换成数值,一定要满足从字符串中包含有效数值格式,否则就会转换成NaN。
e、对象转换为数值
除了 数组(Array)、Date等引用类型,其他基本引用类型都会转化为 NaN
// 数组为空,转换为0
console.log(Number([])); // 0
// 数组中只有一个元素,则按照上述规则对该元素进行转换
console.log(Number([1])); // 1
console.log(Number(["12"])); // 12
// 数组有多个元素,则转换为 NaN
console.log(Number([1, 2])); // NaN
// 日期对象, 返回从1970年1月1日0时0分0秒(UTC,即协调世界时)到该日期的毫秒数。
console.log(Number(new Date())) // 1650073121195
// 其他任何对象都会转换为NaN
console.log(Number({})); // NaN
console.log(Number(Math)); // NaN
(2)parseInt() 转换规则
parseInt() 更加关注字符串是否包含数值模式,字符串最前面的空格会被忽略,从第一个非空格字符开始转换。
- 如果第一个字符不是数值字符、加号或减号,parseInt() 会立即返回NaN
- 如果第一个字符是数值字符、加号或减号,则依次检测每个字符,直到字符串末尾,或遇到非数值字符。
parseInt() 提供第二个参数,用于指定底数(进制数),默认为10(十进制)
// 需要注意的是,parseInt()会将空字符串转换成NaN
console.log(parseInt('')) // NaN
console.log(parseInt('123')) // 123
console.log(parseInt('123.1')) // 123
console.log(parseInt('12f')) // 12
console.log(parseInt('f12')) // NaN
// 指定底数
console.log(parseInt('AF')) // NaN
console.log(parseInt('0xAF')) // 175
console.log(parseInt('AF', 16)) // 175
console.log(parseInt('11', 2)) // 3
console.log(parseInt('11', 8)) // 9
console.log(parseInt('11', 10)) // 11
console.log(parseInt('11', 16)) // 17
(3)parseFloat() 转换规则
parseFloat() 转换规则与parseInt() 类似,但是需要注意以下几点:
- 字符串中第一个出现的小数点是有效的,后面出现的小数点无效
- 只能识别浮点形式、十进制形式的字符串,十六进制形式都会转换为0
// 空字符串转换为NaN
console.log(parseFloat('')) // NaN
console.log(parseFloat('12e')) // 12
console.log(parseFloat('12.8')) // 12.8
// 第一个小数点有效
console.log(parseFloat('12.2.3')) // 12.2
// 十六进制转换为0
console.log(parseFloat('0x12')) // 0
// 忽略字符串开头的0
console.log(parseFloat('0003.2')) // 3.2
console.log(parseFloat('.32')) // 0.32
// 支持转换指数形式字符串
console.log(parseFloat('3.2e4')) // 32000
3、转换成字符串(String)
(1)转换规则
console.log(String(undefined)) // "undefined"
console.log(String(null)) // "null"
// 布尔值转换成字符串
console.log(String(true)) // "true"
console.log(String(false)) // "false"
// 数值转换成字符串
console.log(String(123)) // "123"
console.log(String(12.3)) // "12.3"
console.log(String(12.3e3)) // "12300"
// 数值指定底数
console.log((123).toString(8)) // "173"
console.log((123).toString(16)) // "7b"
// 全局字面量
console.log(String(NaN)); // "NaN"
console.log(String(-Infinity)); // "-Infinity"
console.log(String(+Infinity)); // "Infinity"
console.log(String(Infinity)); // "Infinity"
// BigInt转换成字符串
console.log(String(1n)) // "1"
// 符号型转换为字符串
console.log(String(Symbol("123S"))) // "Symbol(123S)"
// 数组对象转换为字符串
console.log(String([])) // ""
console.log(String([1, 2])) // "1,2"
console.log(String([1,2, "123", {}])) // "1,2,123,[object Object]"
// 日期对象转换为字符串,返回一个美式英语日期格式的字符串
console.log(String(new Date())) // Sat Apr 16 2022 09:42:55 GMT+0800 (中国标准时间)
// 其他对象转换成字符串
console.log(String({})) // "[object Object]"
console.log(String(new Map())) // "[object Map]"
二、对象转换为原始数据类型
JavaScript中对象转换成原始数据类型,与Symbol.toPrimitive、Object.prototype.valueOf()、Object.prototype.toString()密切相关,所以首先简单介绍一下:
1、Symbol.toPrimitive、Object.prototype.valueOf()、Object.prototype.toString()
(1)Symbol.toPrimitive
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数
在 Symbol.toPrimitive属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
(2)Object.prototype.valueOf()
valueOf() 方法返回指定对象的原始值。
默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。
| 对象 | 返回值 |
|---|---|
| Array | 返回数组对象本身。 |
| Boolean | 布尔值。 |
| Date | 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。 |
| Function | 函数本身。 |
| Number | 数字值。 |
| Object | 对象本身。这是默认情况。 |
| String | 字符串值。 |
| Math 和 Error 对象没有 valueOf 方法。 |
console.log([1, 2].valueOf()); // [1, 2]
console.log((true).valueOf()); // true
console.log(new Date().valueOf()); // 1650074218265
console.log(new Function().valueOf()) // ƒ anonymous() {}
console.log(new Object().valueOf()) // {}
console.log(("a").valueOf()); // "a"
(3)Object.prototype.toString()
toString() 方法返回一个表示该对象的字符串。
每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。
console.log([1, 2].toString()); // "1, 2"
console.log([].toString()); // ""
console.log((true).toString()); // "true"
console.log(new Date().toString()); // Sat Apr 16 2022 10:00:34 GMT+0800 (中国标准时间)
console.log(new Function().toString()) // ƒ anonymous() {}
console.log(new Object().toString()) // [object Object]
console.log((234).toString()); // "234"
2、转换规则
(1)对象obj中存在 Symbo.toPrimitive 属性
那么直接调用 objSymbol.toPrimitive ,然后对返回值按转换规则进行转换。(无论转换结果如何,后续不再调用 valueOf() 和 toString() )
有关参数hint:
加法操作符 和 相等/不相等操作符中进行对象的隐式类型转换时,hint 参数为 "defalt"
Number()转型函数强制转换:hint参数为 "number"
String() 转型函数强制转换: hint参数为 "Number"
例1:如下代码,obj具有Symbol.toPrimitive属性,当转换成原始数据类型时,直接调用Symbol.toPrimitive。并且会按照相应规则对返回值进行转换。
let obj = {
valueOf(){
return 3;
},
toString() {
return 'str';
},
[Symbol.toPrimitive](hint) {
if (hint === 'number')
return '1';
else if(hint === 'string')
return 'toPrimitive'
}
}
console.log(Number(obj)); // 1
console.log(String(obj)); // toPrimitive
例2:如下代码,无论是转换成Number 还是 String 都会调用Symbol.toPrimitive,返回值为{},由于对象不能转换成原始数据类型,所以报错。这也说明如果对象中存在 Symbo.toPrimitive 属性,在进行对象类型转换时,只会调用 objSymbol.toPrimitive,无论结果如何都不会再去调用 valueOf() 或 toString()
let obj = {
valueOf(){
return 1;
},
toString() {
return 'str';
},
[Symbol.toPrimitive](hint) {
return {}
}
}
console.log(Number(obj)); // 报错:Uncaught TypeError: Cannot convert object to primitive value
console.log(String(obj)); // 报错:Uncaught TypeError: Cannot convert object to primitive value
(2)对象obj中不存在 Symbo.toPrimitive 属性
-
- 如果转换成数值类型(或者默认情况下) ,会优先调用 valueOf() 函数,
-
- 若 valueOf() 返回值是原始数据类型,则按数值转换规则进行转换
- 若没有 valueOf() 或者 valueOf() 的返回值是对象,则调用 toString()
-
- 若 toString() 返回值是原始数据类型,则按数值转换规则进行转换
- 若 toString() 返回值是仍然是对象,则会抛出 Uncaught TypeError: Cannot convert object to primitive value
- 如果转换成字符串类型,会优先调用 toString() 函数
-
- 若 toString() 返回值是原始数据类型,则按字符串转换规则进行转换
- 若没有 toString() 或者 toString() 的返回值是对象,则调用 valueOf()
-
- 若 valueOf() 返回值是原始数据类型,则按字符串转换规则进行转换
- 若 valueOf() 返回值是仍然是对象,则会抛出 Uncaught TypeError: Cannot convert object to primitive value
// 不存在Symbol.toPrimitive属性,转换成数值优先调用valueOf(),转换成字符串优先调用toString()
let obj = {
valueOf(){
return 1;
},
toString() {
return 'str';
},
}
console.log(Number(obj)); // 1
console.log(String(obj)); // str
// valueOf() 返回值为对象,转换为数值调用valueOf()后会继续调用toString()
let obj = {
valueOf(){
return {};
},
toString() {
return '123';
},
}
console.log(Number(obj)); // 123
console.log(String(obj)); // "123"
// toString() 返回值为对象,转换为数值调用toString()后会继续调用valueOf()
let obj = {
valueOf(){
return 12;
},
toString() {
return {};
},
}
console.log(Number(obj)); // 12
console.log(String(obj)); // "12"
// valueOf() 和 toString()都返回对象时,对象无法转换成原始数据类型
let obj = {
valueOf(){
return {};
},
toString() {
return {};
},
}
console.log(Number(obj)); // 报错:Uncaught TypeError: Cannot convert object to primitive value
console.log(String(obj)); // 报错:Uncaught TypeError: Cannot convert object to primitive value
《JavaScript高级程序设计(第四版)》中写道“当对象转换成数值时,会优先调用valueOf(),若转换后值为NaN,会继续调用toString()”。关于该点,个人代码运行结果并非如此。
如下代码:
- 对象obj 中存在 valueOf() 函数和 toString() 函数。
- 当对象 obj 转换成数值时,首先调用了 valueOf() 函数,返回值为 "a",然后按照数值转换规则,转换为NaN。但是转换值为NaN后,并没有继续调用 toString() 函数,而是直接返回了NaN。
- 另外,toString() 的返回值为 '123' 是可以转换成数值 123 的,这也说明转换成NaN后,并未调用toString()。
关于这点,希望大佬们可以给点建议,个人还不是很明白。
let obj = {
valueOf(){
return 'a';
},
toString() {
return '12';
},
}
console.log(obj.valueOf()) // "a"
console.log(Number(obj.valueOf())) // NaN
console.log(obj.toString()); // "12"
console.log(Number(obj.toString())) // 12
console.log(Number(obj)); // NaN
console.log(String(obj)); // "12"
3、总结
有关对象转换成原始数据类型总结如下图:
即 Symbo.toPrimitive 优先级最高,若没有 Symbo.toPrimitive ,转换成数值时valueOf() 优先级高于 toString(), 转换成字符串时toString() 优先级高于 valueOf()。
三、隐式类型转换
由于JavaScript是”弱类型“语言,所以在很多情况下,都会进行隐式类型转换。
1、一元操作符中的隐式类型转换
(1)递增(++) / 递减(--)操作符
递增(++) 应用到非数值(不包括BigInt),会先执行与使用 Number() 转型函数一样的类型转换,然后对转换后的数值加1
递减(--) 应用到非数值(不包括BigInt),会先执行与使用 Number() 转型函数一样的类型转换,然后对转换后的数值减1
注意:
- 对非数值变量进行递增(++) / 递减(--)操作后,变量类型会改变成 Number。
- 如果转换后的值为NaN,那么都返回NaN,因为NaN与数值进行计算,结果都返回NaN。
- ES6中加入的 BigInt 类型,在执行递增递减操作与Number类型相同,且不会改变变量类型。
let a;
console.log(++a); // NaN
a = true;
console.log(++a); // 2
a = false;
// 这里结果为0,说明非数值在执行 ++ 之前会先进行数值转换
console.log(a++); // 0
a = 1n;
console.log(++a); // 2n
// BigInt类型数据执行++后不会改变数据类型
console.log(typeof a) // BigInt
a = '123';
console.log(++a); // 124
a = '123a';
console.log(++a); // NaN
let obj = {
valueOf(){
return {};
},
toString() {
return '1';
},
}
console.log(++obj); // 2
// 执行 ++ 操作后,改变了变量的类型
console.log(typeof obj); // Number
(2)一元加和减
一元加应用到非数值(不包括BigInt),会执行与使用 Number() 转型函数一样的类型转换。
一元减应用到非数值(不包括BigInt),也会执行与使用 Number() 转型函数一样的类型转换,然后取对应的负值。
所以有时候可以用一元加直接来做数值转换。
console.log(+undefined); // NaN
console.log(+null); // 0
console.log(+true); // 1
console.log(+"123"); // 124
console.log(+"12a"); // NaN
// 关于BigInt需要注意
console.log(+1n); // Uncaught TypeError: Cannot convert a BigInt value to a number
console.log(-1n); // -1n
let obj = {
valueOf(){
return {};
},
toString() {
return '1';
},
}
console.log(+obj); // 1
注意:单目运算符 + 不能使用于BigInt,会报错
2、逻辑操作符
(1)逻辑非(!)
逻辑非(!) 应用于非布尔值时,会先按Boolean()转型函数类似规则进行转换,然后对转换后的结果取反。
console.log(!undefined); // true
console.log(!null); // true
console.log(!123); // false
console.log(!0); // true
console.log(!"12a"); // false
console.log(!1n); // true
console.log(!{}); // false
(2)逻辑与(&&)
逻辑与(&&)可以用于任何类型的操作数,但不一定返回布尔值。具体规则如下:
操作数1 && 操作数2
对操作数1进行布尔值转换
- 操作数1转换后为false,那么直接返回操作数1
- 操作数1转换后为true,那么直接返回操作数2
(3)逻辑或(||)
逻辑与(||)可以用于任何类型的操作数,但不一定返回布尔值。具体规则如下:
操作数1 || 操作数2
对操作数1进行布尔值转换
- 操作数1转换后为true,那么直接返回操作数1
- 操作数1转换后为false,那么直接返回操作数2
逻辑与和逻辑非的返回值在条件语句中会进行布尔类型的转换。
3、加性操作符
3.1 加法操作符(+)
(1)加法操作符的运算规则:
- 如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
-
- 如果有任一操作数是 NaN,则返回 NaN;
- 如果是 Infinity 加 Infinity,则返回 Infinity;
- 如果是-Infinity 加-Infinity,则返回-Infinity;
- 如果是 Infinity 加-Infinity,则返回 NaN;
- 如果是+0 加+0,则返回+0;
- 如果是-0 加+0,则返回+0;
- 如果是-0 加-0,则返回-0。
NaN + 1 // NaN
Infinity + Infinity // Infinity
Infinity + -Infinity // NaN
-Infinity + -Infinity / -Infinity
+0 + +0 // 0
-0 + -0 // -0
-0 + +0 // 0
- 如果有一个操作数是字符串,则要应用如下规则:
-
- 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
- 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
console.log(12 + "213") // "12213"
console.log("213" + 12) // "21312"
console.log("12" + null) // "12null"
- 若有两个操作数是 Number、Undefined、Null、布尔值 之间一个, 两个操作数都会转换为数值Number类型,再进行加法运算。
console.log(1 + undefined) // NaN
console.log(1 + null) // 1
console.log(1 + true) // 2
console.log(1 + false) // 1
console.log(null + null) // 0
console.log(null + true) // 1
- 如果有任一操作数是对象,会按以下步骤调用函数:
-
- 若存在 Symbol.toPrimitive 属性 则调用 Symbol.toPrimitive,若返回值为对象则报错:TypeError
- 若不存在 Symbol.toPrimitive 属性 ,则首先调用 valueOf() 方法取得表示它的数值
-
- 若不存在valueOf() 方法 或者 valueOf() 返回值为对象,继续调用 toString() 方法取得表示它的字符串
-
- 若 toString()返回值仍为对象,则报错:TypeError
对象操作数调用上述函数的返回值,会与另一个操作数按前面的规则进行计算。
let obj = {
valueOf(){
return 1;
},
toString() {
return 1;
},
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
}
console.log(obj + 1) // default1
(2)总结
加法运算符中:
- 任一操作数为对象,那么首先按规则调用对象的 Symbol.toPrimitive、valueOf()、toString()方法,将该对象转换成原始值。然后将该对象转换后的与另一个操作数进行运算。
- 任一操作数为字符串,则另外一个操作一定是转换成字符串形式,然后进行拼接。
- 若操作数中不存在字符串、对象,那么操作数都转换成Number类型进行运算
此外,操作数不能是Symbol类型,否则会报错。
console.log("12" + Symbol(1)); // Uncaught TypeError: Cannot convert a Symbol value to a string
console.log(1 + Symbol(1)); // Uncaught TypeError: Cannot convert a Symbol value to a number
console.log(true + Symbol(1)); // instanceof.js:29 Uncaught TypeError: Cannot convert a Symbol value to a number
3.2 减法操作符
(1)运算规则
- 如果两个操作数都是数值
-
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 减 Infinity,则返回 NaN。
- 如果是-Infinity 减-Infinity,则返回 NaN。
- 如果是 Infinity 减-Infinity,则返回 Infinity。
- 如果是-Infinity 减 Infinity,则返回-Infinity。
- 如果是+0 减+0,则返回+0。
- 如果是+0 减-0,则返回+0。
- 如果是-0 减-0,则返回+0。
- 如果是-0 减+0,则返回-0。
NaN - 1 // NaN
Infinity - Infinity // NaN
Infinity - -Infinity // Infinity
-Infinity - -Infinity // NaN
-Infinity - Infinity // -Infinity
+0 - +0 // 0
+0 - -0 // 0
-0 - -0 // 0
-0 - +0 // -0
- 如果有任一操作数是字符串、布尔值、null 或 undefined,则先在后台使用 Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN,则减法计算的结果是 NaN。
console.log(1 - undefined); // NaN
console.log(1 - null); // 1
console.log(1 - true); // 0
console.log(1 - '1') // 0
console.log(1 - 'a') // NaN
- 如果有任一操作数是对象
-
- 若存在 Symbol.toPrimitive 属性, 则调用 Symbol.toPrimitive ,并对返回值进行数值转换,在进行计算。
- 若不存在 Symbol.toPrimitive 属性 ,则调用其 valueOf()方法取得表示它的数值,并对返回值进行数值转换。如果对象没有 valueOf()方法 或者 valueOf 返回值为对象,则调用其 toString()方法,然后再将得到的字符串转换为数值
let obj = {
valueOf(){
return 1;
},
toString() {
return 1;
},
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return '12';
default:
throw new Error();
}
}
}
console.log(obj - 1); // 122
(2)总结
减法操作符中:
- 除了Number类型以外的数据类型,都要转换成Number类型,再进行减法运算。
- 减法操作符对象的转换和加法操作符不同在于,加法操作符中根据另一个操作数的类型,对对象调用函数后的返回值进行类型转换,而减法操作符中直接对对象调用函数后的返回值进行数值类型转换。
4、乘性操作符
4.1 乘法操作符(*)
(1)运算规则
乘法操作符针对特殊值也有一些特殊的行为:
- 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果 ECMAScript 不能表示乘积,则返回 Infinity 或 -Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 乘以 0,则返回 NaN 。
- 如果是 Infinity 乘以非 0的有限数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
- 如果是 Infinity 乘以 Infinity,则返回 Infinity。
- 如果有不是数值的操作数,则先在后台用 Number()将其转换为数值,然后再应用上述规则。
NaN * 12 // NaN
Infinity * 0 // NaN
Infinity * 12 // Infinity
Infinity * -1 // -Infinity
Infinity * Infinity // Infinity
Infinity * -Infinity // -Infinity
4.2 除法操作符(/)
(1)运算规则
除法操作符针对特殊值也有一些特殊的行为:
- 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正 值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果是 0 除以 0,则返回 NaN。
- 如果是非 0 的有限值除以 0,则根据第一个操作数的符号返回 Infinity 或-Infinity。
- 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
- 如果有不是数值的操作数,则先在后台用 Number()函数将其转换为数值,然后再应用上述规则。
Infinity / Infinity // NaN
0 / 0 // NaN
12 / 0 // Infinity
Infinity / 12 // Infinity
4.3 取模操作符(%)
(1)运算规则
取模操作符对特殊值也有一些特殊的行为。
- 如果操作数是数值,则执行常规除法运算,返回余数。
- 如果被除数是无限值,除数是有限值,则返回 NaN。
- 如果被除数是有限值,除数是 0,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果被除数是有限值,除数是无限值,则返回被除数。
- 如果被除数是 0,除数不是 0,则返回 0。
- 如果有不是数值的操作数,则先在后台用 Number()函数将其转换为数值,然后再应用上述规则。
Infinity % 12 // NaN
Infinity % Infinity // NaN
12 % Infinity // 12
12 % 0 // NaN
0 % 12 // 0
5、关系操作符
关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=)
- 如果操作数都是数值,则执行数值比较。
- 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
- 如果有任一操作数是对象,则会执行Number() 函数转换成数值后再去进行比较。
- BigInt类型可以与数值类型进行比较。
注意:在比较 NaN 时,无论是小于还是大于等于,比较的结果都会返回 false。
总结: 除了字符串和字符串之间的比较、BigInt 和 BigInt的比较 不会转换为数值类型,其他情况下都是转换成数值类型,然后进行比较。
6、相等操作符
6.1 等于和不等于
等于操作符用两个等于号(==)表示,如果操作数相等,则会返回 true。
不等于操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回 true。
这两个操作符都会先进行类型转换(通常称为强制类型转换) 再确定操作数是否相等。
(1)转换规则
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0,true 转换 为 1。
- 如果任一操作数是BigInt,则尝试将BigInt转换为Number,再比较是否相等。
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
- 如果一个操作数是对象,另一个操作数不是,则与加法运算符中的对象转换规则相同,即Symbol.toPrimitive -> valueOf() -> toString()
(2)比较规则
- 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回
true
console.log(new Number(1) == new Number(1)); // false
- null 和 undefined 相等。
console.log(NaN == null); // true
- null 和 undefined 不能转换为其他类型的值再进行比较。
console.log(NaN == 'NaN'); // false
console.log(undefined == 'undefined'); // false
- 如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两 个操作数都是 NaN,相等操作符也返回 false,因为按照规则,NaN 不等于 NaN。
console.log(NaN == NaN); // false
console.log(NaN != NaN); // true
console.log(NaN == 1); // false
console.log(NaN == "1"); // false
console.log(NaN == false); // false
6.2 全等和不全等
(1)全等操作符由 3 个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回 true
let result1 = ("55" == 55); // true,转换后相等
let result2 = ("55" === 55); // false,不相等,因为数据类型不同
(2)不全等操作符用一个叹号和两个等于号(!==)表示,只有两个操作数在不转换的前提下不相等才返回 true。
let result1 = ("55" != 55); // false,转换后相等
let result2 = ("55" !== 55); // true,不相等,因为数据类型不同
注意 : 由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。
这样有助于在代码中保持数据类型的完整性。
四、有关Number 和 BigInt
1、可以通过 Number()转型函数 将 BigInt 强制转换成Number类型,反之也可以通过BigInt() 将 Number 转换成 BigInt 类型
console.log(Number(1n)); // 1
console.log(BigInt(1)); // 1n
2、BigInt 不支持单目加运算符,但支持单目减运算符
console.log(+1n); // Uncaught TypeError: Cannot convert a BigInt value to a number
console.log(-1n); // -1n
3、Number类型 和 BigInt 类型之间不能进行四则运算
console.log(1+1n); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1-1n); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1*1n); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1/1n); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
4、Number类型 和 BigInt 类型可以进行比较
(1)通过关系运算符 或 等于不等于运算符比较时,会通过Number()转型函数将BigInt转换成Number再进行比较
console.log(1 > 2n); // false
console.log(1 == 1n); // true
(2)通过全等和不全等比较时,Number类型和BigInt类型永远不相等
console.log(1 === 1n); // false
console.log(1 !== 1n); // true
console.log(1 === 2n); // false
console.log(1 !== 2n); // true
\
五、 isNaN() 中的隐式转换
1、isNaN()
isNaN() 函数用来确定一个值是否为NaN
如果isNaN函数的参数不是Number类型, isNaN函数会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN进行判断。
具体转换规则和Number()转型函数相同。
isNaN(NaN); // true
isNaN(undefined); // true
isNaN({}); // true
isNaN(true); // false
isNaN(null); // false
isNaN(37); // false
// strings
isNaN("37"); // false: 可以被转换成数值37
isNaN("37.37"); // false: 可以被转换成数值37.37
isNaN("37,5"); // true
isNaN('123ABC'); // true: parseInt("123ABC")的结果是 123, 但是Number("123ABC")结果是 NaN
isNaN(""); // false: 空字符串被转换成0
isNaN(" "); // false: 包含空格的字符串被转换成0
// dates
isNaN(new Date()); // false
isNaN(new Date().toString()); // true
isNaN("blabla") // true: "blabla"不能转换成数值
// 转换成数值失败, 返回NaN
2、Number.isNaN()
既然提到了isNaN(),那也顺带复习以下Number.isNaN()。
Number.isNaN() 方法确定传递的值是否为NaN,并且检查其类型是否为Number
Number.isNaN(NaN); // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0) // true
// 下面这几个如果使用全局的 isNaN() 时,会返回 true。
Number.isNaN("NaN"); // false,字符串 "NaN" 不会被隐式转换成数字 NaN。
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN("blabla"); // false
// 下面的都返回 false
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN(37);
Number.isNaN("37");
Number.isNaN("37.37");
Number.isNaN("");
Number.isNaN(" ");
3、isNaN() 和 Number.isNaN() 的区别
isNaN() 和 Number.isNaN() 的主要区别在于,isNaN()在判断变量是否为 NaN时,如果遇到非数值会先对其进行数值转换,然后再进行判断。
\
以上是个人在整理知识点时的一些理解,也都是基于官方文档和书籍的基础上,如有错误,希望大佬们可以提出指正!
\
主要参考:
[1] MDN 文档
[2]《JavaScript高级程序设计(第四版)》