”你不知道的JavaScript“之细述类型转换

192 阅读8分钟

前言

类型转换算的上是JS中比较难理清楚的一个地方了,因为它涉及很多不同的情况,我们需要进行分类记忆,这样才能在面试或者开发中,把他吃的透透的。

在这里我们可以分为两个大类,一个是隐式类型转换类型,另一个是显示类型转换。在这里我们主要需要了解的是隐式的类型转换,因为它们在 JavaScript 中非常常见,并且可能会导致一些意想不到的行为。

隐式类型转换

隐式类型转换是指在某些情况下JS自动的将一个值从一个类型转换成另一个类型。在这里,我给大家罗列几种会触发隐式类型转换情况。

  1. 运算符操作:例如 +-*/== 等运算符。
  2. 条件判断:例如 if 语句、三元运算符等。
  3. 函数调用:例如 alert()console.log() 等。
  4. 对象与原始值之间的转换:例如在比较或字符串连接时。

+

相信大家都用到过 + 运算符,我们会发现如果两边是数字的话,他就是加法的效果,如果它两边不是数字,会触发什么样的效果呢?

我们可以分三种情况讨论。

  • 当两个操作数都是数字时,它执行数学加法。

    console.log(1 + 2); // 输出: 3
    
  • 如果其中一个操作数是字符串,另一个操作数会被转换成字符串,然后两个字符串被连接起来。

    console.log('1' + 2); // 输出: "12"
    console.log(1 + '2'); // 输出: "12"
    
  • 对于其他类型的值,如 nullundefined,它们首先会被转换为原始值(null 转换为 0undefined 转换为 NaN),然后再根据上下文决定是进行数学加法还是字符串连接。

    console.log(null + 1); // 输出: 1
    console.log(undefined + 1); // 输出: NaN
    console.log('Hello, ' + null); // 输出: "Hello, null"
    

个人感觉前面两种还是蛮容易理解的,重点需要记忆的是null他会变成0,然后undefined是NaN.

- ,*, /

然后剩余的减法运算符 (-)、乘法运算符 (*) 和除法运算符 (/)他们都总是将操作数转换成数字,执行数学操作。加法运算符是唯一一个在遇到非数值时可能不会执行数学加法的算术运算符!

  • 对于数字,它们直接执行数学运算。

    console.log(5 - 2); // 输出: 3
    console.log(4 * 2); // 输出: 8
    console.log(6 / 3); // 输出: 2
    
  • 对于非数字类型,它们会尝试将操作数转换为数字。布尔值 truefalse 分别转换为 10,字符串如果是有效的数字表示则转换为对应的数字,否则转换为 NaNnull 转换为 0undefined 转换为 NaN

    console.log(true - false); // 输出: 1
    console.log('10' * 5); // 输出: 50
    console.log('5.5' / '2.75'); // 输出: 2
    console.log('hello' - 1); // 输出: NaN
    console.log(null * 3); // 输出: 0
    console.log(undefined + 1); // 输出: NaN
    

==

== 和 === 的区别就是在于前者会进行隐式类型转换,后者是严格相等(不仅值要相等,类型也要) 所以我们来介绍一下 ==,我们可以根据等号两边类型是否一致来分类。

  1. 如果两个操作数是相同类型,直接比较它们的值:

    • 如果两者都是 null 或 undefined,则返回 true
    • 如果两者都是布尔值、数字或字符串,则直接比较它们的值。
    • 如果两者都是对象(包括数组和函数),则只有在它们引用同一个对象时才返回 true
  2. 如果两个操作数是不同类型,JavaScript 会尝试进行类型转换,然后再次比较:

    • 如果一个是 null 或 undefined,另一个也是 null 或 undefined,则返回 true
    • 如果一个是数字,另一个是字符串,则将字符串转换为数字后再进行比较。
    • 如果一个是布尔值,则将其转换为数字(true 转换为 1false 转换为 0),然后继续比较。
    • 如果一个是对象,另一个是原始值(如字符串、数字或布尔值),则将对象通过 [[ToPrimitive]] 方法转换为原始值,再进行比较。
    • 其他情况下,返回 false

条件判断中的类型转换

在条件判断中,JavaScript 会把非布尔类型的表达式转换成布尔值。以下是一些常见的假值,只有它们会被转换为 false,然后剩余的全是true,所以我们把这些假值记牢即可。

  • false
  • 0 (零)
  • -0 (负零)
  • 0n (BigInt 零)
  • "" 或 '' 或 `` (空字符串)
  • null
  • undefined
  • NaN

函数中的类型转换

其实这个就是我们天天用的 console.log(), 还有 alert()。这是因为这些函数在处理传递给它们的参数时,通常会将这些参数转换为字符串以进行显示或记录。

例如:

alert(42); // 显示: 42
alert(true); // 显示: true
alert({a: 1}); // 显示: [object Object]

在这个例子中,alert 函数接收各种类型的参数,并尝试将它们转换成字符串形式来展示给用户。对于对象,它使用默认的对象转字符串机制,这通常会导致 [object Object] 的输出,除非对象定义了自定义的 toString 方法。

同样的情况也适用于 console.log(),不过它的行为稍微复杂一些,因为它能够以更丰富的方式格式化和展示数据结构,比如对象和数组。尽管如此,当 console.log() 需要将值呈现为文本(例如,在某些环境中或者当值被强制转换为字符串时),它也会执行类似的隐式类型转换。


