将值从一种类型转换为另一种类型通常称为类型转换。
ES6 前,JavaScript 共有六种数据类型:
原始值(基本类型):Undefined、Null、Boolean、Number、String
引用值(引用类型):Object
我们先捋一捋基本类型之间的转换。

一、数据类型间的转换

原始值转布尔值

我们使用 Boolean 函数将类型转换成布尔类型,在 JavaScript 中,只有 6 种值可以被转换成 false,其他都会被转换成 true。

  1. console.log(Boolean()) // false
  2. console.log(Boolean(false)) // false
  3. console.log(Boolean(undefined)) // false
  4. console.log(Boolean(null)) // false
  5. console.log(Boolean(+0)) // false
  6. console.log(Boolean(-0)) // false
  7. console.log(Boolean(NaN)) // false
  8. console.log(Boolean("")) // false

注意,当 Boolean 函数不传任何参数时,会返回 false。

原始值转数字

我们可以使用 Number 函数将类型转换成数字类型,如果参数无法被转换为数字,则返回 NaN。
在看例子之前,我们先看 ES5 规范 15.7.1.1 中关于 Number 的介绍:
类型转换 - 图1
根据规范,如果 Number 函数不传参数,返回 +0,如果有参数,调用 ToNumber(value)
注意这个 ToNumber 表示的是一个底层规范实现上的方法,并没有直接暴露出来。
ToNumber 则直接给了一个对应的结果表。表如下:

参数类型 结果
Undefined NaN
Null +0
Boolean 如果参数是 true,返回 1。参数为 false,返回 +0
Number 返回与之相等的值
String 这段比较复杂,看例子

我们写几个例子验证一下

  1. console.log(Number()) // +0
  2. console.log(Number(undefined)) // NaN
  3. console.log(Number(null)) // +0
  4. console.log(Number(false)) // +0
  5. console.log(Number(true)) // 1
  6. console.log(Number("123")) // 123
  7. console.log(Number("-123")) // -123
  8. console.log(Number("1.2")) // 1.2
  9. console.log(Number("000123")) // 123
  10. console.log(Number("-000123")) // -123
  11. console.log(Number("0x11")) // 17
  12. console.log(Number("")) // 0
  13. console.log(Number(" ")) // 0
  14. console.log(Number("123 123")) // NaN
  15. console.log(Number("foo")) // NaN
  16. console.log(Number("100a")) // NaN

如果通过 Number 转换函数传入一个字符串,它会试图将其转换成一个整数或浮点数,而且会忽略所有前导的 0。
如果有一个字符不是数字,结果都会返回 NaN,鉴于这种严格的判断,我们一般还会使用更加灵活的 parseInt 和 parseFloat 进行转换。

parseInt 只解析整数,parseFloat 则可以解析整数和浮点数,如果字符串前缀是 "0x" 或者"0X",parseInt 将其解释为十六进制数,parseInt 和 parseFloat 都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。
如果第一个非空格字符是非法的数字直接量,将最终返回 NaN:

  1. console.log(parseInt("3 abc")) // 3
  2. console.log(parseFloat("3.14 abc")) // 3.14
  3. console.log(parseInt("-12.34")) // -12
  4. console.log(parseInt("0xFF")) // 255
  5. console.log(parseFloat(".1")) // 0.1
  6. console.log(parseInt("0.1")) // 0
  7. console.log(parseInt("abc 3")) // NaN
  8. console.log(parseFloat("abc 3")) // NaN
  9. console.log(parseInt('')) // NaN
  10. console.log(parseInt(' ')) // NaN
  11. console.log(parseFloat('')) // NaN
  12. console.log(parseFloat(' ')) // NaN

原始值转字符串

我们使用 String 函数将类型转换成字符串类型,依然先看 规范15.5.1.1中有关 String 函数的介绍:
类型转换 - 图2
如果 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value),而 ToString 也给了一个对应的结果表。表如下:

注意这里的 ToString 和上面的 ToNumber 都是底层规范实现的方法,并没有直接暴露出来。

参数类型 结果
Undefined “undefined”
Null “null”
Boolean 如果参数是 true,返回 “true”。参数为 false,返回 “false”
Number 0、-0、NaN、Infinity、-Infinity、正常数值看例子
String 返回与之相等的值

我们写几个例子验证一下

  1. console.log(String()) // 空字符串
  2. console.log(String(undefined)) // undefined
  3. console.log(String(null)) // null
  4. console.log(String(false)) // false
  5. console.log(String(true)) // true
  6. console.log(String(0)) // 0
  7. console.log(String(-0)) // 0
  8. console.log(String(NaN)) // NaN
  9. console.log(String(Infinity)) // Infinity
  10. console.log(String(-Infinity)) // -Infinity
  11. console.log(String(1)) // 1
  12. console.log(String(-1)) // -1

