js隐式转换

124 阅读8分钟

一、JavaScript中的数据类型

  1. 简单数据类型(原始类型): String Number Boolean undefined null Symbol(ES6新增,用于表示独一无二的. 值, 因为是原始类型,创建时不需new)
  2. 引用数据类型(复杂数据类型): Object(Object、Array、Function本质上都是Object)

object ====> 对象转换成原始值,必然会调用toPrimitive()内部函数

二 、隐式转换

当运算符两端的数据类型不一致,会触发隐式转换

1.涉及隐式转换最多的运算符 + 、>和 ==。

1.+运算符即可数字相加,也可以字符串相加。
2.== 不同于===,故也存在隐式转换。
3.- * / 这些运算符只会针对number类型,故转换的结果只能是转换成number类型

2.三种隐式转换类型

2.1隐式转换中将值转为原始值,ToPrimitive:

ToPrimitive介绍: 
    ToPrimitive有着这样的形式:ToPrimitive(input, PreferredType)
   (input是要转换的值,PreferredType是可选参数,仅可以是Number或String类型 )

      let obj = {
        valueOf: () => 5,
        toString: () => 'five'
      };
      let primitiveValue1 = Number(obj);
      console.log(primitiveValue1); // 5
      
      let primitiveValue1 = String(obj);
      console.log(primitiveValue1); // five
   注意:
  (1). PreferredType的值会按照这样的规则来自动设置:
      1、该对象为Date类型,则PreferredType被设置为String
      2、否则,PreferredType被设置为Number
  (2).为什么?
     当PreferredType为number时,会先调用valueof方法,若不是原始值,则调用tostring方法,
        这样做的好处就是最大可能的保证将传入的值保持为原有值,而PreferredType设置为string,
        则先调用tostring一股脑的将传入转为string。
     但是当日期为date格式时,valueof方法会取到日期的时间戳,我们获取时间戳那么大一个数字显然没太大意义,
     而tostring获取的是日期格式,所以input为date时,设置PreferredType为string更好
      
  (3).ToPrimitive是如何对输入进行类型转化的呢?
    (3.1)首先如果PreferredType传为Number:
      1、如果输入的值已经是一个原始值,则直接返回它
      2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
        如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
      3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
      4、否则,抛出TypeError异常。
    (3.2)如果PreferredType传为String:
      1、如果输入的值已经是一个原始值,则直接返回它
      2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
      3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
        如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
      4、否则,抛出TypeError异常。
    (3.3)如果不传:
      PreferredType会被默认设置为Number,但是当输入input为Date类型,则PreferredType被设置为String
    例子:
      1. {} + {} // ???  NaN
        * 首先是js内部使用ToPrimitive,由于我们没有设置PreferredType,于是PreferredType就是默认值number。
        * 所以会执行valueOf方法,({}).valueOf返回的还是{},不是原始值。
        * 执行toString()方法,({}).toString返回的是"[object Object]"字符串,是原始值
        * 所以结果为"[object Object]" + "[object Object]" = "[object Object][object Object]",
        *  为什么不是"[object Object][object Object]" ? 
          在node中会将以“{”开始,“}”结束的语句外面包裹一层( ),就变成了({ } + { }),结果就符合预期。而普通版本的chrome依然会解析成{};+{},结果就变成了NaN

      2.[] + [] // ???
        * 两个都是Array对象,不是Date对象,所以PreferredType为Number,
        * 所以先调用valueOf(),结果还是[ ],不是原始值,
        * 所以继续调用toString(),结果是“ ”原始值,将“ ”返回。
        * 第二个[ ]过程是相同的,返回“ ”。加号两边结果都是String类型,所以进行字符串拼接,结果是“ ” 
      3. {} + [] // ???
        * {} ===> "[object Object]" ,然而 [] ===> ' '
        * 加号两边结果都是String类型,所以进行字符串拼接,结果是“[object Object]”
        * 显示的答案是0! 这是什么原因呢?原来{ } + [ ]被解析成了{ };+[ ],前面是一个空代码块被略过,
        剩下+[ ]就成了一元运算。[ ]的原值是” ”, 将” ”转化成Number结果是0
     4. [1,2,3] == '1,2,3'
       左边数组对象进行隐式转换ToPrimitive([1,2,3]) => '1,2,3'
         

2.2 通过ToNumber将值转换为数字

2.3 通过ToString将值转换为字符串

3.具体应用

3.1. 字符串连接符 与 算术运算符 隐式转换

规则:字符串连接符 + 其他数据类型 调用String()方法,进行拼接

算术运算符 + 其他数据类型 Number()方法,进行加法计算

1. console.log(1 + 'true')
   console.log(undefined+"1") 
   console.log([1,2,3]+"测试") //1,2,3测试
   console.log({name:"田本初"}+"测试") //  [object Object]测试
   
2. console.log(1 + true )
3. console.log(1 + undefined)
4. console.log(1 + null)

结果:  
 1String(1) + 'true' = '1true' // 字符串连接符 + 其他数据类型 调用String()方法,进行拼接
 2Number(true) + 1 = 2 // 算术运算符 + 其他数据类型 Number()方法,进行加法计算
 3Number(undefined) + 1 = NaN
 4) Number(null) + 1 = 0 + 1 = 1

3.2. 逻辑非隐式转换 与 关系运算符 隐式转换

规则:字符串 和数字比较,字符串转成数字

字符串比较,看CSII码;若为不等长字母,比较同位置字母大小,直至做出判断

