类型转换
将值从一种类型转换为另一种类型通常称为类型转换。
JavaScript 的类型转换既简单又复杂,从 JavaScript 诞生之日起一直是饱受争议的话题。
截止写稿日期,JavaScript 共有八种数据类型,分别是:Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
今天,我们一起来学习字符串、数字和布尔值之间类型转换的基本规则。ES5 规范第 9 节中定义了一些“抽象操作”(即“仅供内部使用的操作”)和转换规则,这里主要介绍 ToString(英文/中文)、ToNumber(英文/中文) 和 ToBoolean(英文/中文),附带介绍一下 ToPrimitive(英文/中文)。
ToString
它负责处理非字符串到字符串的强制类型转换。转换规则见下表:
参数类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | 如果参数是 true,那么结果为 "true"。 如果参数是 false,那么结果为 "false"。 |
Number | 这个比较复杂,见下面例子 |
String | 返回输入的参数(不转换) |
Object | 分两步走: 1、primValue = ToPrimitive(input, String)。 2、返回ToNumber(primValue)。 |
传入 Number 类型时,输出结果:
var negative_0 = -0
var negative_Infinity = -Infinity
var a = 3.14 * 10000 * 10000 * 10000 * 10000 * 10000 * 10000
console.log(String(NaN)) // NaN
console.log(String(negative_0)) // 0
console.log(String(0)) // 0
console.log(String(Infinity)) // Infinity
console.log(String(negative_Infinity)) // -Infinity
console.log(String(a)) // 3.14e24
console.log(String(10)) // 10
至于传入 Object 类型时,输出结果的分析,我们等介绍完 ToPrimitive 后再细说,这里简单提及两个:
- 对普通对象来说,除非自行定义,否则
toString()
返回内部属性 [[Class]] 的值。如果对象本身有定义 toString() 方法,那么字符串化时就会调用该方法并使用其返回值。
// demo1
var o = { name: 'xiaoming' }
console.log(o.toString()) // [object Object]
// demo2
var o = {
name: 'xiaoming',
toString: function () {
return 'type string'
}
}
console.log(o.toString()) // type string
- 数组默认的
toString()
方法经过了重新定义,将所有单元字符串化后再用“,”连接起来。toString()
可以被显示调用,或者在需要字符串化时自动调用。
var a = [1, 2, 3]
console.log(a.toString()) // 1,2,3
JSON.stringify
JSON.stringify()
方法在将 JSON 对象序列化为字符串时也用到了 ToString
,所以 JSON 字符串化并非严格意义上的强制类型转换,只是其中也涉及到 ToString
的相关规则,因此这里顺带扩展下。
对大多数简单值来说,JSON 字符串化和 toString()
的效果基本相同,只不过序列化的结果总是字符串。见下面例子:
console.log(JSON.stringify(43)) // 43
console.log(JSON.stringify('43')) // "43"
console.log(JSON.stringify(null)) // null
console.log(JSON.stringify(undefined)) // undefined,打印结果不是字符串的 undefined
console.log(JSON.stringify(false)) // false
JSON.stringify()
在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在数组中则会返回 null 以保证单元位置不变。
console.log(JSON.stringify([1, undefined, function(){}, 4])) // [1, null, null, 4]
console.log(JSON.stringify({ a: 1, b: function(){} })) // {"a":1}
对包含循环引用的对象执行 JSON.stringify()
会报错。
var o = {}
var b = {
a: 2,
c: o,
d: function() {},
e: b // 循环引用对象 b
}
JSON.stringify(b) // 报错
遇到上面情况时,可以自定义 toJSON()
方法来返回一个安全的 JSON 值。
// 代码接上
b.toJSON = function () {
return { b: 'hello world' }
}
JSON.stringify(b) // {"b":"hello world"}
提示
这里的 toJSON 返回的是一个适当的值,可以是任何类型,然后再由 JSON.stringify()
方法对其进行字符串化。也就是说,toJSON()
返回一个能够被字符串化的安全的 JSON 值。
举个例子:
var o1 = {
v: 1,
toJSON: function () {
return v
}
}
var o2 = {
v: 1,
toJSON: function () {
return v + ''
}
}
console.log(JSON.stringify(o1)) // 1
console.log(JSON.stringify(o2)) // "1" 可能不是理想结果(字符串基础上再添加 "")
接下来我们介绍下 JSON.stringify
的第二个参数 replacer
。
如果该参数是一个函数,它可以携带两个参数,分别是键(key)和值(value),则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
举个例子:
var a = {
b: 2,
c: '3',
d: [1, 2, 3]
}
console.log(JSON.stringify(a, ['b', 'c'])) // {"b":2,"c":"3"}
console.log(JSON.stringify(a, function (k, v) {
if (k !== 'c') {
return v
}
})) // {"b":2,"d":[1,2,3]}
接下来我们介绍下 JSON.stringify
的第三个参数 space
。
指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
该参数用来控制结果字符串里面的间距。如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);如果是一个字符串,则每一级别会比上一级别多缩进该字符串(或该字符串的前10个字符)。
举个例子:
var a = {
b: 2,
c: '3',
d: [1, 2, 3]
}
console.log(JSON.stringify(a, null, '----'))
// {
// ----"b": 2,
// ----"c": "3",
// ----"d": [
// --------1,
// --------2,
// --------3
// ----]
// }
ToNumber
根据规范 15.7.1.1 中的定义,如果给 Number()
方法提供参数,那么返回值将由 ToNumber(value)
计算的 Number 值,否则返回 +0。
而 ToNumber
的转换规则见下表:
参数类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 如果参数是 true,那么结果为 1。 如果参数是 false,那么结果为 +0。 |
Number | 结果等于输入的参数(不转换)。 |
String | 这个比较复杂,见下面例子。 |
Object | 分两步走: 1、primValue = ToPrimitive(input, Number)。 2、返回ToNumber(primValue)。 |
传入 String
类型时,输出结果:
console.log(Number()) // +0
console.log(Number(undefined)) // NaN
console.log(Number(null)) // +0
console.log(Number("100")) // 100
console.log(Number("-100")) // -100
console.log(Number("0100")) // 100
console.log(Number("-0100")) // -100
console.log(Number("bar")) // NaN
console.log(Number("100a")) // NaN
分析:如果 Number
函数传入一个字符串,它会优先转换为数值类型,而且忽略前导零,如果字符串出现一个非数字字符,那么结果返回 NaN。
ToPrimitive
为了将对象转换为相应的基本类型值,抽象操作 ToPrimitive (参考规范 9.1)会首先 (通过内部操作 DefaultValue,参考规范 8.12.8)检查该值是否有 valueOf()
方法,如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()
方法的返回值来进行强制类型转换。根据规则,调用顺序与转换字符串或数字有关。
如果 valueOf()
和 toString()
均不返回基本类型值,会产生 TypeError 错误。
其中 toString
方法是除了 null 和 undefined 之外,所有对象都具有的。它是返回该对象的字符串 "[object type]"
,type 表示对象的类型。
举个例子:
var o = new Object()
console.log(o.toString()) // [object Object]
不过有些对象定义了不同版本的 toString
方法,具体见下面例子:
var o1 = {}
var o2 = [1, 2, 3]
var o3 = function () {
var v = 1
}
var o4 = /\d+/g
var o5 = new Date(2021, 5, 7)
console.log(o1.toString()) // [object Object]
console.log(o2.toString()) // 1,2,3
console.log(o3.toString()) // function (){var v = 1;}
console.log(o4.toString()) // /\d+/g
console.log(o5.toString()) // Mon Jun 07 2021 00:00:00 GMT+0800 (中国标准时间)
另一个 valueOf
方法是返回指定对象的原始值。默认返回该对象本身,数组、函数、正则简单继承了这个方法。Math
和 Error
对象没有 valueOf
方法。Date
对象的 valueOf
方法返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。
var date = new Date(2021, 5, 7)
console.log(date.valueOf()) // 1622995200000
让我们回到前面提到的对象(obj
)转字符串(String
)和数字(Number
)的处理步骤。
对象转字符串处理步骤如下:
- 调用
toString
方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。 - 调用
valueOf
方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。 - JavaScript 抛出异常。
对象转数字处理步骤如下:
- 调用
valueOf
方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。 - 调用
toString
方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。 - JavaScript 抛出异常。
ToBoolean
我们直接来看它的转换规则表:
参数类型 | 结果 |
---|---|
Undefined | false |
Null | false |
Boolean | 结果等于输入的参数(不转换)。 |
Number | 如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。 |
String | 如果参数是空字符串(其长度为零),结果为 false,否则结果为 true。 |
Object | true |
注意一个比较特别的例子:
console.log(Boolean()) // false
结语
JavaScript 的类型转换规则比较难记忆,希望这篇文章可以帮助你加深印象,也方便日后忘记了可以回来查阅。
本文参考《你不知道的JavaScript中卷》