原始值转对象

原始值到对象的转换非常简单,原始值通过调用 String()Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。
nullundefined 属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError)异常,而不会执行正常的转换。

  1. var a = 1;
  2. console.log(typeof a); // number
  3. var b = new Number(a);
  4. console.log(typeof b); // object

对象转布尔值

对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为 true。
对于包装对象也是这样,举个例子:

  1. Boolean(new Boolean(false)) // true

对象转字符串和数字

对象到字符串和对象到数字的转换都是通过调用待转换对象的一个方法来完成的。

而 JavaScript 对象有两个不同的方法来执行转换,一个是 toString,一个是 valueOf。注意这个跟上面所说的 ToStringToNumber 是不同的,这两个方法是真实暴露出来的方法。

三个方法

下面我们先来了解 toString 和 valueOf,这两个方法。

toString

所有的对象除了 nullundefined 之外的任何值都具有 toString 方法,通常情况下,它和使用 String 方法返回的结果一致。
toString方法的作用在于返回一个反映这个对象的字符串,然而这才是情况复杂的开始。

Object.prototype.toString 方法会根据这个对象的[[class]]内部属性,返回由 "[object " 和 class 和 "]"三个部分组成的字符串。举个例子:

  1. Object.prototype.toString.call({a: 1}) // "[object Object]"
  2. ({a: 1}).toString() // "[object Object]"
  3. ({a: 1}).toString === Object.prototype.toString // true

我们可以看出当调用对象的 toString 方法时,其实调用的是 Object.prototype.toString 方法。
然而 JavaScript 下的很多类根据各自的特点,定义了更多版本的 toString 方法。例如:

  1. 数组的 toString 方法将每个数组元素转换成一个字符串,并在元素之间添加逗号后合并成结果字符串。
  2. 函数的 toString 方法返回源代码字符串。
  3. 日期的 toString 方法返回一个可读的日期和时间字符串。
  4. RegExp 的 toString 方法返回一个表示正则表达式直接量的字符串。 ```javascript var obj = { name: ‘konsoue’ } var arr = [1, 2, {name: ‘konsoue’ }]; function fn() {console.log(123)} var date = new Date(2022, 2, 9); var reg = /123\d+/;

obj.toString(); // [object Object] [].toString(); // 空字符串 [null].toString(); // 空字符串 arr.toString(); // 1,2,[object Object] date.toString(); // Wed Mar 09 2022 00:00:00 GMT+0800 (中国标准时间)’ reg.toString(); // /123\d+/

  1. <a name="hqWRH"></a>
  2. #### valueOf
  3. 默认的 `valueOf` 方法返回这个对象本身。
  4. 1. 数组、函数、正则简单的继承了这个默认方法,也会返回对象本身。
  5. 2. 日期是一个例外,它会返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。
  6. ```javascript
  7. var obj = { name: 'konsoue' }
  8. var arr = [1, 2, {name: 'konsoue' }];
  9. function fn() {console.log(123)}
  10. var date = new Date(2022, 2, 9);
  11. var reg = /123\d+/;
  12. obj.valueOf(); // obj 本身
  13. arr.valueOf(); // arr 本身
  14. fn.valueOf(); // fn 本身
  15. reg.valueOf(); // reg 本身
  16. date.valueOf(); // 1646755200000

内置的 ToPrimitive

ToPrimitive 方法:输入一个值,返回一个一定是基本类型的值。

  1. ToPrimitive(input[, PreferredType])

ToPrimitive函数的参数

  • 第一个参数是 input,表示要处理的输入值。
  • 第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。
    • 当不传入 PreferredType 时,
      • 如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。
      • 如果 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值

如果是 ToPrimitive(obj, Number),处理步骤如下:

  1. 如果 obj 为 基本类型,直接返回
  2. 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
  3. 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
  4. 否则,JavaScript 抛出一个类型错误异常。

如果是 ToPrimitive(obj, String),处理步骤如下:

  1. 如果 obj 为 基本类型,直接返回
  2. 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
  3. 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
  4. 否则,JavaScript 抛出一个类型错误异常。

对象转字符串

我们分析下从对象到字符串是如何转换的。
看规范 ES5 9.8,其实就是 ToString 方法的对应表,只是这次我们加上 Object 的转换规则:

参数类型 结果
Object 1. primValue = ToPrimitive(input, String)
2. 返回ToString(primValue).