1.console.log("2" > 10)
2.console.log("2" > "10")
3.console.log("abc" > "b")
4.console.log("abc" > "aad")

结果:
 1.Number('2')=2 > 10 ?  false
 2.'2'.charCodeAt() = 50  '10'.charCodeAt() = 49  ? true
 3.'a' 'b'  charCodeAt(). ? false
 4.'ab' 'aa'  ? true

3.3. 复杂数据类型 在进行 关系运算符 作比较的时候,会隐式调用 valueOf这个方法

规则: 引用类型,先调用valueOf()

var a = {
    valueOf: function() {
      console.log(123)
      return 4
    }
}
if(a == 4) {
  console.log('haha ')
}
/// 123 hahaha
  var a = {
    i: 0,
    valueOf: function() {
      console.log('123', a)
      return ++a.i
    }
  }
 if(a == 1 && a == 2 && a == 3) {
   console.log('神奇的123 ')
  }
  /// 神奇的123

4.==运算符

比较操作符 == ,会进行类型转换后再进行比较 , 分析(六种基本情况+三种特殊情况)

4.1.对象和字符串比较

转换规则: 调用ToPrimitive()内部函数,将对象或者数组转换成字符串,然后进行比较

 // 数组和字符串
console.log([1, 2, 3] == '1,2,3')
console.log('对象和字符串', [11] == '11')
console.log([] == 0) 
// 对象和字符串
let obj = { a: 1}
console.log( obj == '[object Object]')

4.2. 对象和数值比较

转换规则:

调用ToPrimitive()内部函数,将对象转换成字符串;

再调用ToNumber()将字符串转成数字,进行比较

 // 对象和数值
console.log('对象和数值', [11] == 11)

分析:ToPrimitive([11]) => '11',ToPrimitive('11') => 11

4.3 对象和布尔值比较

转换规则:对象:调用ToPrimitive()内部函数,将对象转换成字符串;再调用ToNumber()将字符串转成数字;

布尔值:调用ToNumber()将布尔值转成数字;进行比较

console.log('对象和布尔值', [] == false)

分析:ToPrimitive([]) => '',ToPrimitive('') => 0;ToPrimitive('false') => 0

4.4 字符串和数值

转换规则:调用ToNumber()将字符串转换成数字,然后比较

  console.log('11' == 11)  // true

4.5. 字符串和布尔值

转换规则:调用ToNumber()将字符串转换成数字;调用ToNumber()将布尔值转成数字;然后比较

console.log('1' == true) // true

4.6. 布尔值和数值

转换规则: 调用ToNumber()将布尔值转成数字; 然后比较

console.log(1 == true) 
//分析: ToPrimitive('true') => 1

4.7. 存在!运算符(特殊情况1)

转换规则:

先将数据转换成布尔值,其余按照上述规则进行;除了 0、NaN、''、null、undefined转换成false,其余true

 1. console.log([] == 0) 
    console.log([] == false)  // true
    console.log(![] == false) // true
    
 2. console.log(![] == 0)
 3. console.log(![] == [])

 结果:1.// true  
       空数组 `[]` 会被转换为 `0`,因为在比较数组和数字时,JavaScript 会将数组转换为其长度。
     2.// true    
     `!` 是逻辑非操作符,用于取反一个值。空数组 `[]` 在布尔上下文中会被视为 `true`,因为它是一个
     存在的对象。 所以 `![]` 的结果是 `false`。
     接着,`false` 会被转换为数字 `0`,因此表达式变成了 `false == 0`
      3.// false
     `![]` 的结果是 `false`。 空数组 `[]` 在布尔上下文中也会被视为 `true`,
     所以 `[]` 转换为布尔值后也是 `true`

4.8.null 和 undefined 的比较(特殊情况二)

转换规则:null 和 undefined都代表着无效的值,但两者数据类型不一样,故不全等

console.log(undefined == undefined)  true
console.log(undefined == null)       true
console.log(null == null)            true

4.9.存在NaN(特殊情况三)

转换规则:NaN(Not a Number)表示不是一个数字,因此 NaN 与任何值都不相等,包括 NaN 本身

console.log(NaN === NaN)             false

具体总结

比较运算 x==y, 其中 x 和 y 是值,返回 true 或者 false。这样的比较按如下方式进行:
1、若 Type(x) 与 Type(y) 相同, 则
    1* 若 Type(x) 为 Undefined, 返回 true。---
    2* 若 Type(x) 为 Null, 返回 true。   ---
    3Type(x) 为 Number, 则
  
        (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)、返回 false。
        
    4Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。 否则, 返回 false。
    5Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。 否则, 返回 false。
    6* 当 x 和 y 为引用同一对象时返回 true。否则,返回 false。
  
2、若 x 为 null 且 y 为 undefined, 返回 true。---
3、若 x 为 undefined 且 y 为 null, 返回 true。---
4、*若 Type(x) 为 Number 且 Type(y) 为 String,返回比较 x == ToNumber(y) 的结果。
5、*若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。
6、*若 Type(x) 为 Boolean, 返回比较 ToNumber(x) == y 的结果。
7、*若 Type(y) 为 Boolean, 返回比较 x == ToNumber(y) 的结果。
8、若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。
9、若 Type(x) 为 ObjectType(y) 为 String 或 Number, 返回比较 ToPrimitive(x) == y 的结果。
10、返回 false。

补充:---:null 和 undefined都代表着无效的值
    -NaN(Not a Number)表示不是一个数字,因此 NaN 与任何值都不相等,包括 NaN 本身