一文让你搞懂JavaScript隐式转换

2,734 阅读8分钟

前言

JavaScript是一门动态弱类型的语言,在我们判断1 == true时,若是java由于两变量类型不同所以会之间判断为false,但是对JavaScript来说这里涉及到了隐式类型转换,Boolean类型的值true经过隐式转换变成了Number类型的值1,所以判断结果为true。下面本文将会详细讲解JavaScript隐式转换规则

装箱和拆箱

装箱

装箱意为将原始类型转换为引用类型,即创建包装类型

对包装类型不了解的可以阅读我的上一篇文章# 特殊的引用类型——包装类型

拆箱

拆箱意为将引用类型转换为原始类型,在拆箱过程中会遵循ECMAScript规定的toPrimitive原则,对引用类型进行转换

toPrimitive函数

什么是toPrimitive函数

toPrimitive方法是在引用类型转换为原始类型的过程中js自动帮我们调用的方法

toPrimitive方法中会按条件调用[Symbol.toPrimitive];valueOf();toString()中的一种或多种方法帮助我们将引用类型转换为原始类型

toPrimitive函数执行的一般顺序
  1. 若存在[Symbol.toPrimitive]属性,则优先调用并返回
  2. 否则,将优先调用valueOf方法,返回原始类型的值
  3. 若不存在valueOf属性或返回类型不是原始类型,则调用toString方法,返回原始类型的值
  4. 若也不存在toString属性或返回类型不是原始类型,则抛出异常TypeError
对于不同的引用类型toPrimitive方法的执行也不同
  • 对于Object(对象)

    1. 一般对象({} 或 new Object())是不存在[Symbol.toPrimitive]属性的
    2. 优先调用valueOf方法,返回的是其本身还是引用类型,不是number类型
    3. 调用toString方法,得到字符串"[object Object]"并返回
  • 对于Array(数组)

    1. 优先调用valueOf方法,返回的是其本身还是引用类型,不是number类型
    2. 调用toString方法,得到相当于调用Array.join(',')生成的字符串并返回
  • 对于Function(函数)

    1. 优先调用valueOf方法,返回的是其本身还是引用类型,不是number类型
    2. 调用toString方法,得到字符串形式的函数体并返回
  • 对于Date对象(日期对象)

    1. Date对象与上述不同,优先调用toString()方法转换为字符串并返回

不同类型的值之间相互转换

注:这里只会将值转换为Number、String、Boolean三种类型的值

JS隐式转换.png

隐式转换的规则

下面会列举出容易发生隐式转换的场景,以及对应每种场景我们应该如何去对变量进行类型转换

if语句和逻辑语句

若if语句或逻辑语句中只存在单个变量,那我们优先将该变量转换成Boolean类型的值

快速转换为Number类型或String类型的写法

下面的操作可以让我们便捷地将变量转换为Number类型或String类型

// Number
console.log(+true) // 1
console.log(+[]) // 0
console.log(+{}) //NaN
console.log(+"123") // 123

// String
console.log(true + "") // "true"
console.log([] + "") // ""
console.log({} + "") // "[object Object]"
console.log(123 + "") // "123"

各种运算符

  • 对于(- * /)三种运算符

    在四则运算符中的 - * / 三种运算符中我们只需要将非Number类型的数据转换为Number类型即可

    console.log(1 - []); // [] -> 0 1
    console.log(1 - [1,2]); // [1,2] -> NaN NaN
    console.log(2 - {}); // {} -> NaN -> NaN
    console.log(2 - {name: "LDec.27"});  // {name: "LDec.27"} -> NaN NaN
    console.log(2 - "123"); // "123" -> 123 -121
    console.log(2 / "1"); // "1" -> 1
    console.log(2 * {}); // {} -> NaN NaN
    
  • 对于特殊的 "+" 运算符我们需要分情况考虑

    对“+”来说只有 Number + Number 或 String + String 两种情况

    1. 若一边为String类型变量则另一边也要转换为String类型做字符串拼接
    2. 若一边为Number类型,另一边为原始类型(除String类型外),则转换为Number类型做加法
    3. 若一边为Number类型,另一边为引用类型,则都转换为String类型做字符串拼接
    4. 若两边都为引用类型,需要遵循ECMAScript规定的toPrimitive原则,对引用类型进行转换后再根据上面3点进行分析进一步转换(一般来讲遵循toPrimitive原则引用类型都是转换为String类型)