总结一下 当我们用 String 方法转化一个值的时候,如果是基本类型,就参照 “原始值转字符串”的对应表, 如果不是基本类型,我们会将调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转字符串”的对应表进行转换。

对象转数字

其实,从对象到数字的转换也是一样:

参数类型 结果
Object 1. primValue = ToPrimitive(input, Number)
2. 返回ToNumber(primValue)。

虽然转换成基本值都会使用 ToPrimitive 方法,但传参有不同,最后的处理也有不同,转字符串调用的是 ToString,转数字调用 ToNumber

二、隐式转换

有很多情形会发生隐式类型转换,比如

  • +==
  • if? :&&!等情况,但相对来说,比较简单
    1. !xxx会将xxx隐式转换为Boolean(xxx),再进行运算!Boolean(xxx)
    2. if(xxx),也会将 xxx隐式转换为Boolean(xxx),再进行运算!Boolean(xxx)

    &&?:同理

⭐注意:switch case的判断是强等于 ===

1. 一元运算符 +

  1. console.log(+'1');

当 + 运算符作为一元操作符的时候,查看 ES5规范11.4.6,会调用ToNumber 处理该值,相当于 Number('1'),最终结果返回数字 1

那么下面的这些结果呢?

  1. console.log(+[]);
  2. console.log(+['1']);
  3. console.log(+['1', '2', '3']);
  4. console.log(+{});

既然是调用ToNumber方法,当输入的值是对象的时候,先调用ToPrimitive(input, Number) 方法,执行的步骤是:

  1. 如果 obj 为基本类型,直接返回
  2. 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
  3. 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
  4. 否则,JavaScript 抛出一个类型错误异常。 ```javascript // 调用了 ToPrimitive([], Number); // 1. 由于第二个参数的 Number,所以先执行 valueOf(),得到还是数组[],不是原始类型,不能返回 // 2. 执行 toString(),得到了空字符串,是原始类型,返回空字符串 // 3. 获得空字符串,由于一元运算符调用 ToNumber 方法,按照原始值转数字,得到 0 // 结果 0 console.log(+[]);

// 调用ToPrimitive([‘1’], Number); // 1. 由于第二个参数的 Number,所以先执行 valueOf(),得到还是数组[‘1’],不是原始类型,不能返回 // 2. 执行 toString(),得到了’1’,是原始类型,返回’1’ // 3. 获得’1’,由于一元运算符调用 ToNumber 方法,按照原始值转数字,得到 1 // 结果 1 console.log(+[‘1’]);

// 调用ToPrimitive([‘1’, ‘2’, ‘3’], Number); // 1. 由于第二个参数的 Number,所以先执行 valueOf(),得到还是数组[‘1’, ‘2’, ‘3’],不是原始类型,不能返回 // 2. 执行 toString(),得到了’1,2,3’,是原始类型,返回’1,2,3’ // 3. 获得’1,2,3’,由于一元运算符调用 ToNumber 方法,按照原始值转数字,得到 NaN // 结果:NaN console.log(+[‘1’, ‘2’, ‘3’]);

// 调用ToPrimitive({}, Number); // 1. 由于第二个参数的 Number,所以先执行 valueOf(),得到还是数组{},不是原始类型,不能返回 // 2. 执行 toString(),得到了 ‘[object Object]’,是原始类型,返回 // 3. 获得’[object Object]’,由于一元运算符调用 ToNumber 方法,按照原始值转数字,得到 NaN // 结果 NaN console.log(+{});

  1. <a name="EomSg"></a>
  2. ## 2. 二元运算符 +
  3. 现在 + 运算符又变成了二元操作符,毕竟它也是加减乘除中的加号<br />`1 + '1'` 我们知道答案是 `'11'`,那 `null + 1`、`[] + []`、`[] + {}`、`{} + {}` 呢?<br />如果要了解这些运算的结果,不可避免的我们要从规范下手。让我们根据 [规范 11.6.1 ](http://es5.github.io/#x11.6.1)来捋一捋:
  4. 当计算 `value1 + value2`时:
  5. 1. `lprim = ToPrimitive(value1)`
  6. 2. `rprim = ToPrimitive(value2)`
  7. 3. 如果 `lprim` 是字符串或者 `rprim` 是字符串,则返回 `ToString(lprim)` 和 `ToString(rprim)`的拼接结果
  8. 4. 其余的,返回 `ToNumber(lprim)` 和 `ToNumber(rprim)`的运算结果
  9. > 我们来举几个例子
  10. <a name="EzZQO"></a>
  11. ### null 和 数字
  12. ```javascript
  13. // 按照规则,我们一步步来
  14. // lprim = ToPrimitive(null) = null
  15. // rprim = ToPrimitive(1) = 1
  16. // 都不是字符串,执行 ToNumber
  17. // lprim = ToNumber(null) = 0
  18. // rprim = ToNumber(1) = 1
  19. // 所以结果:0 + 1 = 1
  20. console.log(null + 1);

