基础篇:类型转换

类型转换(Type Conversion)是指将数据由一种类型变换为另一种类型。在 JavaScript 中,在一些运算类型与预期不符的情况下,会发生隐式转换,也可以用一些写法强制要求进行显式转换。

JavaScript 中有 7 种简单数据类型(也称为基本数据类型):数字(Number)、字符串(String)、布尔值(Boolean)、Undefined、Null、ES6 新增的 Symbol,以及 ES2020 起正式纳入的 BigInt。此外还有 1 种复杂数据类型(也称引用类型):对象(Object)。

强制类型转换

强制类型转换主要是使用 Number()String()Boolean() 三个函数,手动将各种类型的值分别转换成数字、字符串、布尔值。

Number()

Number 对象是经过封装的处理数值的对象,主要有两个作用:一是作为构造函数,创建数字对象;二是用来将任意类型的值转换为数值。

原始类型值

  • 数值:返回该数值本身。控制台把极大或极小的数打成科学计数法,那是显示用的字符串,不是 Number() 又转了一遍。
  • 字符串:整段都能当成数值则转换;有任何一个字符不行就返回 NaN;空字符串(以及只含空白)转为 0
  • 布尔值:true 转成 1false 转成 0
  • undefined:转为 NaN
  • null:转为 0
  • Symbol:不能转成数字,会抛 TypeError。
  • BigInt:转为 Number,字面量末尾的 n 不会出现。超出安全整数范围会丢精度。
Number(324) // 324

Number('324')    // 324
Number('324abc') // NaN
Number('')       // 0

Number(true)  // 1
Number(false) // 0

Number(undefined) // NaN
Number(null)      // 0

Number(Symbol('a')) // Uncaught TypeError: Cannot convert a Symbol value to a number
Number(1n)          // 1
Number(9007199254740993n) // 9007199254740992,超出安全整数,精度丢失

Number() 函数将字符串转为数值,要比 parseInt() 函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为 NaN。

另外,parseInt() 和 Number() 函数都会自动过滤一个字符串前导和后缀的空格。

对象

简单的规则是:Number() 的参数是对象时,先按 hint "number" 转成原始值再 ToNumber。空数组、只含一个数字的数组、DatevalueOf 返回时间戳)都能得到有限数字;普通 {}、多元素数组通常是 NaN

若对象定义了 Symbol.toPrimitive,会优先调用它(见下文)。否则按下面顺序。

Number({})         // NaN
Number({a: 1})     // NaN
Number([1, 2, 3])  // NaN
Number([])         // 0
Number([5])        // 5
Number(new Date()) // 1653321770387

具体规则:

  • 调用对象自身的 valueOf() 方法。如果返回原始类型的值,则使用 Number() 函数转换该值。
  • 如果 valueOf() 方法返回的还是对象,则改为调用对象自身的 toString() 方法;如果 toString() 方法返回原始类型的值,则使用 Number() 函数转换该值。
  • 如果 toString() 方法返回的是对象,则报错。
const obj = {}
obj.valueOf()     // {}
obj.toString()    // '[object Object]'
Number('[object Object]') // NaN

另外,一个对象的 valueOf() 和 toString() 方法,是可以自定义的。

const obj = {
    valueOf: function () { return {}; },
    toString: function () { return {}; }
};
obj.valueOf()  // {}
obj.toString() // {}
Number(obj)    // Uncaught TypeError: Cannot convert object to primitive value

String()

String 是一个用于字符串或一个字符序列的构造函数,另外,还可以作普通函数使用,可以将任意类型的值转化成字符串。

原始类型值

  • 数值:转换为相应的字符串。
  • 字符串:返回原值。
  • 布尔值:true 转换为字符串 "true";false 转换为字符串 "false"。
  • undefined:转换为字符串 "undefined"。
  • null:转换为字符串 "null"。
  • Symbol:可以显式强制转换为字符串,然而隐式强制类型转换会产生错误。
  • BigInt:转为对应的十进制字符串,字面量末尾的 n 不会出现。
String(123)         // '123'
String('abc')       // 'abc'
String(true)        // 'true'
String(undefined)   // 'undefined'
String(null)        // 'null'
String(Symbol('a')) // 'Symbol(a)'
'a' + Symbol('a')   // Uncaught TypeError: Cannot convert a Symbol value to a string
String(BigInt(1))   // '1'