==

  • 对原始类型来说

    1. NaN与任何类型比较都为false(包括它自身)

      console.log(NaN == 123) // false
      console.log(NaN == NaN); // false
      console.log(NaN == []); // fasle
      console.log(NaN == {}); // fasle
      console.log(NaN == true) // fasle
      console.log(NaN == '123') // false
      
    2. Number类型与String类型比较String类型会转换为Number类型

      // String类型和Number类型比较
      console.log(1 == "1") // "1" -> 1 true
      console.log(1 == "abc") // "abc" -> NaN fasle
      console.log(NaN == "abc") // "abc" -> NaN fasle
      
    3. Undefined类型与Null类型

      Undefined类型Null相互比较时判断为true,其与其余类型比较均返回false

      // Undefined 和 Null
      console.log(undefined == null) // true
      console.log(undefined == 123) // false
      console.log(undefined == true) // fasle
      console.log(undefined == '123') // false
      console.log(undefined == []); // fasle
      console.log(undefined == {}); // fasle
      console.log(null == 123) // false
      console.log(null == true) // fasle
      console.log(null == '123') // false
      console.log(null == []); // fasle
      console.log(null == {}); // fasle
      
    4. Boolean类型

      对于Boolean类型的值会优先转化为Number类型再与另一边进行比较

      console.log(true == 123) // true -> 1 fasle
      console.log(true == 1); // true -> 1 true
      console.log(true == "1"); // true -> 1, "1" -> 1 true
      console.log(true == "true"); // true -> 1, "true" -> NaN false
      
  • 对引用类型来说

    1. 引用类型与引用类型比较

      引用类型与引用类型比较通过比较其在栈空间中存放的地址是否一致,若不一致则返回false

      // 引用类型与引用类型比较
      
      let obj1 = {name: "LDec.27"}
      let obj2 = obj1
      let obj3 = {name: "LDec.27"}
      console.log(obj1 == obj2) // true
      console.log(obj1 == obj3) // fasle
      
    2. 引用类型与原始类型比较

      引用类型与原始类型比较,引用类型会遵循toPrimitive原则转换为原始类型,再套用原始类型的比较方法进行比较

      // 引用类型与原始类型进行比较
      console.log(1 == {}); // {} -> [object, Object] -> NaN false
      console.log(1 == []); // [] -> '' -> 0 fasle
      

实战

  • 接下来有几道类型转换的题目供我们练习:
  1. ![] == []

解析:

  1. !的优先级高于==所以先进行逻辑语句的转换将 ![] -> false -> false == []
  2. 若一边为基本类型一边为引用类型,将引用类型转换为原始类型 [] -> '' -> fasle == ''
  3. 若一边有Boolean类型的值先将其转换为Number类型 false -> 0 -> 0 == ''
  4. 若一边为Number类型一边为String类型将String类型转换为Number类型 '' -> 0 -> 0 == 0
  5. 判断为true
  1. {} + {}

解析:

  1. 这道题很特殊有两种答案根据浏览器的不同其结果也不同,一般来说浏览器会认为以{}开头是一个代码块,代码块是不会被转换的
  2. 在谷歌浏览器中认为这是两个空对象{},会将其遵循toPrimitive规则转换为两个[object Object]字符串相加,最后答案为'[object Object][object Object]'
  3. 在其他浏览器认为以{}开头是一个代码块,代码块是不会被转换的所以就变成了+{}的形式即上文提到的快速将变量转换为Number类型的方法,所以答案为NaN
  1. {} + []

解析:

  1. 这道题与上道题类似{}被当做代码块变成了 +[]强制转换为Number类型,最后答案为0
  1. [] + {}

解析:

  1. 先将引用类型转换为字符串 [] -> '', {} -> '[object Object]'
  2. 字符串相加得到答案 '[object Object]'
  1. let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;

解析:

  1. 代码是从上到下从左往右执行的,所以先执行 100 + true, 一边为Number类型另一边为除String类型以外的原始类型,所以将 true -> 1得到 101 + 21.2
  2. 正常相加得到122.2 + null,同第一步 null -> 0 得到 122.2
  3. 122.2 + undefined,依然同第一步 undefined -> NaN 得到NaN
  4. NaN + "Tencent", 一边为String类型,则另一边要转换为String类型做字符串拼接, NaN -> "NaN", 得到 "NaNTencent"
  5. 因为有一边已经是String类型了,所以另一边无论是什么类型都转换为String类型,最后得到结果'NaNTencentnull9false'
  1. [] == 0

解析:

  1. 一边为引用类型,先将其转化为String类型 [] -> ''
  2. 一边为String类型,一边为Number类型,将String类型转换为Number类型 '' -> 0
  3. 0 == 0 得到结果true
  1. [2] == 2

解析:

  1. 同第六题, [2] -> '2'
  2. '2' -> 2
  3. 2 == 2 返回true
  1. [null] == 0

解析:

  1. null意为此处不该有值所以[null] == []
  2. 将引用类型转换为String类型 [null] -> ''
  3. '' == 0, 一边为String类型,一边为Number类型,将String类型转换为Number类型 '' -> 0
  4. 0 == 0, 返回true
  1. [undefined] == 0

解析:

  1. undefined意为此处缺少一个值,应该有但还没定义所以[undefined] == []
  2. 与第八题类似最后可转换成 0 == 0,答案为true
  1. undefined == false

解析:

  1. 一边为Boolean类型的值,将其转化为Number类型 false -> 0
  2. 一边为Number类型,另一边也转换为Number类型 undefined -> NaN
  3. NaN == 0, 答案为false
  • 最后附上一张思维导图帮助我们学习和记忆

JS类型转换.png

参考文章

# 【JS 进阶】你真的掌握变量和类型了吗

# JavaScript 对象转换到基本类型值算法 ToPrimitive

# JS的{} + {}与{} + []的结果是什么?