数组和数组

  1. // 按照规则
  2. // lprim = ToPrimitive([], Number) = ''
  3. // rprim = ToPrimitive([], Number) = ''
  4. // 有一个是字符串,所以执行 ToString
  5. // lprim = ToString('') = ''
  6. // rprim = ToString('') = ''
  7. // 结果两个空字符串拼接:''
  8. console.log([] + [])
  9. // 同理,自己推算一下
  10. // '1231,[object Object]'
  11. console.log(['123'] + [1, {name: 'a' }]);

数组和对象

  1. // 按照规则
  2. // lprim = ToPrimitive([], Number) = ''
  3. // rprim = ToPrimitive({}, Number) = '[object Object]'
  4. // 有字符串,执行 ToString,然后拼接
  5. // '[object Object]'
  6. console.log([] + {});
  7. // 结果和上面一样
  8. console.log({} + []);

小离谱的事情
以上的运算都是在 console.log 中进行,如果你直接在 Chrome 或者 Firebug 开发工具中的命令行直接输入,你也许会惊讶的看到一些结果的不同,比如:类型转换 - 图3
我们刚才才说过{} + [] 的结果是 “[object Object]” 呐,这怎么变成了 0 了?
不急,我们尝试着加一个括号:
类型转换 - 图4
结果又变成了正确的值,这是为什么呢?
其实,在不加括号的时候,{} 被当成了一个独立的空代码块,所以 {} + [] 变成了 +[],结果就变成了 0

日期与数组

在内置的 ToPrimitive 中说过,日期类型进行类型转换时,传的参数是 String
这里举个例子,练习一下

  1. var date = new Date(2022, 2, 9);
  2. console.log(date + ['1']);
  3. // 按照规则
  4. // lprim = ToPrimitive(date, String) = 'Wed Mar 09 2022 00:00:00 GMT+0800 (中国标准时间)'
  5. // rprim = ToPrimitive(['1'], Number) = '1'
  6. // 都是字符串,执行 toString,然后拼接
  7. // 结果:'Wed Mar 09 2022 00:00:00 GMT+0800 (中国标准时间)1'

3. == 相等

== 用于比较两个值是否相等,当要比较的两个值类型不一样的时候,就会发生类型的转换。
关于使用==进行比较的时候,具体步骤可以查看规范11.9.5
当执行x == y 时:

  1. 如果 x 与 y 是同一类型:
    1. x 是 Undefined,返回 true
    2. x 是 Null,返回 true
    3. x 是 数字:
      1. x 是 NaN,返回 false
      2. y 是 NaN,返回 false
      3. x 与 y 相等,返回 true
      4. x 是 +0,y 是 -0,返回 true
      5. x 是 -0,y 是 +0,返回 true
      6. x 不等于 y,返回 false
    4. x 是字符串,完全相等返回 true,否则返回 false
    5. x 是布尔值,x 和 y 都是 true 或者 false,返回 true,否则返回 false
    6. x 和 y 指向同一个对象,返回 true,否则返回 false
  2. x 是 null 并且 y 是 undefined,返回true
  3. x 是 undefined 并且 y 是 null,返回true
  4. x 是数字,y 是字符串,判断x == ToNumber(y)
  5. x 是字符串,y 是数字,判断ToNumber(x) == y
  6. x 是布尔值,判断ToNumber(x) == y
  7. y 是布尔值,判断x ==ToNumber(y)
  8. x 是字符串或者数字,y 是对象,判断x == ToPrimitive(y)
  9. x 是对象,y 是字符串或者数字,判断ToPrimitive(x) == y
  10. 其余,返回 false

    是不是觉得很离谱,我们分类来学一下,逝世看

null 和 undefined

  1. // true
  2. console.log(null == undefined);
  3. console.log(undefined == null);

根据这个特性,出一道题。
编写判断对象的类型type函数时,如果输入值是undefined,就返回'undefined',如果是null,就返回字符串 null。

  1. function type(obj) {
  2. if(obj == null) {
  3. return obj + '';
  4. }
  5. }

字符串和数字

结果都是转成数字后,才比较。

  1. // 123 == ToNumber('123')
  2. // 结果:true
  3. console.log(123 == '123')
  4. // 1 == ToNumber('1a')
  5. // 结果:false
  6. console.log(1 == '1a')

布尔值和其他类型

