类型转换

将值从一种类型转换为另一种类型通常称为类型转换。

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 后再细说,这里简单提及两个:

  1. 对普通对象来说,除非自行定义,否则 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
  1. 数组默认的 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 的转换规则见下表:

参数类型结果
UndefinedNaN
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 方法是返回指定对象的原始值。默认返回该对象本身,数组、函数、正则简单继承了这个方法。MathError 对象没有 valueOf 方法。Date 对象的 valueOf 方法返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。

var date = new Date(2021, 5, 7)
console.log(date.valueOf()) // 1622995200000

让我们回到前面提到的对象(obj)转字符串(String)和数字(Number)的处理步骤。

对象转字符串处理步骤如下:

  1. 调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。
  2. 调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。
  3. JavaScript 抛出异常。

对象转数字处理步骤如下:

  1. 调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。
  2. 调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回,否则走下一步。
  3. JavaScript 抛出异常。

ToBoolean

我们直接来看它的转换规则表:

参数类型结果
Undefinedfalse
Nullfalse
Boolean结果等于输入的参数(不转换)。
Number如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。
String如果参数是空字符串(其长度为零),结果为 false,否则结果为 true。
Objecttrue

注意一个比较特别的例子:

console.log(Boolean()) // false

结语

JavaScript 的类型转换规则比较难记忆,希望这篇文章可以帮助你加深印象,也方便日后忘记了可以回来查阅。

本文参考《你不知道的JavaScript中卷》