加法类型转换
在JavaScript中,加法的规则其实很简单,只有两种情况:
所有其他类型的值都会被自动转换成这两种类型的值
在JavaScript中,一共有两种类型的值:
- 原始值有:
undefined、null、 布尔值(Boolean)、 数字(Number)、字符串(String)、Symbol - 对象值:其他的所有值都是对象类型的值,包括数组(arrays)和函数(functions)
❝基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。在 JavaScript 中,共有7种基本类型:string,number,bigint,boolean,null,undefined,symbol加法运算符会触发三种类型转换:将值转换为原始值、转换为数字、转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作:
❞
通过 ToPrimitive() 将值转换为原始值
ToPrimitive(input, PreferredType?)
可选参数PreferredType可以是Number或者String它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值。如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值:
- 否则,如果输入的值是一个对象。则调用该对象的
valueOf()方法。如果valueOf()方法的返回值是一个原始值,则返回这个原始值。 - 否则,调用这个对象的
toString()方法。如果toString()方法的返回值是一个原始值,则返回这个原始值。
如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换。如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置: Date类型的对象会被设置为String,其它类型的值会被设置为Number。
通过 ToNumber() 将值转换为数字
| 「参数」 | 「结果」 |
|---|
undefined | NaN |
null | +0 |
| |
| |
| 由字符串解析为数字。例如:"324"被转换为324 |
如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字。
通过 ToString() 将值转换为字符串
| 「参数」 | 「结果」 |
|---|
undefined | "undefined" |
null | "null" |
| "true" 或者"false" |
| |
| |
如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串。
减法类型转换
在JavaScript中,减法的规则核心是「统一转换为数字进行运算」,不存在“字符串减法”的情况——无论参与运算的值是什么类型,最终都会被转换为数字后再执行减法操作。
在JavaScript中,一共有两种类型的值:
- 原始值有:undefined、null、 布尔值(Boolean)、 数字(Number)、字符串(String)、Symbol
- 对象值:其他的所有值都是对象类型的值,包括数组(arrays)和函数(functions)
基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。在 JavaScript 中,共有7种基本类型:string,number,bigint,boolean,null,undefined,symbol
减法运算符仅触发两种类型转换:将值转换为原始值、转换为数字,这对应了JavaScript引擎内部的两种抽象操作:
❝注意:减法运算「不会触发ToString()」 转换,这是与加法最核心的区别(加法可能触发字符串拼接)。
❞
通过 ToPrimitive() 将值转换为原始值
ToPrimitive(input, PreferredType?) 可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值。
对于减法运算来说,无论输入值是否为Date类型,PreferredType都会被「强制设置为Number」(这也是与加法的关键差异:加法中Date类型的PreferredType为String),因此转换流程固定为:
- 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法。如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
- 否则,调用这个对象的toString()方法。如果toString()方法的返回值是一个原始值,则返回这个原始值。
通过 ToNumber() 将值转换为数字
减法运算中,所有原始值最终都会通过ToNumber()转换为数字,转换规则如下:
| |
|---|
| |
| |
| |
| |
| 由字符串解析为数字。例如:"324"被转换为324;"12a"被转换为NaN;空字符串""被转换为+0 |
| 抛出TypeError异常(Symbol无法转换为数字) |
如果输入的值是一个对象,则会首先调用ToPrimitive(obj, Number)将该对象转换为原始值,然后再调用ToNumber()将这个原始值转换为数字。
减法运算的具体执行示例
示例1:原始值之间的减法
// 数字 - 数字:直接运算10 - 5; // 5// 布尔值 - 数字:布尔值先转数字true - 1; // 1 - 1 = 0false - 2; // 0 - 2 = -2// 字符串 - 数字:字符串先转数字"100" - 20; // 100 - 20 = 80"12a" - 5; // NaN - 5 = NaN"" - 1; // 0 - 1 = -1// undefined/null 参与减法undefined - 1; // NaN - 1 = NaNnull - 5; // 0 - 5 = -5// Symbol 参与减法(报错)Symbol('a') - 1; // Uncaught TypeError: Cannot convert a Symbol value to a number
示例2:对象参与减法
// 数组参与减法:先ToPrimitive再ToNumber[10] - 3; // 10 - 3 = 7(数组valueOf返回自身,toString返回"10",转数字为10)[1,2] - 5; // NaN("1,2"转数字为NaN)[] - 2; // 0 - 2 = -2(空数组toString返回"",转数字为0)// 普通对象参与减法({}) - 10; // NaN(对象toString返回"[object Object]",转数字为NaN)({valueOf: () => 20}) - 5; // 20 - 5 = 15(自定义valueOf返回原始值)
示例3:特殊值的减法
// NaN参与减法:结果恒为NaN10 - NaN; // NaNNaN - NaN; // NaN// +0和-0的减法+0 - (-0); // 0-0 - +0; // 0
减法与相等性判断的关联
减法运算的类型转换逻辑,与相等性判断中==的转换逻辑高度一致(尤其是对象/原始值转换为数字的规则):
- 当使用
==比较数字与非数字类型时,非数字类型会先转数字(与减法的ToNumber规则相同); - 当对象参与
==比较时,会先通过ToPrimitive(obj, Number)转原始值(与减法的对象转换规则相同); - 严格相等
===不会触发任何隐式转换,这也是为什么在判断减法运算结果时,推荐使用===:
// 避免隐式转换导致的误判(10 - "0") === 10; // true(明确的数字比较)(10 - "0") == "10"; // true("10"转数字后比较)
总结
- JS减法运算的核心规则是「所有参与运算的值最终都会转换为数字」,不会触发字符串拼接(与加法本质区别)。
- 对象参与减法时,先通过ToPrimitive(obj, Number)转为原始值,再通过ToNumber()转为数字;Symbol类型参与减法会直接报错。
- 减法的类型转换规则与
==的转换逻辑一致,但判断减法结果时推荐使用===(严格相等)避免隐式转换误判。
相等性判断
ES2015中有四种相等算法:
- 严格相等比较 (
===): 用于 Array.prototype.indexOf, Array.prototype.lastIndexOf, 和 casematching - 同值零: 用于
%TypedArray% 和 ArrayBuffer构造函数、以及Map和Set操作, 并将用于 ES2016/ES7 中的String.prototype.includes
JavaScript提供三种不同的值比较操作:
- 严格相等比较 (也被称作“strict equality”, “identity”, “triple equals”),使用 === ,
- 抽象相等比较 (“loose equality”,“double equals”) ,使用 ==
- 以及 Object.is (ECMAScript 2015/ ES6 新特性)
严格相等===
全等操作符比较两个值是否相等,两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,这两个值是不全等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是 Number 类型时,两个值全等。最后,如果两个值都是 Number 类型,当两个都不是 NaN,并且数值相同,或是两个值分别为 +0 和 -0 时,两个值被认为是全等的。
在日常中使用全等操作符几乎总是正确的选择。对于除了数值之外的值,全等操作符使用明确的语义进行比较:一个值只与自身全等。对于数值,全等操作符使用略加修改的语义来处理两个特殊情况:第一个情况是,浮点数 0 是不分正负的,全等操作符认为这两个值是全等的。第二个情况是,全等操作符认为 NaN 与其他任何值都不全等,包括它自己。(等式 (x !== x) 成立的唯一情况是 x 的值为 NaN)
非严格相等==
一些总结规律:
undefined 与 null 互相相等,与其他值为falseNumber与String/Boolean比较时,后者将会被转化ToNumber;与对象进行比较时,后者将会被转化ToPrimitiveString与Number/Boolean比较时,均转换ToNumber;与对象比较时,后者将被转换ToPrimitiveBoolean与Number/String比较时,与上例相同Object与Number/String/Boolean比较时,前者将被转换ToPrimitive,Boolean被转换ToNumber
同值相等
由Object.is方法提供
JS 圣经图

