== VS ===
在讲解之前我们先看一个有意思的东西,==和===,你说这俩有啥区别吗,不都是一样的用吗? a == 1和a === 1难道结果还不一样吗?很多情况下确实是没啥区别,但是你觉得官方会闲的没事打造两个作用一模一样的东西吗?
看个例子:
javascript
console.log(1 == '1');
console.log(1 === '1');
结果:

什么?怎么打印出来的结果不一样?这就是大名鼎鼎的类型转换。
前言
在 JavaScript 的世界里,类型转换就像个 "隐形工匠",时而帮我们简化代码,时而悄悄挖下陷阱。
先来一道面试题:
javascript
[] == ![];
如果你回答false,那么可以下一个问题了,因为答案是true。不是这玩意会是true?你别说还真是。这背后,就是 JS 里让人又爱又恨的隐式类型转换------ 它像个魔术师,悄咪咪把变量 "变" 成另一种类型,稍不注意就会 "翻车"~ 带着这个问题,我们开始今天的学习。
一、先搞懂:什么是 "类型转换"?
JS 里变量分两大阵营:
- 原始值:简单纯粹的基础类型(数字、字符串、布尔、undefined、null、Symbol、BigInt)
- 引用类型 :复杂的对象类型(对象
{}、数组[]、函数等,本质都是对象)
但有时候运算或者判断需要 "统一类型",这时候就会触发类型转换:
- 显式转换 :你主动喊它变(比如
Number(str)、String(num)) - 隐式转换 :JS 偷偷帮你变(比如
1 == '1'里,字符串'1'被偷偷转成了数字)
二、原始值转原始值 ------ 基础类型的 "内部变身"
原始值之间的转换,就像同一个班级里同学换座位,规则简单直接,没啥别的套路,全靠 3 个 "魔法公式":
1. 转数字:ToNumber(x)(对应显式Number(x))
把任意原始值变成数字:
javascript
// 数字→数字:不变
console.log(Number(123)); // 123
// 布尔→数字:true=1,false=0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
// 字符串→数字:纯数字字符串转对应数字,非数字字符串转NaN,空字符串是特例
console.log(Number('456')); // 456
console.log(Number('hello')); // NaN(非数字字符串直接翻车)
console.log(Number('')); // 0(空字符串是特例)
// undefined→NaN,null→0(记住这两个特殊规则)
console.log(Number(undefined)); // NaN
console.log(Number(null)); // 0

2. 转字符串:ToString(x)(对应显式String(x))
把原始值变成字符串,就是 "给值加引号" 的简单操作:
javascript
// 数字→字符串:直接套引号
console.log(String(789)); // 输出:"789"
// 布尔→字符串:true→"true",false→"false"
console.log(String(true)); // 输出:"true"
console.log(String(false)); // 输出:"false"
// undefined/null→固定字符串
console.log(String(undefined)); // 输出:"undefined"
console.log(String(null)); // 输出:"null"

3. 转布尔:ToBoolean(x)(对应显式Boolean(x))
原始值转布尔只有 "两极" :6 个假值 转false,其余全是true:
javascript
// 假值清单(记死!)
console.log(Boolean(0)); // false
console.log(Boolean('')); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean(false)); // false
// 其他全是真值
console.log(Boolean(1)); // true
console.log(Boolean(' ')); // true(空格字符串不是空字符串)
console.log(Boolean('abc')); // true

具体可看官方网页:es5.github.io/#x9.3
三、引用类型转原始值 ------V8 引擎的 "暗箱操作"
引用类型(对象、数组等)不能直接参与运算或者判断,V8 引擎会自发执行一套 "转换流程"(ToPrimitive方法),把它变成原始值再处理。
关键规则:转换目标决定调用顺序 (转数字和转字符串的流程不一样!),最终都要通过valueOf()或toString()获取原始值,失败就报错。
1. 引用类型转布尔:最简单的 "一刀切"
所有引用类型转布尔,不管内部是什么内容,全是 true!
javascript
// 空对象转布尔
console.log(Boolean({})); // true
// 空数组转布尔
console.log(Boolean([])); // true
// 有属性的对象转布尔
console.log(Boolean({ name: '张三' })); // true
// 有元素的数组转布尔
console.log(Boolean([1, 2, 3])); // true

2. 引用类型转数字:ToNumber(ToPrimitive(x, Number))
转换流程(重点记顺序!):
- 先调用引用类型的
valueOf()方法,若返回原始值,直接用这个值转数字; - 若
valueOf()返回的还是引用类型(比如对象本身),再调用toString()方法,用返回的字符串转数字; - 若
toString()也没返回原始值,直接报错(一般很少见)。
我用三个案例来深度解析一下:
案例1
javascript
// 案例1:数组[]转数字
const arr = [];
// 第一步:arr.valueOf() → 返回数组本身([],还是引用类型)
// 第二步:调用arr.toString() → 空数组toString()返回""(空字符串,原始值)
// 第三步:ToNumber("") → 0
// 最终结果:[]转数字是0
console.log(Number([])); // 0
console.log([] == 0); // true(因为[]转数字0,和右边0相等)

看完这个例子你是不是觉得很熟悉,没错这不就是我开头抛出的问题吗?
面试题
javascript
[] == ![]; // []转布尔→true
[] == !true; // !true→false
[] == false; // false转数字→0
[] == 0; // []转字符串''
'' == 0; // ''转数字→0,0==0
0 == 0; // 类型+值均相等,直接匹配

现在面试官的这个问题已经难不倒你了!
案例2
javascript
// 案例2:数组[1,2]转数字
const arr2 = [1,2];
// arr2.valueOf() → [1,2](引用类型)
// arr2.toString() → "1,2"(字符串)
// ToNumber("1,2") → NaN(非纯数字字符串)
console.log(Number([1,2])); // NaN

