学习链接

阮一峰:数据类型的转换

现代 JavaScript 教程:类型转换

现代 JavaScript 教程:对象 —— 原始值转换

数据类型转换

原始值之间

字符串转换

转换发生在输出内容的时候,也可以通过 **String(value)** 进行显式转换。原始类型值的 string 类型转换通常是很明显的。

数字型转换

转换发生在进行算术操作时,也可以通过 **Number(value)** 进行显式转换

数字型转换遵循以下规则:

变成……
undefined NaN
null 0
true / false 1 / 0
string “按原样读取”字符串,两端的空格会被忽略。空字符串变成 0。转换出错则输出 NaN。(是用 Number而非 parseInt

布尔型转换

转换发生在进行逻辑操作时,也可以通过 **Boolean(value)** 进行显式转换

布尔型转换遵循以下规则:

变成👇
0, null, undefined
, NaN, ""
false
其他值 true

值得注意的例子

  • undefined 进行数字型转换时,输出结果为 NaN,而非 0
  • "0" 和只有空格的字符串(比如:" ")进行布尔型转换时,输出结果为 true

对象转原始值

转换类型

  1. 没有转换为布尔值。所有的对象在布尔上下文(context)中均为 **true**。只有字符串和数字转换。
  2. 数字转换,例如数学运算
  3. 字符串转换,例如 alert() 的参数期望是字符串

hint

JavaScript 类型转换在各种情况下有三种变体。它们被称为 “hint”。

  • "string":对象到字符串的转换
  • "number":对象到数字的转换
  • "default":在少数情况下发生,当运算符“不确定”期望值的类型时。
    • 二元加法 **+** 可用于字符串连接,也可以用于数字相加,这时到底应该进行哪种转换不是很明确。
      将依据 "default" hint 来对其进行转换。
    • 如果对象被用于与字符串、数字或 symbol 进行 **==** 比较,这时到底应该进行哪种转换不是很明确。
      将依据 "default" hint 来对其进行转换。
    • <> 这样的小于/大于比较运算符,也可以同时用于字符串和数字。
      不过,它们使用 "number" hint,而不是 "default"。这是历史原因。

除了一种情况(Date 对象)之外,所有内建对象都以和 **"number"** 相同的方式实现 **"default"** 转换

Date 对象以和 "string" 相同的方式实现 "default" 转换。

转换算法

对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。

  1. 先调用 obj[Symbol.toPrimitive](hint)如果这个方法存在的话
  2. 否则,如果 hint 是 **"string"**
    按序尝试调用 obj.toString()obj.valueOf()
  3. 否则,如果 hint 是 **"number"****"default"**
    按序尝试调用 obj.valueOf()obj.toString()

所有这些方法都必须返回一个原始值才能工作(如果已定义)。

Symbol.toPrimitive

名为 Symbol.toPrimitive 的内建 symbol,被用来给转换方法命名

  1. obj[Symbol.toPrimitive] = function(hint) {
  2. // 这里是将此对象转换为原始值的代码
  3. // 它必须返回一个原始值 否则会报错
  4. // hint = "string"、"number" 或 "default" 中的一个
  5. }

如果 Symbol.toPrimitive 方法存在,则它会被用于所有 hint,无需更多其他方法。

toString/valueOf

如果没有 Symbol.toPrimitive,那么 JavaScript 将尝试寻找 toStringvalueOf 方法:

  • 对于 **"string"** hint:先调用 toString 方法,如果它不存在,则调用 valueOf 方法
    (因此,对于字符串转换,优先调用 toString
  • 对于其他 hint:先调用 valueOf 方法,如果它不存在,则调用 toString 方法
    (因此,对于数学运算,优先调用 valueOf 方法)

自定义这些方法时,应该返回一个原始值(string/boolean/number)。

如果 toStringvalueOf 返回了一个对象,那么返回值会被忽略,但是方法依旧会被执行

如果 toStringvalueOf 返回了一个 Symbol,直接报错。

默认情况下,普通对象具有 toStringvalueOf 方法:

  • toString 方法返回一个字符串 "[object Object]"
  • valueOf 方法返回对象自身。

测试用例

  1. const obj = {};
  2. obj.toString = () => {
  3. console.log('toString');
  4. // return {};
  5. return 'toString'
  6. }
  7. obj.valueOf = () => {
  8. console.log('valueOf');
  9. // return {};
  10. // return Symbol();
  11. return 'valueOf';
  12. }
  13. // obj[Symbol.toPrimitive] = (hint) => {
  14. // console.log('Symbol.toPrimitive');
  15. // console.log('hint: ', hint);
  16. // // return {};
  17. // return 'Symbol.toPrimitive';
  18. // }
  19. console.log('obj + ' + obj);

转换可以返回任何原始类型

关于所有原始转换方法,有一个重要的点需要知道,就是它们不一定会返回 “hint” 的原始值。

没有限制 toString() 是否返回字符串,或 Symbol.toPrimitive 方法是否为 "number" hint 返回数字。

唯一强制性的事情是:这些方法必须返回一个原始值(string/boolean/number),而不是对象

历史原因

由于历史原因,如果 toStringvalueOf 返回一个对象,则不会出现 error,但是这种值会被忽略(就像这种方法根本不存在)。这是因为在 JavaScript 语言发展初期,没有很好的 “error” 的概念。

相反,Symbol.toPrimitive 更严格,它 必须 返回一个原始值,否则就会出现 error。

进一步的转换

许多运算符和函数执行类型转换,例如乘法 * 将操作数转换为数字。

如果我们将对象作为参数传递,则会出现两个运算阶段:

  1. 对象被转换为原始值(通过前面我们描述的规则)。
  2. 如果还需要进一步计算,则生成的原始值会被进一步转换。

例如:

  1. let obj = {
  2. // toString 在没有其他方法的情况下处理所有转换
  3. toString() {
  4. return "2";
  5. }
  6. };
  7. alert(obj * 2); // 4,对象被转换为原始值字符串 "2",之后它被乘法转换为数字 2。
  1. 乘法 obj * 2 首先将对象转换为原始值(字符串 “2”)。
  2. 之后 "2" * 2 变为 2 * 2(字符串被转换为数字)。