引言
JS中,类型转换这方面的规则十分复杂,但是又经常用到,一不小心就会掉入陷阱。今天抽空来梳理一下这方面的知识,在开始之前,我们先回顾下JS有多少种数据类型。
es6之前共有6种
-
原始类型(Primitive Types) :
number
: 数字类型,包括整数和浮点数。string
: 字符串类型。boolean
: 布尔类型,true
或false
。null
: 表示空值。undefined
: 表示未定义的值。
-
对象类型(Object Types) :
object
: 包括普通对象、数组、函数等。
强制类型转换
强制转换主要指使用Number
、String
和Boolean
三个函数,手动将各种类型的值,分布转换成数字、字符串或者布尔值。
Boolean()
原始类型转布尔
它的转换规则相对简单:除了以下六个值的转换结果为false
,其他的值全部为true
。
false
undefined
null
-0
或+0
NaN
''
(空字符串)
注意:什么都不传,默认是false
js
// Primitive -> Boolean
// 构造函数来用
console.log(Boolean()) // 什么都不传,默认是false
console.log(Boolean(false)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false
console.log(Boolean(+0),'+0') // false
console.log(Boolean(-0),'-0') // false
console.log(Boolean(NaN),'NaN') // false
console.log(Boolean(''),'空字符串') // false
补充说明:
+0
和 -0
是两种特殊的数字值,它们在大多数情况下表现一致,但在某些场景下会有区别。
- 做除法运算时
js
console.log(1 / +0) //Infinity 正无穷大
console.log(1 / -0) //-Infinity 负无穷大
- 使用
==
或===
比较时,+0
和-0
被认为是相等的,但可以使用Object.is()
准确检测。
js
console.log( +0 === -0) //true
console.log( +0 == -0) //true
console.log( Object.is(+0,-0)) //false
对象类型转布尔
对象转换为布尔值的规则非常简单:所有对象(包括数组、函数、普通对象等)在转换为布尔值时,都会被认为是 true
。这是因为 JavaScript 中的对象是"真值"(truthy)
注意:使用 new Boolean(false)
创建的包装对象是一个对象,因此会被转换为 true
。
js
console.log(Boolean(new Boolean(false))); // true
Number()
原始类型转数字
大致规则如下
原始数据类型 | 转换规则 |
---|---|
number |
直接返回原值。 |
string |
1.如果字符串是有效的数字,则转换为对应的数字。 2. 空字符串 "" 和空格字符" " 转换为 0 。 3.其他情况返回 NaN 。 |
boolean |
1.true 转换为 1 。 2.false 转换为 0 。 |
null |
转换为 0 。 |
undefined |
转换为 NaN 。 |
具体例子:
什么都不传,默认为0
js
console.log(Number()) // 什么都不传,默认是0
console.log(Number(123)) // 123
// undefined 在数值上下文中没有转成一个特定数字的含义,而是转成了NaN
console.log(Number(undefined),'undefined') // NaN
console.log(Number(null),'null') // 0
console.log(Number(false),'false') // 0
console.log(Number(true),'true') // 1
console.log(Number(''),'空字符串') // 0
console.log(Number('123'),'字符串123') // 123
console.log(Number('-123'),'字符串-123') // -123
console.log(Number(""),Number(" ")) // 0
console.log(Number("100a"),'字符串100a')// NaN
console.log(Number("-0")) // 0
console.log(Number("+0")) // 0
说明:
字符串转数字:
- 字符串开头和结尾的空白字符会被忽略。
- 忽略所有前导的 0
- 如果字符串包含非数字字符(除了开头的空白和正负号),则返回
NaN
。
js
console.log(Number(' 123 ')) // 123
console.log(Number('+123')) // 123
console.log(Number('-123')) // -123
console.log(Number('0x11')) // 17 16进制
console.log(Number('--123')) // NaN
console.log(Number('123abc')) // NaN
NaN
的特殊性:
NaN
是一个特殊的数字值,表示"不是一个数字"。- 任何与
NaN
的运算结果都是NaN
。 - 使用
isNaN()
或Number.isNaN()
可以检测一个值是否为NaN
。
parseInt()
使用:
parseInt
函数将字符串转为数值,要比Number()
函数宽松一点。
- 从字符串的开头开始解析,直到遇到无法识别为数字的字符为止。
- 忽略后续的非数字部分。
- 默认只解析整数(可以通过第二个参数指定进制)。
js
parseInt("123"); // 123
parseInt("123.45"); // 123 (只取整数部分)
parseInt("123abc"); // 123 (忽略后面的 "abc")
parseInt("abc123"); // NaN (开头不是数字或有效符号)
parseInt(" 123 "); // 123 (会自动去除首尾空格)
对象类型转数字
对象转 Number
的过程:
-
首先尝试通过
valueOf()
获取原始值,直接对该原始值使用Number
函数,不再进行后续步骤。。 -
如果
valueOf()
返回的不是原始值,再尝试通过toString()
获取原始值,也是使用Number
函数。 -
如果
valueOf()
和toString()
都返回的是对象,最终会报错。
valueOf()
方法的主要目的是返回对象的"原始值"表示。如果对象本身没有明确的原始值,则默认返回对象自身。
对于 原始值包装对象(如 Number、String、Boolean),valueOf() 返回对应的原始值。而普通对象 或 数组,valueOf() 默认返回对象自身。Date
对象是个例外,valueOf()
返回的是时间戳(以毫秒为单位的时间值)。
toString()
方法的主要目的是将对象转换为字符串表示形式。
- 普通对象 的
toString()
默认返回[object Object]
。 - 数组 的
toString()
返回元素的字符串化结果,用逗号连接。- 包含单个数值的数组不会在末尾添加逗号
undefined
和null
作为数组元素转换为空字符串""
- 函数 的
toString()
返回函数的源代码。 - 日期对象 的
toString()
返回可读的日期时间字符串。 - 原始值包装对象 的
toString()
返回原始值的字符串表示。 - 可以通过重写
toString()
方法来自定义对象的字符串化行为。
看晕了吗,没关系,我们通过具体例子来理解:
javascript
var obj = {x: 1};
Number(obj) // NaN
// 等同于
if (typeof obj.valueOf() === 'object') {
Number(obj.toString());
} else {
Number(obj.valueOf());
}
上面代码中,首先调用obj.valueOf
方法, 结果返回对象本身;于是,继续调用obj.toString
方法,这时返回字符串[object Object]
,对这个字符串使用Number
函数,得到NaN
。
之前说过,默认情况下,对象的valueOf
方法返回对象本身,所以一般总是会调用toString
方法,而toString
方法返回对象的类型字符串(比如[object Object]
)。所以,会有下面的结果。
js
Number({}) // NaN
如果toString
方法返回的不是原始类型的值,结果就会报错。
js
var obj = {
valueOf: function () {
console.log('Calling valueOf...');
return {};
},
toString: function () {
console.log('Calling toString...');
return {};
}
};
Number(obj)
// Calling valueOf...
// Calling toString...
// TypeError: Cannot convert object to primitive value
上面代码的valueOf
和toString
方法,返回的都是对象,所以转成数值时会报错,并且可以看到确实是先valueOf
,再toString
。
从上例还可以看到,valueOf
和toString
方法,都是可以自定义的。
javascript
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
上面代码对两个个对象使用Number
函数。第一个对象返回valueOf
方法的值,第二个对象返回toString
方法的值。
String()
原始类型转字符
原始类型转换为字符串的规则非常简单且直观。以下是 原始类型转换为字符串 的详细规则:
原始类型 | 转换规则 | 示例 | 转换结果 |
---|---|---|---|
number |
直接转换为对应的数字字符串。 | String(42) |
"42" |
string |
直接返回原值。 | String("hello") |
"hello" |
boolean |
- true 转换为 "true" 。 - false 转换为 "false" 。 |
String(true) String(false) |
"true" "false" |
null |
转换为 "null" 。 |
String(null) |
"null" |
undefined |
转换为 "undefined" 。 |
String(undefined) |
"undefined" |
注意:什么都不传,默认是""空字符。
javascript
console.log(String()) // 什么都不传,默认是""
toString()
方法
除了使用 String()
函数,原始类型(除了 null
和 undefined
)还可以通过调用 toString()
方法转换为字符串。
vbscript
let num = 42;
console.log(num.toString()); // "42"
let bool = true;
console.log(bool.toString()); // "true"
- 原始类型也能通过
.
调用方法? 实际上,JavaScript 中的其他原始类型(如Number
、String
、Boolean
)都有对应的包装对象。这些包装对象允许开发者创建相应的对象实例,并使用一些额外的方法和属性。
css
var a = 1.234
console.log(typeof a,a.toFixed(2)) //number 1.23
var b = new Number(a)
console.log(typeof b,b.toFixed(2)) //object 1.23
- 注意 :
null
和undefined
没有toString()
方法,调用会抛出错误,因为undefined
和null
都没有包装对象,它们纯粹是原始值。
对象类型转字符
String
方法背后的转换规则,与Number
方法基本相同,只是互换了valueOf
方法和toString
方法的执行顺序。
- 先调用对象自身的
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果
toString
方法返回的是对象,再调用原对象的valueOf
方法。如果valueOf
方法返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果
valueOf
方法返回的是对象,就报错。
具体例子:
scss
String({a: 1})
// "[object Object]"
// 等同于
String({a: 1}.toString())
// "[object Object]"
上面代码先调用对象的toString
方法,发现返回的是字符串[object Object]
,就不再调用valueOf
方法了。
如果toString
法和valueOf
方法,返回的都是对象,就会报错。
javascript
var obj = {
valueOf: function () {
console.log('Calling valueOf...');
return {};
},
toString: function () {
console.log('Calling toString...');
return {};
}
};
String(obj)
// Calling toString...
// Calling valueOf...
// TypeError: Cannot convert object to primitive value
可以看出确实是先toString
,再valueOf()
。
下面是通过自定义toString
方法,改变返回值的例子。
javascript
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
// "3"
上面代码对两个个对象使用String
函数。第一个对象返回toString
方法的值(数值3),第二个对象返回的还是toString
方法的值([object Object]
)。
原始类型转对象
之前提到了一下,对原始类型.
操作,会自动创建一个对应的包装对象,不需要我们手动操作。
原始值到对象的转换非常简单,原始值通过调用 String()、Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。
隐式类型转换
遇到以下三种情况时,JavaScript 会自动转换数据类型,即转换是自动完成的,用户不可见。
数学运算符
- 减、乘、除
我们在对各种非Number
类型运用数学运算符(- * /
)时,会先将非Number
类型转换为Number
类型。
仔细想一下,我们期望做的是数学运算,所以要得到数字类型来进行计算是理所应当的,就应该这么做。
javascript
1 - true // 0, 首先把 true 转换为数字 1, 然后执行 1 - 1
1 - null // 1, 首先把 null 转换为数字 0, 然后执行 1 - 0
1 * undefined // NaN, undefined 转换为数字是 NaN
2 * ['5'] // 10, ['5']首先会变成 '5', 然后再变成数字 5
之前说过,当数组只有一个值的时候,不会在末尾添加逗号,所以valueOf()
不行之后,toString()
得到了原始值'5'。
- 加法的特殊性
为什么加法要区别对待?因为JS里 +
还可以用来拼接字符串,所以结果变的十分复杂,我们直接上图。
js
console.log('hello' + 1) //hello1
console.log('hello' + undefined) //helloundefined
console.log('hello' + NaN) //helloNaN
console.log('hello' + null) //hellonull
console.log('hello' + 'world') //hello world
console.log('hello' + true) //hellotrue
console.log(1 + null) //1
console.log(1 + undefined) //NaN
console.log(1 + NaN) //NaN
console.log(1 + true) //2
console.log([]+ 1 ) //
function f(){}
console.log(f + 1) //function f(){}1
console.log([]+[]) //空字符
console.log({}+{})
console.log({
valueOf:function(){
return 1
},
}+{
valueOf:function(){
return 2
}
}) //3
console.log({
toString:function(){
return {}
},
}+{
toString:function(){
return {}
}
}) //TypeError: Cannot convert object to primitive value
逻辑语句中的类型转换
当我们使用 if
while
for
语句时,我们期望表达式是一个Boolean
,所以一定伴随着隐式类型转换。而这里面又分为两种情况:
1.单个变量
如果只有单个变量,会先变量转换为Boolean值。
arduino
if ('abc') {
console.log('hello')
} // "hello"
不过这里有个小技巧,我们之前都说明过:
只有 null
undefined
''
NaN
0
false
这几个是 false
,其他的情况都是 true
,比如 {}
, []
这些对象。
2.使用 == 比较中的6条规则
- 规则 1:
NaN
和其他任何类型比较永远返回false
(包括和他自己)。
js
NaN == NaN // false
- 规则 2:Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
js
true == 1 // true
true == '2' // false, 先把 true 变成 1,而不是把 '2' 变成 true
true == ['1'] // true, 先把 true 变成 1, ['1']toPrimitive成 '1', 再参考规则3
true == ['2'] // false, 同上
undefined == false // false ,首先 false 变成 0,然后参考规则4
null == false // false,同上
- 规则 3:
String
和Number
比较,先将String
转换为Number
类型。
js
123 == '123' // true, '123' 会先变成 123
'' == 0 // true, '' 会首先变成 0
- 规则 4:
null == undefined
比较结果是true
,除此之外,null
、undefined
和其他任何结果的比较值都为false
。
js
null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false
- 规则 5:
原始类型
和引用类型
做比较时,引用类型会依照ToPrimitive
规则转换为原始类型。
就像金翅小鹏王从四级境压制到道宫境与同为道宫境的叶凡一战。引用类型太强大了,并且一身傲骨,不屑于用境界压制原始人,自降境界与原始类型一较高下。
ToPrimitive
规则
ToPrimitive 是 JavaScript 中的一个内部操作,用于将对象转换为原始值(primitive value)。它的行为由一个 提示(hint) 决定,这个提示告诉 ToPrimitive 应该优先尝试哪种类型转换。常见的提示有以下三种:
"string": 表示希望得到一个字符串。
"number": 表示希望得到一个数字。
"default": 表示没有明确的偏好,默认行为因对象类型而异。大部分情况下跟
"number"
处理相同 ,但+
号运算符比较特殊:
- 如果
+
号的另一个操作数是字符串 ,则default
更倾向于"string"
,优先调用toString()
。- 否则,
default
和"number"
处理方式相同 ,优先调用valueOf()
,就比如==
。
如果没法得到一个原始类型,就会抛出TypeError
。
ini
'[object Object]' == {}
// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值
'1,2,3' == [1, 2, 3]
// true, 同上 [1, 2, 3]通过 toString 得到一个基本类型值
- 规则 6:
引用类型
和引用类型
做比较时,比较的是地址。
js
console.log([] == []) //false
function foo(){}
const bar = foo
console.log(foo == bar) //true
对非数值类型的值使用一元运算符
一元运算符也会把运算子转成数值。
js
console.log(+'abc') //NaN
console.log(-'abc') //NaN
console.log(+true) //1
console.log(+[])//0
console.log(+{})//NaN
最后看几道复杂的题
1 [1,2,3].map(parseInt)
提示:
js
console.log([1,2,3].map(item => parseInt(item))) //[ 1, 2, 3 ]
直接将 parseInt 作为 map 的回调函数时,map 会依次将当前元素的值、当前元素的索引和原数组传递给 parseInt。而 parseInt 函数接收两个参数:第一个参数是要解析的字符串,第二个参数是基数(可选,范围是 2 到 36)。
所以等价于:
c
console.log([1,2,3].map((item,index,array) => {
console.log(item,index,array)
return parseInt(item,index)
}))
- 答案是[ 1, NaN, NaN ]
2. [] == ![]
ini
- 第一步,![] 会变成 false
- 第二步,应用 规则2 ,题目变成: [] == 0
- 第三步,应用 规则5 ,[]的valueOf是[],toString是'',题目变成: '' == 0
- 第四步,应用 规则3 ,''=>0 题目变成 0 == 0
- 所以, 答案是 true
3. [undefined] == false
ini
- 第一步,应用 规则5 ,[undefined]通过toString变成 '',
题目变成 '' == false
- 第二步,应用 规则2 ,题目变成 '' == 0
- 第三步,应用 规则3 ,题目变成 0 == 0
- 所以, 答案是 true !
// 但是 if([undefined]) 又是个true!
写完才发现,我排版的有点乱哈,一些后面讲的知识,在前面就用到了,兄弟们多多包含。