案例3
javascript
// 案例3:对象{}转数字
const obj = {};
// obj.valueOf() → {}(引用类型)
// obj.toString() → "[object Object]"(字符串)
// ToNumber("[object Object]") → NaN
console.log(Number({})); // NaN

3. 引用类型转字符串:ToString(ToPrimitive(x, String))
转换流程(和转数字相反,先调toString()):
- 先调用引用类型的
toString()方法,若返回原始值,直接用这个值作为最终字符串; - 若
toString()返回引用类型,再调用valueOf()方法,用返回的原始值转字符串; - 若还是没拿到原始值,报错。
我依旧用三个案例来深度解析:
案例1
javascript
// 案例 1:数组[1,2]转字符串
const arr3 = [1,2];
// 第一步:arr3.toString() → "1,2"(原始值)
// 直接返回这个字符串,不用走valueOf()
console.log(String([1,2])); // "1,2"
console.log([1,2] + "3"); // "1,23"(转字符串后拼接)

案例2
javascript
// 案例2:对象{}转字符串
const obj2 = {};
// 第一步:obj2.toString() → "[object Object]"(原始值)
// 直接作为结果
console.log(String({})); // "[object Object]"
console.log({} + [1,2]); // "[object Object]1,2"(先转字符串再拼接)

案例3
javascript
// 案例3:自定义对象的转换(更直观)
const user = {
name: 'henry',
// 重写toString(),让它返回自定义字符串
toString() {
return `我是${this.name}`;
},
// 重写valueOf(),看看转字符串时会不会用到(这里不会,因为toString()已经返回原始值)
valueOf() {
return { age: 20 };
}
};
console.log(String(user)); // "我是henry"(直接用toString()的结果)

四、"魔术现场":隐式转换什么时候触发?
知道了两大核心转换场景,再看隐式转换的触发时机,就一目了然了!
场景 1:==判断 ------ 跨类型比较必触发
==不会直接比较,而是先把两边转成同一类型(优先转数字),再比较。结合前面的转换规则。我举的面试题就是最好的例子。
场景 2:+运算 ------ 加法还是拼接,看转换结果
+是 "两面派",规则如下:
-
作为一元运算符(比如
+x):直接调用ToNumber(x)(原始值转数字,引用类型按转数字流程来); -
作为二元运算符(
a + b):- 先把 a 和 b 都转成原始值(
ToPrimitive(a)、ToPrimitive(b)); - 只要有一个原始值是字符串,就按字符串拼接来;
- 否则,两边都转数字做加法。
- 先把 a 和 b 都转成原始值(
javascript
// 案例 1:一元运算符 +[]
// []转数字是0,所以+a→0
console.log(+[]); // 0
// 案例 2:[1,2,3] + '1'
// [1,2,3]转原始值(按String规则)→"1,2,3"(字符串)
// 有字符串,所以拼接→"1,2,31"
console.log([1,2,3] + '1'); // "1,2,31"
// 案例 3:{} + 1
// {}转原始值(按Number规则)→toString()返回"[object Object]",转数字→NaN
// NaN + 1 → NaN
console.log({} + 1); // NaN
// 案例4:true + 1
// true转数字→1,所以1+1=2(没有字符串,做加法)
console.log(true + 1); // 2

场景 3:判断语句(if/while)------ 全转布尔
if(xxx)、while(xxx)里的条件,会自动把xxx转成布尔值(原始值按ToBoolean,引用类型转布尔全是 true):
javascript
// 案例 1:if({}) → 对象转布尔true,执行代码
if({}) {
console.log('对象转布尔是true'); // 会执行
}
// 案例 2:if([]) → 数组转布尔true,执行代码
if([]) {
console.log('空数组转布尔也是true'); // 会执行
}
// 案例 3:if('') → 空字符串转布尔false,不执行
if('') {
console.log('这句话不会输出');
}

其实还有很多,总的来说可分为两大类:
- 四则运算 :
+-*/% - 判断语句 :
ifwhile==>=<=><!=
五、toString () 方法:JS 里的 "格式转换器"
前面我们一直用到了toString () 方法,那么不同类型的返回值是不一样的:
-
对象
{}:调用 toString() 固定返回'[object Object]',例如 ({}).toString() →'[object Object]'(加括号避免被解析为代码块)。 -
数组
[]:返回内部元素用逗号拼接的字符串,例如 [1, 'a', true].toString() →'1,a,true',空数组 [].toString() →''。 -
原始值(数字、布尔、字符串等) :直接将值用引号包裹,例如 123.toString() →
'123'、true.toString() →'true'、'hello'.toString() →'hello'。
六、避坑小技巧:别让 "魔术" 坑了你
- 优先用
===代替==:===不会做任何隐式转换,必须 "类型 + 值" 都相等才返回 true,比如1 === '1'→false,避免踩坑; - 引用类型参与运算前,主动显式转换:比如想把数组转数字,直接写
Number([1,2].join('')),而不是靠隐式转换; - 记住核心转换规则:引用类型转数字 "先 valueOf 后 toString",转字符串 "先 toString 后 valueOf",转布尔全是 true;
- 警惕
NaN:引用类型转数字很容易得到NaN,NaN和任何值比较都是 false(包括NaN === NaN→false)。
结语
总结一下:JS 的类型转换看似复杂,其实核心就是 "原始值转原始值看 3 大规则,引用类型转原始值看 ToPrimitive 流程"。记住这些逻辑,再遇到[] == ![]、{} + [1,2]这类 "离谱" 代码,就能轻松拆解开背后的 "魔术套路" 啦!
具体规则可看:es5.github.io/