JS 数据类型的转换机制是是学习 JS 过程中的一大难点,一度成为初学者面临的巨大挑战,这一复杂性往往源于 JS 的灵活性和动态性,使得在不同的上下文中,同一个值可能被解释为不同的数据类型。
本文将针从底层出发,借助官方文档,深度剖析 JS 类型转换机制。
JS 中的数据类型
首先我们回忆一下 JS 中的数据类型。
js
//基本数据类型
let num = 123 //数字
let str = 'hello world' //字符串
let flag = true //布尔
let un = undefined //未定义
let nu = null //空值
let bigint = 9007199254740991n //大整数
let sy = Symbol('symbol') //符号
//引用数据类型
var person = { //对象
name: "John",
age: 30,
isStudent: false,
hobbies: ["reading", "coding"]
};
var numbers = [1, 2, 3, 4, 5]; //数组
function foo() { //函数
return "你好!掘友";
}
//等等
类型转换
JS 类型转换主要分为两种:隐式类型转换 和显式类型转换。
1. 隐式类型转换:在隐式类型转换中,JS 引擎自动地将一种数据类型转换为另一种类型,通常发生在运算或比较的过程中。这种转换是隐式的,开发者不需要明确地进行操作,而是由 JS 引擎在必要的时候自动完成。
2. 显式类型转换:显式类型转换是由开发者明确指定的类型转换,通过调用相应的转换函数或使用一些特定的语法进行。这种转换是开发者有意识地进行的,用于确保数据在特定上下文中具有期望的类型。
看到这你可能还是一头雾水,举个例子:
js
var a = 12;
var b = a + ""; //隐式类型转换
var c = String(a); //显式类型转换
在这里,隐式强制类型转换发生在变量 a
被赋值给变量 b
的过程中。通过将 a
与一个空字符串 ""
相加,JS 引擎会自动将 a
的值转换为字符串,然后与空字符串拼接在一起。这是一种常见的将数字转换为字符串的方式,是隐式的,因为你并没有明确指示进行类型转换。
而显式强制类型转换通过调用 String()
函数来实现。你明确告诉 JS 引擎将变量 a
的值转换为字符串。这种方式更加明显,开发者有意识地进行了类型转换。
总体而言,这两种方式都能将数字 12
转换为字符串,但发生的时机和方式略有不同。隐式转换是在某些操作中自动进行的,而显式转换是由开发者明确调用的。
下面我们分别对这两种类型转换展开分析。
显示转换
我们通常使用官方定义的 Number()
、String()
、Boolean()
等函数进行显示转换,这些函数提供了明确的方式将值转换为特定的数据类型。
原始值转其他类型
1. 原始值转换成布尔值
js
console.log(Boolean()); // false
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean(-1)); // true
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean('123')); // true
console.log(Boolean('')); // false
console.log(Boolean(' ')); // true
见官方文档 9.2:Annotated ES5 9.2
简而言之:
false
、0
、''
、null
、undefined
以及NaN
转换为false
- 其他所有值转换为
true
2. 原始值转换成数字
js
console.log(Number('123')); //123
console.log(Number('abc')); //NaN
console.log(Number()); //0
console.log(Number(true)); //1
console.log(Number(false)); //0
console.log(Number(null)); //0
console.log(Number(undefined)); //NaN
见官方文档 9.3:Annotated ES5 9.3
大概的意思就是:
- 如果是数字字符串,则转换为对应的数字。
- 如果是非数字字符串或无法解析为数字的字符串,则转换为
NaN
。- 布尔值
true
被转换为1
,false
被转换为0
。null
被转换为0
。undefined
被转换为NaN
。
其中,对象转数字规则没有罗列,我们将在后文对 对象类型(Object)转数字类型进行着重分析。
3. 原始值转换成字符串
js
console.log(String()) //''
console.log(String(123)) //'123'
console.log(String(NaN)) //'NaN'
console.log(String(undefined)) //'undefined'
console.log(String(null)) //'null'
console.log(String(true)) //'true'
- 空值或没有提供参数时,返回空字符串。
- 数字转字符串:直接将数字转换为对应的字符串形式。
NaN
被转换为字符串'NaN'
。- 布尔值转字符串:
true
转换为"true"
,false
转换为"false"
。 null
转换为字符串"null"
。undefined
转换为"undefined"
。
见官方文档 9.8:Annotated ES5 9.8
4. 原始值转对象
原始数据类型可以通过其对应的包装对象来实现转换,例如:
js
console.log(Object(true)) //[Boolean: true]
console.log(Object(123)) //[Number: 123]
console.log(Object('123')) //[String: '123']
console.log(Object(undefined)) //{}
console.log(Object(null)) //{}
见官方文档 9.9:Annotated ES5 9.9
- 布尔值、数字、字符串分别被转换为对应的 Boolean、Number、String 对象。
- 对于
undefined
,使用Object()
转换为一个空对象{}
。- 对于
null
,同样使用Object()
转换为一个空对象{}
。
以上就是原始值转换数据类型的官方解释,我们不用过多考虑,只需要无脑接受就好。
当我们要用到的时候,查下这个表就行了。
因为官方没有给Null
和Undefined
定义显示转换的方法,所以我们暂且不聊。
然而以上这些内容都不是本文的重点,我们的重点是对象转换成原始值的过程,好戏才刚刚开始。
对象转原始值
对象转Null
与Undefined
没有意义,所以我们只聊对象转字符串、数字和布尔值。
1. 对象转数字
这时候我们再打开官方文档,回到之前铺垫的位置,如果转换的对象为对象类型的话,会进行红框中的操作。
大概意思是,你传入一个值,经过ToPrimitive
这样一个函数的调用并返回一个值,这个值会被转换成数字类型,我们可以通过文档(Annotated ES5 9.3)进入到ToPrimitive
这个函数内部。
看不懂,出国留学一下······
意思很明了,当你传入的是基本数据类型的时候,不会进行转换。唯独传入的是对象(Object)的时候才会有进行转换,它会调用对象的 [[DefaultValue]] 内部方法,于是我们跟随文档,跳到 8.12.8 。
翻译就不给大家看了,直接告诉大家它什么意思吧。
ToPrimitive(obj, Number)
是 ECMAScript 规范中描述的一种抽象操作,用于将给定对象 obj
转换为一个原始值,在进行数据转换的时候才会触发。这里我们目标是要将对象转换成数字,所以第二个参数是Number
,字符串则是String
。
下面是 ToPrimitive(obj, Number)
的执行步骤:
-
如果
obj
是基本类型:- 如果
obj
已经是基本类型(例如数字、字符串、布尔值等),那么直接返回obj
。
- 如果
-
否则,调用
valueOf
方法:- 如果
obj
不是基本类型,即它是一个对象,首先会尝试调用valueOf
方法。 - 如果
valueOf
方法存在并返回一个原始值,那么返回这个原始值。
- 如果
-
否则,调用
toString
方法:- 如果
valueOf
方法不存在,或者它返回的仍然是一个对象,那么接下来会调用toString
方法。 - 如果
toString
方法存在并返回一个原始值,那么返回这个原始值。
- 如果
-
否则,报错:
- 如果
toString
方法也不存在,或者它返回的仍然是一个对象,那么就无法将对象转换为原始值,此时会抛出一个错误。
- 如果
简化一下:
ToPrimitive(obj, Number) ==> Number({})
- 如果 obj 是基本类型,直接返回
- 否则,调用 valueOf 方法,如果得到原始值,则返回
- 否则,调用 toString 方法,如果得到原始值,则返回
- 否则,报错
这就相当于一个公式,当我们在将一个对象转换成数字类型的时候,直接套就行。
举个例子:
js
a = {}
console.log(Number(a)); //NaN
开始套公式:
-
如果
a
是基本类型:a
是一个对象,不是基本类型。
-
调用
valueOf
方法:- 尝试调用
valueOf
方法,看是否可以获取到原始值。 - 由于
a
是一个空对象{}
,而普通的对象(没有重写valueOf
方法的情况下)的valueOf
方法会返回对象本身,而不是一个原始值。
- 尝试调用
-
调用
toString
方法:- 由于
valueOf
方法没有返回原始值,接下来调用toString
方法。 - 对于普通的空对象
{}
,toString
方法通常会返回"[object Object]"
字符串。
- 由于
调用toString
方法的时候得到了原始值字符串"[object Object]"
,于是ToPrimitive
这个函数将其返回,最后就相当于 Number("[object Object]")
执行了这段代码。在 JS 引擎内部,ToPrimitive
这个函数相当于进行了将 Number(a)
转化成了 Number("[object Object]")
,相当于将字符串转换成数字,所以最终打印结果为 NaN
。
2. 对象转字符串
与对象转数字类似,唯一的区别就是转字符串时在ToPrimitive
函数中是先调用 toString
方法,后调用 typeOf
方法。
ToPrimitive(obj, String) ==> String({})
- 如果 obj 是基本类型,直接返回
- 否则,调用 toString 方法,如果得到原始值,则返回
- 否则,调用 valueOf 方法,如果得到原始值,则返回
- 否则,报错
与对象转数字用法一致,区别在于调用 toString
方法和 valueOf
方法的顺序,区分这一点就行了。
3. 对象转布尔
js
console.log(Boolean({})); // true
console.log(Boolean([])); // true
对象转换为布尔值时,结果总是 true
。
隐式转换
隐式转换通常发生在一些运算符操作中,其中一些运算符会触发 JS 引擎自动进行类型转换以满足操作的要求。下面我们简单聊一下 +
这个运算符。
一元运算 + a
官方文档(Annotated ES5 11.4.6)这样描述:
如果遇到 +
号,且它是当作一元运算符来使用,那么 +
号后面的数据一定要被强制转化为 Number
类型。
举个例子:
js
console.log(+'1'); //1
这个例子中, +
后面接的是数字字符串,则转换成对应数字,所以打印结果为1
,没问题。
那么 +
后面接的是对象类型呢,我们再举个例子:
js
console.log(+{}); //NaN
这个例子中, +
后面接的是一个空对象,那么对象怎么转换成Number
呢,是不是要走ToPrimitive
函数那一套哇,直接套公式就行了。
我直接搬过来:
ToPrimitive(obj, Number) ==> Number({})
- 如果 obj 是基本类型,直接返回
- 否则,调用 valueOf 方法,如果得到原始值,则返回
- 否则,调用 toString 方法,如果得到原始值,则返回
- 否则,报错
我们前面已经走过一遍流程了,这个ToPrimitive
函数最后会返回一个"[object Object]"
字符串,所以最终就相当于 +
后面接的是一个字符串,则转换成 NaN
,打印结果也为 NaN
,没问题。
我们也可以试着模拟一下整个过程:
js
// +{} 的过程模拟
console.log(+{}); //0
console.log({}.valueOf()); //{}
console.log({}.toString()); //"[object Object]"
//返回"[object Object]"字符串
console.log(Number("[object Object]")); //NaN
二元运算 a + b
官方文档(Annotated ES5 11.6.1)这样描述:
二元运算符 +
可以执行字符串连接,也可以执行数字加法。简而言之,将 +
左右两边的数据分别用ToPrimitive
函数调用一遍,如果返回值中有一个是字符串,则进行字符串拼接;否则进行加法运算。
即:
lprim + rprim == ToPrimitive(v1) + ToPrimitive(v2)
- 当 + 两边有一个是字符串,则按字符串进行拼接
- 否则,转到 number 进行计算
举个例子:
js
console.log(1 + '1'); //'11'
我们分别让数字1
和字符串'1'
走一遍ToPrimitive
,分别观察它们的返回值,发现'1'
是数字字符串,在 valueOf
和 toString
方法后返回的还是'1'
本身,即进行字符串拼接,得到打印结果'11'
。
当你理解透彻后很容易得到:
js
console.log(1 + 'null'); //1
console.log([] + []); //''
console.log([] + {}); //'[object Object]'
console.log({} + {}); //'[object Object][object Object]'
最后
当你读到这段话时,你已经跨过了JS类型转换这一座大山!
希望这篇文章能够为你提供帮助,如果你还有不懂之处,可以反复阅读,或者在评论区留言,学习是一个循序渐进,敢于试错的过程,我们顶峰相见!
我的Gitee: CodeSpace (gitee.com)
技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 "点赞 收藏+关注" ,感谢支持!!