JavaScript中的数据类型转换
JavaScript中的数据类型转换是一个很容易造成困惑的地方。本文是《你不知道的JavaScript(中)》类型转换相关的知识整理,强烈建议去看这本书。
数据类型
变量类型包括
- 原始类型,包括Boolean、String、Number、Null、Undefined、Symbol、BitInt
- 引用类型,Object、Array、Function等
在某些时候,需要对数据的类型进行判断,常用的方法有
typeof
,可以得到undefined
、boolean
、number
、string
、object
、function
、symbol
等类型结果- 注意对于数组、null和object而言,typeof都会返回
'object'
- 注意对于数组、null和object而言,typeof都会返回
instanceof
,用于实例和构造函数的对应,其工作机制是:检测右侧构造函数的prototype
属性是否出现在左侧实例对象的原型链上。Object.prototype.toString.call(xxx)
将会得到一个包含变量类型的字符串
显式类型转换
首先我们需要了解JS中一些显式的类型转换
ToString
ToString
负责非字符串到字符串的强制类型转换
对于基础类型
null
转换为'null'
undefined
转换为'undefined'
true
转换为'true'
,false
转换为'false'
[]
转换为''
,{}
转换为'{}'
- 数字遵循通用规则转换,极大值或极小值使用指数形式
对于对象而言,
- 如果未实现
toString
,则返回[object Object]
;如果实现了toString
,则调用该方法并使用返回值
const a = {
toString() {
return 'custom tostring'
}
}
const b = {}
console.log(`${a}`) // custom tostring
console.log(`${b}`) // [object Object]
- 数组对象默认自定义了
toString
,会将所有单元字符串化之后再使用逗号,
连接起来。这里的一个特例是数组中null与undefined的元素会被字符串化为空字符串''
,而不是上面的普通类型转换规则jsconsole.log([1,null,2].toString()) // '1,,2'
ToNumber
ToNumber
定义了将非数字转换为数字的规则
基本类型的转换
true
转换为1,false
转换为0undefined
转换为NaN
,null
转换为0''
、'\n'
、' '
等空白字符时会返回0,其余数字常量的字符串会转换为对应数字,转换失败则返回NaN
- '0'开头的十六进制数字字符串是按照十进制进行转换
对象首先会被ToPrimitive
转换成对应的基本值,如果返回的是非数字的基本类型,再应用上面的规则转换为数字
ToBoolean
布尔值只有真假两个值,因此JS中的值转成布尔值的话,也分为两类,
- 可以被强制转换为
false
的值,包括undefined
、null
、false
、+0
、-0
、NaN
和空字符串''
- 其他被强制转换为
true
的值- 注意对象(包括包装对象)永远为真值
ToPrimitive
可以参考:
在 JavaScript 中,ToPrimitive
是一种类型转换操作,它用于将对象转换为原始值。这个操作在涉及到对象与原始值之间的比较、算术运算以及模板字符串插值等情况下非常重要。ToPrimitive
的工作机制包括调用对象的内置方法来尝试获得一个原始值。
具体来说,ToPrimitive
操作可以分为以下几步:
*. 尝试调用 valueOf
方法:
- 首先,尝试调用对象的
valueOf
方法。如果返回一个原始值(string
、number
、boolean
、symbol
、bigint
),则返回该值。 - 如果
valueOf
返回一个对象,则继续执行下一步。
*. 尝试调用 toString
方法:
- 如果
valueOf
方法没有返回原始值,接下来尝试调用对象的toString
方法。如果返回一个原始值,则返回该值。 - 如果
toString
也返回一个对象,则会抛出类型错误(TypeError
)。
ToPrimitive
操作还可以接受一个可选的 hint
参数,以指定希望的转换类型。hint
可以是 "number"
、 "string"
或"default"
。根据 hint
的不同,ToPrimitive
操作的优先级会有所不同:
- 当
hint
为"number"
时,ToPrimitive
操作优先调用valueOf
方法。 - 当
hint
为"string"
时,ToPrimitive
操作优先调用toString
方法。
hint会根据当前值参与的运算,自动推断出具体的值,表示当前语境传入的值(或者说是表示这是一个什么类型的运算),下面是一个简单的例子来演示 valueOf
和toString
的操作:
let obj = {
valueOf() {
return 42;
},
toString() {
return "Hello";
}
};
console.log(+obj); // 目标是转成数字,所以hint为number,调用valueOf,输出 42
console.log(`${obj}`); // 目标是转成字符串,所以hint为string,调用toString,输出 "Hello"
当前语境对于数据的转换有决定性的作用,比如下面的代码
console.log(obj + ''); // '42'
console.log(`${obj}`); // 'Hello'
就是因为传入的hint不同,导致得到了不同的结果;我们可以编写一个代码来测试不同的运算中传入的hint值,
var a = {
[Symbol.toPrimitive](hint) {
console.log(hint)
// 这里也可以返回原始值,作为toPrimitive操作的结果
}
}
console.log(+a) // hint为number
console.log(a + '') // hint为default
console.log(`${a}`) // hint为string
console.log(a + 1) // hint为default
var o = {}
o[a] = 'xx' // hint为string
Symbol.toPrimitive
是一个特殊的对象方法名,将在toPrimitive
的过程中被优先调用
- 我们创建的字面量对象
{}
和[]
不包含Symbol.toPrimitive
方法,且valueOf
方法返回的是自身,仍旧为引用值 - 但是
Date
对象实现了默认的Symbol.toPrimitive
方法,且返回的返回的是date.toString
的值
==相等判断
判断两个值是否相等时很常见的,JS中提供了==
与===
两个判断相等的运算符:“== 允许在相等比较中进行强制类型转换,而 === 不允许。”
那么在==
中如何进行类型转换呢?JS内部有一系列比较复杂的规则。
类型相同
如果两个值的类型都是基础类型,且类型相同时,仅比较他们的值是否相等,但是要注意特例
- NaN不等于NaN
+0
等于-0
当二者均为对象类型时时,比较的是内存地址而不是具体值是否相等
console.log([] == []) // false, 数组地址不同
console.log([1,2] == [1,2]) // false, 数组地址不同
console.log({} == {}) // false,对象地址不同
类型不同
如果两个值的类型不同,则会发生强制类型转换,会将其中之一或者二者转换成同一类型后再继续比较
在x == y
时,ES5 规范 11.9.3.4-5 这样定义:
数字 vs 字符串
规则1:二者有一个为数字,另一个为字符串,则将字符串转换成数字类型
- 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果
- 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果
// 返回100 == 100的结果,类型相同,比较值,返回true
'100' == 100 // true
// 首先ToNumber('100x')返回NaN,然后 NaN == 100都为数字类型,比较值,返回false
'100x' == 100 // false
布尔值 vs 其他类型
规则2:二者有一个为布尔值,另一个为其他类型时,会先将布尔值转换成数字,然后再进行==
比较
- 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果
- 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果
// 首先将a转换成数字1,此时计算 1 == '100',应用上面的规则1,将字符串转换成数字,返回 1 == 100的结果
true == '100' // false
// 基于上面的特点,永远不要使用下面的写法
if(a == true) {}
null vs undefined
null
表示一个对象是“没有值”的值,也就是值为“空”;undefined
表示一个变量声明了没有初始化(赋值);
规则3:null与undefined之间的比较
- 如果 x 为 null,y 为 undefined,则结果为 true。
- 如果 x 为 undefined,y 为 null,则结果为 true。
- 除此之外其他任何值都与这两个值不相等
null == undefined // true
undefined = true // true
null == true // false
null == false // false
null == '' // false
null == 0 // false
undefined == true // false
undefined == false // false
undefined == '' // false
undefined == 0 // false
其他 vs 对象
规则4:对象与非对象之间的对比
- 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
- 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPrimitive(x) == y 的结果。
- 如果基础值是布尔值,会先应用规则3,然后再进行上面的ToPrimitive操作
// 首先[42]调用ToPrimitive转换为基础值
42 == [42] // true
建议
- 如果两边的值中有 true 或者 false,千万不要使用 ==。
- 如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。
还有一些常见的坑
// []转换为'',而{}转换为[object Object],因此这里是字符串相加
[] + {}; // "[object Object]"
// 这里{}会被当做一个空的代码块,因此等价于 +[],会将[]转换为数字,
// 因此调用ToPrimitive返回空字符串, 然后将空字符串转ToNumber换为数字0
{} + []; // 0
伪代码
下面用伪代码演示了==
的运算规则
function looseEqual(x, y) {
// 步骤 1
if (Type(x) === Type(y)) {
return strictEqual(x, y);
}
// 步骤 2
if (x == null && y == null) {
return true;
}
// 步骤 3
if (Type(x) === 'Number' && Type(y) === 'String') {
return looseEqual(x, ToNumber(y));
}
if (Type(x) === 'String' && Type(y) === 'Number') {
return looseEqual(ToNumber(x), y);
}
// 步骤 4
if (Type(x) === 'Boolean') {
return looseEqual(ToNumber(x), y);
}
if (Type(y) === 'Boolean') {
return looseEqual(x, ToNumber(y));
}
// 步骤 5
if ((Type(x) === 'String' || Type(x) === 'Number' || Type(x) === 'Symbol' || Type(x) === 'BigInt') && Type(y) === 'Object') {
return looseEqual(x, ToPrimitive(y));
}
if ((Type(y) === 'String' || Type(y) === 'Number' || Type(y) === 'Symbol' || Type(y) === 'BigInt') && Type(x) === 'Object') {
return looseEqual(ToPrimitive(x), y);
}
return false;
}
在实际生产中,除了极少数特殊的场景之外,建议都使用===
进行严格的相等判断。
+ 加法
参考:MDN文档
加法运算符(+)为两种不同的运算重载:数字加法和字符串连接。
在求值时,它首先将两个操作数强制转换为基本类型。然后,检查两个操作数的类型:
- 如果有一方是字符串,另一方则会被转换为字符串,并且它们连接起来。
- 如果双方都是 BigInt,则执行 BigInt 加法。如果一方是 BigInt 而另一方不是,会抛出 TypeError。
- 否则,双方都会被转换为数字,执行数字加法。
console.log([] + []); // '' + [] -> '' + '' -> ''
console.log({} + []); // '[object Object]'+ '' -> '[object Object]'
console.log([] + {}); // '[object Object]'
console.log({} + {}); // '[object Object][object Object]'
小结
JavaScript的数据类型转换是开发和面试中经常遇见的问题,稍有不慎就容易犯错,本文主要整理类型转换的一些规则,用以备忘。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。