JavaScript 学习笔记:类型转换
一、 基础知识与转换原理
JavaScript 将值从一种类型转换为另一种类型通常称为类型转换。在梳理具体的转换之前,我们需要先弄懂 JavaScript 在底层是如何看待和处理这些转换的。
1. 数据类型
-
基本类型(原始值):
Undefined、Null、Boolean、Number、String、(Symbol、BigIntES6+新增) -
:
JavaScript 将值从一种类型转换为另一种类型通常称为类型转换。在梳理具体的转换之前,我们需要先弄懂 JavaScript 在底层是如何看待和处理这些转换的。
基本类型(原始值):Undefined、Null、Boolean、Number、String、(Symbol、BigInt ES6+新增)
:
Object// 可以使用 typeof 查看基本类型(注意 null 的历史遗留 bug)
console.log(typeof undefined); // "undefined"
console.log(typeof true); // "boolean"
console.log(typeof 42); // "number"
console.log(typeof "text"); // "string"
console.log(typeof null); // "object" (历史遗留问题)
console.
在进行类型转换时,JavaScript 底层实际上会调用几个并没有直接暴露给开发者的规范方法:
ToNumber(value):将值转换为数字。
ToString(value):将值转换为字符串。
ToPrimitive(input, PreferredType):将对象转换为基本类型(非常关键)。
input:要处理的值。
PreferredType:期望转换的类型(Number 或 String)。如果不传,除了 Date 类型默认是 String 外,其他对象默认视为 Number。
处理步骤(以偏好 Number 为例):
若输入已经是基本类型,直接返回。
调用 valueOf(),若返回基本类型,则返回该值。
调用 toString(),若返回基本类型,则返回该值。
若都未拿到基本类型,抛出 TypeError。
(注:若偏好 String,则将步骤 2 和 3 对调,先调 toString(),再调 valueOf())。
(注:底层逻辑为引擎内部实现,无直接调用的 JS 代码,理解其步骤即可)
除了底层规范,对象本身自带了两个真实暴露出来的方法,用于向基本类型转换:
toString():返回反映这个对象的字符串。
普通对象调用 Object.prototype.toString,返回 "[object Object]"。
数组会将元素用逗号拼接。
函数返回源码字符串,Date 返回可读的时间字符串。
console.log({a: 1}.toString()); // "[object Object]"
console.log([1, 2, 3].toString()); // "1,2,3"
console.log([].toString()); // ""
console.log((function a
valueOf():返回对象的原始值。
默认返回对象本身(数组、函数、正则也是如此)。
Date 会返回时间戳(1970年以来的毫秒数)。
var obj = {a: 1};
console.log(obj.valueOf() === obj); // true (返回对象本身)
var arr = [1, 2];
console.log(arr.valueOf() === arr); // true (返回数组本身)
var date = new Date(2023, 0, 1)
规则:在 JS 中,只有 6 种值 会被转换为 false,其余全部转换为 true。
// 转为 false 的情况
console.log(Boolean()); // false (不传参默认 false)
console.log(Boolean(false)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean
Number() 的转换非常严格,如果包含非数字字符,往往会直接返回 NaN。
console.log(Number(undefined)); // NaN
console.log(Number(null)); // 0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
// 字符串转数字
console.log(
(补充:由于 Number() 太严格,开发中常使用 parseInt() 和 parseFloat())
console.log(parseInt("3 abc")); // 3 (尽力解析前面的数字)
console.log(parseFloat("3.14 a")); // 3.14
console.log(parseInt("a 3")); // NaN (第一个非空格字符就是非数字)
非常直观,基本就是直接套上引号。
console.log(String(undefined)); // "undefined"
console.log(String(null)); // "null"
console.log(String(true)); // "true"
console.log(String(0)); // "0"
console.log(String(
通过调用 new String(), new Number(), new Boolean() 可以将原始值包装为对象。
var num = 1;
var numObj = new Number(num);
console.log(typeof numObj); // "object"
console.log(numObj instanceof Number); // true
// 注意:null 和 undefined 无法包装为对象
// new Object(null) 或 new Object(undefined) 会返回一个空对象 {}
// 但如果在期望对象的属性访问中使用它们,会报错:
// console.log(null.toString()); // TypeError: Cannot read properties of null
规则:所有对象(包括数组、函数,甚至是 new Boolean(false) 这样的包装对象)转为布尔值都是 true。
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(function(){})); // true
// 包装对象永远是 true
var falseObj = new Boolean(false);
console.falseObj
调用底层 ToPrimitive(obj, String):先尝试 toString() 拿原始值,拿不到再去尝试 valueOf()。最后将原始值转成字符串返回。
console.log(String({a: 1})); // "[object Object]"
// 过程:{a: 1}.toString() 直接返回 "[object Object]" (是原始值,结束)
console.log(String([1, 2, 3])); // "1,2,3"
// 过程:[1, 2, 3].toString() 返回 "1,2,3" (是原始值,结束)
调用底层 ToPrimitive(obj, Number):先尝试 valueOf() 拿原始值,拿不到再去尝试 toString()。最后将拿到的原始值转成数字返回。
console.log(Number({})); // NaN
// 过程:
// 1. valueOf() 返回 {} (非原始值)
// 2. toString() 返回 "[object Object]" (原始值)
// 3. Number("[object Object]") -> NaN
console.log(Number([])); // 0
// 过程:
// 1. valueOf() 返回 [] (非原始值)
// 2. toString() 返回 "" (原始值)
// 3. Number("") -> 0
console.log(Number([1,
将 JS 值转为 JSON 字符串,规则有其独特性:
// 1. 基本等同于 String(),但 undefined 是例外
console.log(JSON.stringify(42)); // "42"
console.log(JSON.stringify(undefined)); // undefined (并非字符串 "undefined")
// 2. 包装对象会被自动拆包为对应的原始值
console.log(JSON.stringify([new Number(1), new String("false")
+作用:相当于显式调用 Number()(即触发底层 ToNumber 操作)。
console.log(+'1'); // 1
console.log(+[]); // 0 (ToPrimitive 转为 "",再 ToNumber("") -> 0)
console.log(+['1']); // 1 (ToPrimitive 转为 "1",再 ToNumber("1") -> 1)
console.log(+['1', '2']); // NaN (ToPrimitive 转为 "1,2",ToNumber("1,2") -> NaN)
console.log(+{})
+核心判定:获取两边原始值后,只要有一个是字符串,就执行拼接,否则执行加法。
// 既无字符串,转数字相加
console.log(null + 1); // 1 (0 + 1)
console.log(true + true); // 2 (1 + 1)
// 包含字符串,执行拼接
console.log([] + []); // "" ("" + "")
console.log([] + {}); // "[object Object]" ("" + "[object Object]")
console.
踩坑预警:
{} + []和({} + [])的区别
// 在浏览器控制台直接输入(作为语句执行):
> {} + []
0
// 原因:{} 被视为独立的代码块(不参与运算),实际执行的是一元操作符 +[] -> 0
// 加上圆括号(作为表达式执行):
> ({} + [])
"[object Object]"
// 原因:标准二元运算 "[object Object]" + ""
== 相等比较当 JavaScript 引擎执行 x == y 时,会触发抽象相等比较算法。引擎会严格按照以下顺序和条件进行类型转换与比较。如果在某一步完成了转换,引擎会带着转换后的新值,重新从头执行这套判定规则,直到得出最终的布尔值。
底层判定规则(按执行优先级顺序):
类型相同:退化为严格相等 如果 Type(x) 与 Type(y) 相同,则直接返回严格相等比较 x === y 的结果。
Null 与 Undefined 规则:返回 ture,null 和 undefined 与其他任何类型的值进行 == 比较时,均返回 false。不发生数字或字符串的隐式转换。
Number 与 String 规则:字符串与数字比较,字符串转换为数字。
包含 Boolean 的规则:只要有一方是布尔值,必须优先将布尔值转换为数字(true 转为 1,false 转为 0)。
Object 与 原始值(String/Number/Symbol)的规则:对象与基本类型比较,对象调用底层方法转换为原始值。
对象和对象:
true。false。默认兜底 如果不满足以上任何条件,返回 false。
// 示例 1:空字符串与 0
console.log("" == 0); // true
/* 推导:
1. 触发规则 3(String vs Number),执行 ToNumber(""),结果为 0。
2. 比较转变为:0 == 0。
3. 触发规则 1(类型相同),0 === 0 返回 true。
*/
// 示例 2:布尔值与字符串
console.log("0" == false); // true
/* 推导:
1. 触发规则 4(包含 Boolean),执行 ToNumber(false),结果为 0。
2. 比较转变为:"0" == 0。
3. 触发规则 3(String vs Number),执行 ToNumber("0"),结果为 0。
4. 比较转变为:0 == 0,返回 true。
*/
// 示例 3:数组与布尔值
===严格相等比较=== 遵循的是严格相等比较算法。该算法的核心特征是:绝对不进行任何类型转换。
底层判定规则:
类型不同:直接拒绝 如果 Type(x) 与 Type(y) 不同,直接返回 false。
特殊的 Number 规则:NaN 不等于自身;正零和负零严格相等。
基本类型:比较值:若二者的字符序列(String)、布尔状态(Boolean)或唯一标识(Symbol)完全一致,返回 true,否则返回 false。
引用类型(Object):比较内存地址 如果 x 和 y 均为 Object(包含数组、函数等),算法会检查它们是否引用堆内存中的同一个对象实例。
如果指向同一内存地址,返回 true。
如果指向不同内存地址(即使内容一模一样),返回 false。
根据上述推导规则,以下代码结果都是 true:
console.log([] == ![]); // true
/* 推导过程:
1. ! 优先级高,![] 是 false。变成了 [] == false
2. 包含布尔值 false,转数字 0。变成了 [] == 0
3. 对象与基本类型,[] 执行 ToPrimitive 变成 ""。变成了 "" == 0
4. 字符串与数字,"" 转数字 0。变成了 0 == 0 -> true
*/
console.log(false == "0"); // true (false转0, "0"转0 -> 0 == 0)
console.log(false == []); // true (false转0, []转""转0 -> 0 == 0)
console.log