console.log(42); // 输出: 42
console.log(true); // 输出: true
console.log({a: 1}); // 输出: { a: 1 }

此外,如果你传递多个参数给 console.log(),它会在它们之间插入空格并输出所有参数,这也涉及到对非字符串参数的隐式类型转换。

上文中我们讲到了默认的对象转字符串机制,他就是Object.prototype.toString,除了他,js还有哪几种 toString 呢?加上他一共有三种。分别是:

  1. {}.toString()返回由"[object"和class和']'组成的字符串
  2. [].toString()返回由数组内部元素以逗号拼接的字符串
  3. xx.toString()直接返回字符串字面量

对象与原始值之间的转换

原始值到对象的转换

它通常称为装箱机制,即自动装箱。

自动装箱:当原始值被用作对象时,JavaScript 会创建一个对应的包装对象。例如,当你调用一个字符串的方法时,实际上是在临时创建一个 String 对象。

例如:

const bool = true;  
console.log(bool.valueOf()); // 输出: true

还有数字类型的:

const num = 42;
console.log(num.toString()); // 输出: "42"
console.log(num.toFixed(2)); // 输出: "42.00"

具体来说,当需要对原始值执行通常只有对象才有的操作时(例如调用方法或访问属性),JavaScript 会临时创建一个对应的包装对象。这个过程是自动的,通常发生在幕后,开发者不需要显式地进行操作。

对象到原始值的转换

我们需要了解 ToPrimitive(xhint)

[[ToPrimitive]] 是 JavaScript 中的一个内部方法,用于将对象转换为原始值(如字符串、数字或布尔值)。这个过程在多种情况下会发生,例如当你尝试将对象与原始值进行比较、执行算术运算、使用 + 运算符连接字符串和对象时等。[[ToPrimitive]] 方法接收一个提示参数 hint,指示期望的类型("string""number""default"),并根据这个提示来决定如何进行转换。

[[ToPrimitive]] 的工作流程

当需要将对象转换为原始值时,JavaScript 会按照以下步骤调用 [[ToPrimitive]]

  1. 检查 Symbol.toPrimitive:首先检查对象是否定义了 Symbol.toPrimitive 方法。如果有,则调用该方法,并传递 hint 参数。Symbol.toPrimitive 可以根据 hint 返回适当的原始值。

    
    const obj = {
      [Symbol.toPrimitive](hint) {
        if (hint === 'string') {
          return 'String value';
        } else if (hint === 'number') {
          return 42;
        } else {
          return 'Default value';
        }
      }
    };
    
    console.log(obj + ''); // 输出: String value
    console.log(+obj);     // 输出: 42
    console.log(!!obj);    // 输出: true (默认提示是 "default")
    
  2. 调用 valueOftoString:如果对象没有定义 Symbol.toPrimitive,则继续尝试调用 valueOftoString 方法:

    • 如果 hint 是 "number" 或 "default",首先调用 valueOf。如果 valueOf 返回一个原始值,则使用该值;否则,调用 toString
    • 如果 hint 是 "string",首先调用 toString。如果 toString 返回一个原始值,则使用该值;否则,调用 valueOf
    const obj = {
      valueOf: function() {
        return 100;
      },
      toString: function() {
        return 'String value';
      }
    };
    
    console.log(obj + ''); // 输出: String value
    console.log(+obj);     // 输出: 100
    console.log(!!obj);    // 输出: true (默认提示是 "default",优先调用 valueOf)
    
  3. 处理返回值:如果 valueOftoString 都返回了对象而不是原始值,则抛出 TypeError 错误。

    const obj = {
      valueOf: function() {
        return this; //返回当前对象
      },
      toString: function() {
        return this; //返回当前对象
      }
    };
    
    console.log(obj + ''); // 抛出 TypeError: Cannot convert object to primitive value
    

总的来说,[[ToPrimitive]] 是 JavaScript 中用于将对象转换为原始值的关键机制。它通过检查 Symbol.toPrimitivevalueOftoString 方法来决定如何进行转换,并根据上下文提供的 hint 参数选择最合适的转换方式!!

显式类型转换方法

显式类型转换是指开发者通过使用特定的函数或语法,明确地将一个值从一种类型转换为另一种类型,以确保代码的行为和意图清晰明了,比隐式类型转换简单多了。下面给大家简述一下。

1. 转换为布尔值

  • 使用 Boolean() 函数:

    
    Boolean(0); // false
    Boolean("hello"); // true
    

2. 转换为数字

  • 使用 Number() 函数:

    
    Number("123"); // 123
    Number("abc"); // NaN
    Number(true); // 1
    Number(false); // 0
    
  • 使用 parseInt() 和 parseFloat() 函数:

    
    parseInt("123px"); // 123
    parseFloat("123.45px"); // 123.45
    
3. 转换为字符串
  • 使用 String() 函数:

    
    String(123); // "123"
    String(true); // "true"
    String(null); // "null"
    
4. 转换为对象
  • 使用 Object() 函数:

    
    Object(123); // { [[PrimitiveValue]]: 123 }
    Object("hello"); // { [[PrimitiveValue]]: "hello" }
    

END

只有深入细化的分类了解JS的类型转换机制,你才能在开发和面试中临危不乱,避免一些常见的陷阱,写出完美的代码呀,作者也是为了面试不被面试官拷打,决定自己细细的整理一遍,真的受益良多,JS真是让人又爱又恨呀