JS 中的特殊值深度解析
在 JavaScript 中,null、undefined、NaN、+0/-0、Infinity 等特殊值是语言设计的重要组成部分,也是新手最容易混淆的知识点。这些值看似简单,但理解它们的「类型本质」、「转换规则」和「使用场景」,是写出健壮 JS 代码的关键。
一、核心特殊值的基础定义与类型
先明确每个特殊值的「身份」—— 类型归属是理解它们的第一步:
| | | |
|---|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | 「唯一标识」:ES6 新增,无字面量特殊值但本身是特殊类型 |
❝关键提醒:typeof null === "object" 是 JS 设计之初的历史 Bug(底层用 32 位存储值,前 3 位为 0 则判定为 object,null 的所有位都是 0),但 null 本质是「原始值」,不是对象。
❞
二、逐个拆解核心特殊值
1. undefined:「被动」的未定义
「核心特性」
- 变量声明但未赋值:
let a; console.log(a); // undefined - 函数无返回值时的默认返回:
function fn() {}; console.log(fn()); // undefined - 对象访问不存在的属性:
const obj = {name: '张三'}; console.log(obj.age); // undefined - 函数参数未传递:
function fn(x) {console.log(x);}; fn(); // undefined
- 转数字(ToNumber):
Number(undefined) // NaN - 转字符串(ToString):
String(undefined) // "undefined" - 转布尔值(ToBoolean):
Boolean(undefined) // false
undefined == null; // true(仅这一对特殊相等)undefined === null; // false(类型不同)undefined === undefined; // true
「最佳实践」
- 不要主动给变量赋值
undefined(比如 let a = undefined),如需表示「空」,优先用 null; - 判断变量是否未赋值:用
typeof a === "undefined"(避免变量未声明时报错)。
2. null:「主动」的空值
「核心特性」
- 手动表示变量无值:
let user = null; // 表示当前无用户数据 - 函数返回值表示“无结果”:
function findUser() { return null; } - 清空对象引用:
let obj = {a:1}; obj = null; // 释放引用
- 转数字(ToNumber):
Number(null) // 0 - 转字符串(ToString):
String(null) // "null" - 转布尔值(ToBoolean):
Boolean(null) // false
null == undefined; // truenull === undefined; // falsenull === null; // truenull == 0; // false(易错点:null转数字是0,但==不会把null转数字)
「关键区别(null vs undefined)」
3. NaN:非数字(Not a Number)
核心特性
- 字符串转数字失败:
Number("12a") // NaN、parseInt("abc") // NaN - 无效数学运算:
0 / 0、Math.sqrt(-1)、10 - "abc" - NaN 参与任何数字运算:
NaN + 10 // NaN、NaN * 5 // NaN
NaN === NaN; // false(易错点!)NaN == NaN; // false
- ES6 推荐:
Number.isNaN(值)(严格判断,仅当值是 NaN 且类型为 number 时返回 true); - 不推荐:
isNaN(值)(会先将值转数字,比如 isNaN("abc") // true,易误判)。
- 转字符串(ToString):
String(NaN) // "NaN" - 转布尔值(ToBoolean):
Boolean(NaN) // false
4. +0 / -0:正负零
「核心特性」
- 数学运算:
1 / Infinity // +0、-1 / Infinity // -0; - 类型转换:
Number("-0") // -0、null - 0 // +0;
+0 === -0; // true(=== 认为正负零相等)Object.is(+0, -0); // false(Object.is 严格区分)
- 「实际影响」: 大部分场景下正负零无区别,但涉及「符号敏感」的运算时会体现差异:
1 / +0; // Infinity1 / -0; // -InfinityObject.is(1/+0, 1/-0); // false
5. Infinity / -Infinity:无穷大
「核心特性」
- 超出数字范围:
1 / 0 // Infinity、-1 / 0 // -Infinity; - 大数运算:
Math.pow(10, 1000) // Infinity;
Infinity + 100; // InfinityInfinity - Infinity; // NaN(无效运算)100 / Infinity; // 0Infinity * 0; // NaN
三、特殊值的常见陷阱与避坑指南
陷阱 1:== 的隐式转换导致误判
// 易错案例undefined == 0; // false(undefined转数字是NaN,NaN≠0)null == 0; // false(null不会转数字参与==比较)"" == 0; // true(空字符串转数字是0)false == 0; // true(false转数字是0)
「避坑」:优先使用 ===(严格相等),仅在明确需要隐式转换时使用 ==。
陷阱 2:判断 NaN 用 ===
// 错误写法if (result === NaN) { ... } // 永远为false// 正确写法if (Number.isNaN(result)) { ... }
陷阱 3:typeof null 误判为 object
// 正确判断null的方法function isNull(value) { return value === null; // 直接用===,最可靠}
陷阱 4:Symbol 类型的特殊性
Symbol 作为原始值,无法转换为数字(会报错),转换为字符串需要手动调用 toString():
Symbol('a') - 1; // Uncaught TypeErrorString(Symbol('a')); // "Symbol(a)"Symbol('a').toString(); // "Symbol(a)"
四、特殊值的实用判断函数
整理了日常开发中常用的「特殊值判断」工具函数,可直接复用:
// 判断是否为undefinedfunction isUndefined(value) { return typeof value === 'undefined';}// 判断是否为nullfunction isNull(value) { return value === null;}// 判断是否为null/undefined(空值)function isNil(value) { return value === null || typeof value === 'undefined';}// 严格判断是否为NaNfunction isNaNValue(value) { return Number.isNaN(value);}// 判断是否为有效数字(排除NaN/Infinity/-Infinity)function isNumber(value) { return typeof value === 'number' && !Number.isNaN(value) && isFinite(value);}
总结
undefined 是「被动未定义」,null 是「主动空值」,二者 == 相等但 === 不相等,转数字结果分别为 NaN 和 0;NaN 是唯一不等于自身的值,判断需用 Number.isNaN(),而非 ===;+0/-0 在 === 下相等,但 Object.is() 严格区分,仅在符号敏感运算中体现差异;- 避坑核心:优先使用
=== 避免隐式转换,用专用方法判断特殊值(如 Number.isNaN())。