对象

String 函数的参数如果是对象,先按 hint "string" 转成原始值(优先 toString,见下文)。数组走 Array.prototype.toString,相当于 join(',')

String({a: 1})     // '[object Object]'
String({})         // '[object Object]'
String([1, 2, 3])  // '1,2,3'
String([])         // ''
String(new Date()) // 'Tue May 24 2022 00:03:00 GMT+0800 (中国标准时间)'

具体转换规则(有 Symbol.toPrimitive 则先走它):

  • 先调用对象自身的 toString()。如果返回原始类型的值,再交给 String()
  • 如果 toString() 返回的仍是对象,再调用 valueOf();若得到原始类型,再交给 String()
  • 如果 valueOf() 返回的还是对象,则抛 TypeError。

Boolean()

Boolean 对象是一个布尔值的对象包装器,也可以用作普通函数,将任意类型的值转为布尔值。

如果参数已经是布尔值,则原样返回。下列值转为 false,其余(含非空字符串、非 0 的数字、对象)转为 true

  • undefined
  • null
  • false
  • 0(含 -0+0)、0n
  • NaN
  • ''(空字符串)
Boolean(undefined) // false
Boolean(null)      // false
Boolean(false)     // false
Boolean(0)         // false
Boolean(0n)        // false
Boolean(NaN)       // false
Boolean('')        // false

注意: 普通对象(包括空对象、空数组)以及 Symbol 原始值转为布尔都是 true;连 new Boolean(false) 这种包装对象也是 trueSymbol 不是对象。

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
Boolean(Symbol('a'))        // true

注意: 有一个特殊的对象,它转换成的布尔值是 false。

document.all // HTMLAllCollection(9) [html, head, meta, meta, title, body, div, script, script, viewport: meta]
Boolean(document.all) // false

隐式类型转换

隐式类型转换是以强制类型转换为基础的。

以下三种情况时,JavaScript 会自动转换数据类型:

  • 不同类型的数据互相运算。

  • 对非布尔值类型的数据求布尔值。

  • 对非数值使用一元 +-,以及减、乘、除、取余等算术运算。

自动转换的规则:这个位置需要什么类型,就按那种类型去转。二元 + 只要有一边已经是字符串(或 ToPrimitive 后得到字符串),另一边就转成字符串;其余算术运算转成数值。

比如,'' + a 走字符串拼接;+aNumber(a)

由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用 Boolean()、Number() 和 String() 函数进行强制转换。

自动转换为布尔值

