JavaScript 类型转换

243 阅读17分钟

JavaScript中很多情况下都会进行类型转换,针对不同情况有不同转换规则,看的比较杂乱,对该知识点仍不是很明确,特此整理一波。

JavaScript中主要有Undefined、Null、Boolean、Number、BigInt、String、Symbol、Object八种基本数据类型,但是类型转换只有三种情况,分别是:

  • 转换成布尔值(Boolean)
  • 转换为数值(Number)
  • 转化为字符串(String)

类型转换主要发生在以下几种情况中:

  • 使用转型函数 Boolean() Number() String() 等进行强制转换
  • 使用比较运算符 > 、 < 、 == 时会进行隐式类型转换
  • 使用四则运算符 + - * 计算不同类型数据时会进行隐式类型转

等等……

一、基本转换规则

1、转换为布尔(Boolean)类型

布尔类型只有 true 和 false 两个字面量,转换规则主要如下:

数据类型转换为 true转换为 false
Booleantruefalse
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高级程序设计(第四版)》