根据这两条规则

  1. x 是布尔值,判断ToNumber(x) == y
  2. y 是布尔值,判断x == ToNumber(y)
    1. // ToNumber(true) = 0
    2. // 0 == '2'
    3. // ToNumber('2') = 2
    4. // 比较 0 == 2,不相等
    5. // 结果:false
    6. console.log(true == '2')
    因为布尔值这种特性,所以少用 xx == truexx == false。 ```javascript // 不建议 if (a == true) {}

// 建议 if (a) {} // 更好 if (!!a) {}

  1. <a name="Jd8yP"></a>
  2. ### 对象和非对象
  3. 根据规则
  4. 1. x 是字符串或者数字,y 是对象,判断`x == ToPrimitive(y)`
  5. 2. x 是对象,y 是字符串或者数字,判断`ToPrimitive(x) == y`
  6. ```javascript
  7. // ToPrimitive(['4']) = '4'
  8. // 故比较 4 == '4'
  9. // 4 == ToNumber('4')
  10. // 结果:true
  11. console.log(4 == ['4'])

== 的练习题

  1. // 有布尔值,布尔值进行 ToNumber
  2. // 0 == undefined
  3. // 按照规范 11.9.5,符合最后的其余情况,直接返回 false
  4. console.log(false == undefined)
  1. // 有布尔值,布尔值进行 ToNumber
  2. // 0 == [] 对象与数字比较,对象进行 ToPrimitive
  3. // 0 == '' 对象与字符串比较,字符串进行 ToNumber
  4. // 0 == 0
  5. // 结果:true
  6. console.log(false == [])
  1. // !运算符优先级高,先运算。
  2. // ![] = !Boolean([]) = !true = false
  3. // [] == false,布尔值进行 ToNumber
  4. // [] == 0,对象与数字,对象进行 ToPrimitive
  5. // 0 == 0
  6. // 结果:true
  7. console.log([] == ![])
  1. // 以下都返回 true
  2. console.log(false == "0")
  3. console.log(false == 0)
  4. console.log(false == "")
  5. console.log("" == 0)
  6. console.log("" == [])
  7. console.log([] == 0)
  8. console.log("" == [null])
  9. console.log(0 == "\n")
  10. console.log([] == 0)

Symbol.toPrimitive

上文我们讲了 ToPrimitive 是内置的方法,在 ES6 提供的 Symbol 中,提供了Symbol.toPrimitive,允许我们重写该内置方法。

  1. const obj = {}
  2. console.log(+obj) // NaN
  3. obj[Symbol.toPrimitive] = () => 5
  4. console.log(+obj) // 5
  1. // 判断是否为原始值
  2. const isPrimaryValue = (value) => {
  3. if (value === null) return true
  4. if (['object', 'function', 'symbol'].includes(typeof value)) return false
  5. return true
  6. }
  7. const variable = '123' // 可以自行改成其他类型,测试一下
  8. variable[Symbol.toPrimitive] = function () {
  9. // 1. 如果 variable 是日期类型
  10. // 先调用 toString,是原始值就返回,如果不是再调用 valueOf,是原始值就返回。如果不是报错
  11. if (this instanceof Date) {
  12. const toStringResult = this.toString()
  13. if (isPrimaryValue(toStringResult)) return toStringResult
  14. const valueOfResult = this.valueOf()
  15. if (isPrimaryValue(valueOfResult)) return valueOfResult
  16. return new TypeError()
  17. }
  18. // 2. 如果 variable 是 Undefined、Null、Boolean、Number、String 类型(原始值),直接返回该值
  19. if (isPrimaryValue(this)) return this
  20. // 3. 如果 variable 是其他类型
  21. const valueOfResult = this.valueOf()
  22. if (isPrimaryValue(valueOfResult)) return valueOfResult
  23. const toStringResult = this.toString()
  24. if (isPrimaryValue(toStringResult)) return toStringResult
  25. return new TypeError('variable is error')
  26. }
  27. console.log(+variable);

练习题

  1. // 结果:'a'
  2. [+[][0] + []][0][1]
  3. // 按照优先级,先运算+[][0]
  4. // 由于[][0]为 undefined,所以 +undefiend = Number(undefined) = NaN
  5. // 此时式子变成[NaN + []][0][1]
  6. // 按照优先级,先运算 NaN + [],左边是数字,右边是对象,对象 ToPrimitive
  7. // 运算 NaN + '' = 'NaN'
  8. // 此时式子变成['NaN'][0][1]
  9. // 'NaN'[1] = 'a'

参考资料

《JavaScript 深入之头疼的类型转换(上) 》
《JavaScript深入之头疼的类型转换(下) 》