以下情况,JavaScript 会自动调用 Boolean() 函数:

  • if 语句的条件部分
  • for (;;) 语句中的条件判断表达式(第二个)
  • while ()do...while() 循环中的条件判断表达式
  • 三元表达式(?:)
  • 逻辑非运算符(!
  • 逻辑与 &&、逻辑或 || 判断条件时(返回值仍是原来的操作数,不是布尔值)

自动转换为字符串

以下情况,JavaScript 会自动调用 String() 函数:

字符串的加号运算符(+):当两个操作数,有一个是字符串,另一个为非字符串,则后者转为字符串。

'1' + 1               // '11'
'1' + true            // '1true'
'1' + false           // '1false'
'1' + {}              // '1[object Object]'
'1' + []              // '1'
'1' + function () { } // '1function () { }'
'1' + undefined       // '1undefined'
'1' + null            // '1null'

自动转换为数值

以下情况,JavaScript 会自动调用 Number() 函数:

除了二元加号 + 在碰到字符串时会走拼接,其余算术运算符都会把操作数转成数值。

'5' - '2'     // 3
'5' * '2'     // 10
true - 1      // 0
false - 1     // -1
'1' - 1       // 0
'5' * []      // 0
false / '5'   // 0
'abc' - 1     // NaN
null + 1      // 1
undefined + 1 // NaN

对象转基本类型

对象转基本类型有三种 hint(暗示)。规范规定:对象出现在需要原始值的场合(字符串拼接、alert、算术、比较等)时,会先转成原始值。

Hint

  • "string":需要字符串时,例如模板字符串、String(obj)alert(obj)
  • "number":需要数字时,例如一元 +、减乘除、关系运算符 <><=>=
  • "default":运算符不偏向某一种时,主要是二元 + 和宽松相等 ==。普通对象把 "default""number" 处理;Date"default""string" 处理。

为了实现转换,JavaScript 会按这个顺序:

  • 如果存在 Symbol.toPrimitive,所有 hint 都先走它。
  • 否则,hint 是 "string":先 toString(),再 valueOf()
  • 否则(hint 是 "number""default"):先 valueOf(),再 toString()

Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

const user = {
    name: "John",
    money: 1000,

    [Symbol.toPrimitive](hint) {
        console.log(`hint: ${hint}`);
        return hint == "string" ? `{name: "${this.name}"}` : this.money;
    }
};
alert(user); // hint: string
(+user);      // hint: number
(user + 500); // hint: default

唯一强制性的事情是:这些方法必须返回一个原始值,而不是对象。

toString()valueOf()

toString() 返回对象的字符串表示,valueOf() 返回指定对象的原始值。

如果没有 Symbol.toPrimitive,JavaScript 会找这两个方法:

  • hint 为 "string":先 toString(),没有再用 valueOf()
  • 其他 hint:先 valueOf(),没有再用 toString()

这些方法必须返回原始值。若返回的仍是对象,这次结果会被丢掉,当作该方法不存在,继续试下一个。两个都给不出原始值,就抛 TypeError。

默认情况下,普通对象带有:

  • toString():返回 "[object Object]"
  • valueOf():返回对象自身(不是原始值,因此对转换没有帮助)。

多数情况下,普通对象最终会落到 toString()

四种相等性判断

相等(==)

相等运算符(==)检查其两个操作数是否相等,并返回布尔值。

相等运算符(==)会尝试强制类型转换并且比较不同类型的操作数,转换规则如下:

  • 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回 true。

  • 如果一个操作数是 null,另一个操作数是 undefined,则返回 true。

  • 一个操作数是 undefinednull:另一个也是 nullundefined 则为 true;另一个是普通对象则为 false。例外是带 [[IsHTMLDDA]]document.all,它与 undefinednull== 都为 true

  • 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:

    • 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
    • 如果操作数之一是 Boolean,则 true 转换为1、false 转换为 0。
    • 如果操作数之一是对象,另一个是数字或字符串,会先把对象转成原始值(Symbol.toPrimitive,否则 valueOf / toString),再继续比较。
  • 如果操作数具有相同的类型,则将它们进行如下比较:

    • String: 仅当两个操作数具有相同顺序的相同字符时才返回 true。
    • Number:仅当两个操作数具有相同的值时才返回 true。+0 和 -0 视为相同的值,NaN 和任何值(包括 NaN 本身)返回 false。
    • Boolean:仅当操作数为两个 true 或两个 false 时才返回 true。

以下表格为不同类型一一对应的转换规则:

备注: 表格纵向为被比较值 A,横向为被比较值 B。ToNum(A) 表示把 A 转成数字;ToPrim(A) 表示把 A 转成原始值。普通对象与 undefinednull==falsedocument.all 除外,见表示例。

undefined null Number String Boolean Object
undefined true true false false false false
null true true false false false false
Number false false A===B A === ToNum(B) A === ToNum(B) A == ToPrim(B)
String false false ToNum(A) === B A===B ToNum(A) === ToNum(B) ToPrim(B) == A
Boolean false false ToNum(A) === B ToNum(A) === ToNum(B) A===B ToNum(A) == ToPrim(B)
Object false false ToPrim(A) == B ToPrim(A) == B ToPrim(A) == ToNum(B) A===B

一些特殊的比较:

document.all
// HTMLAllCollection(10) [html, head, meta, meta, title, style, body, div, a, script, viewport: meta]
console.log(document.all == undefined) // true
console.log(document.all == null)      // true

全等运算符(===)

全等运算符(===)会检查它的两个操作数是否相等,并且返回一个布尔值结果。与相等运算符不同,全等运算符总是认为不同类型的操作数不相等,也就是说,不会做隐式类型转换。

全等运算符(===)比较规则:

  • 如果操作数的类型不同,则返回 false。
  • 如果两个操作数都是对象,只有当它们指向同一个对象时才返回 true。
  • 如果两个操作数都为 null,或者两个操作数都为 undefined,返回 true。
  • 如果两个操作数有任意一个为 NaN,返回 false。
  • 否则,比较两个操作数的值:
    • 数字类型必须拥有相同的数值。+0 和 -0 会被认为是相同的值。
    • 字符串类型必须拥有相同顺序的相同字符。
    • 布尔值必须同时为 true 或同时为 false

同值相等

判断两个值在任何情况下是否功能上相同,用 Object.is()

与严格相等(===)只有两处不同:

  • +0 不等于 -0
  • NaN 等于自身。
Object.is(+0, -0)   // false
Object.is(NaN, NaN) // true

零值相等(SameValueZero)

与同值相等类似,但认为 +0-0 相等;NaNNaN 也相等。它既不是 ===NaN === NaN 为 false),也不是 Object.isObject.is(+0, -0) 为 false)。

SameValueZero 用于 Map、Set 的键或值比较,以及 Array.prototype.includes、TypedArray 的 includesString.prototype.includes 是子串查找,不用这套算法。

+0 === -0            // true,这是 ===,还看不出和 Object.is 的差别
[NaN].includes(NaN)  // true,includes 用 SameValueZero
NaN === NaN          // false

const map = new Map();
map.set(+0, 1)
map.get(+0) // 1
map.get(-0) // 1,同一条键

一些例子

行首的 { 在语句里是,不是对象。{} + {} 实际是空块加上一元 +{},结果是 NaN;写成 ({} + {}) 才是拼接两个对象。

![]     // false
+[]     // 0
+![]    // 0
[] + [] // ''
({} + {}) // '[object Object][object Object]'
{} + {}   // NaN。行首 {} 是空块,等价于 +{}
{} + []   // 0。等价于 +[]
{a:0} + 1 // 1。块里的标签 a,再加上 +1
[] + {}   // '[object Object]'
[] + ![]  // 'false'
![] + []  // 'false'
'' + {}   // '[object Object]'
{} + ''   // 0。等价于 +''
[]['map'] + [] // 以 'function map' 开头的函数源码字符串
[]['a'] + []   // 'undefined'
[][[]] + []    // 'undefined'
+!![] + []     // '1'
+!![]    // 1
1 - {}   // NaN
1 - []   // 1
true - 1 // 0
{} - 1   // -1。空块然后 -1
[] == ![]  // true。[] == false → 0 == 0

+((+![])+(+!![])+[]+(+![])) // 10
(![]+[])[+[]]   // 'f'。'false'[0]
(![]+[])[+!![]] // 'a'。'false'[1]

相关问题

(a==1 && a==2 && a==3) 能不能为 true?

可以。

因为 == 会做隐式转换。对象对上数字时,hint 是 "default",普通对象等价于先 valueOf()toString()(有 Symbol.toPrimitive 则先走它)。改这几个方法就能让同一次比较每次得到不同的原始值。

const a = {
    num: 0,
    valueOf: function () {
        return this.num += 1
    }
};
console.log(a == 1 && a == 2 && a == 3) // true
let num = 0;
Function.prototype.toString = function () {
    return ++num;
}
function a() { }
console.log(a == 1 && a == 2 && a == 3) // true
const a = {
    [Symbol.toPrimitive]: (function (i) {
        return function () { return ++i }
    })(0)
};
console.log(a == 1 && a == 2 && a == 3) // true

完善 Cash 打印三个 101

要求只能在 class 里面增加代码:

class Cash {}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a,b)},${new Cash(a+b)}`) // '101,101,101'

首先,三个输出都经过隐式转换。ab 是实例,new Cash(a + b) 说明构造函数也会接到 Cash 相加后的结果。若 toString 仍是默认的 '[object Object]',模板字符串对不上 101,所以要改 valueOf()toString()(或 Symbol.toPrimitive)。

class Cash {
    constructor(a) {
        this.m = a
        this.valueOf = function () { return a }
    }
    add(n) { return this.m + n }
    toString() { return this.m }
    static add(v1, v2) { return v1 + v2 }
}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a, b)},${new Cash(a + b)}`) // '101,101,101'

参考资料

阮一峰 - 数据类型的转换

MDN - JavaScript 中的相等性判断

MDN - 相等(==)

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-26 21:53:29

results matching ""

    No results matching ""