关于js隐式转换

js引擎内部实现类型转换的4个抽象操作

隐式类型转换是在一定场景下,js运行环境自动调用这几个方法,尝试转换成期望的数据类型

  • ToString(argument)
  • ToNumber(argument)
  • ToBoolean(argument)
  • ToPrimitive(input[ , PreferredType])

ToString

这里所说的ToString可不是对象的toString方法,而是指其他类型的值转换为字符串类型的操作。

输入类型结果
Undefined"undefined"
Null"null"
Boolean如果参数是 true,那么结果为 "true"。
如果参数是 false,那么结果为 "false"。
String结果等于输入的参数(不转换)。
Number

NaN => "NaN"

+0, -0  => "0"

-10 => "-10"

Infinity => "Infinity"

极小或者极大的数值使用指数形式

Object应用下列步骤:
  1. 设原始值为调用 ToPrimitive ( 输入参数 ,  字符串类型)的结果。
  2. 返回ToString(原始值)。

String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(10) // '10'
String(1e21) // '1e+21'
String([1,2,3]) // '1,2,3'
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Objecr]'

对象的toString方法,满足ToString操作的规则。

注意:上面所说的规则是在默认的情况下,如果修改默认的toString()方法,会导致不同的结果 

ToNumber

ToNumber指其他类型转换为数字类型的操作。

输入类型结果
UndefinedNaN
Null+0
Boolean如果参数是 true,结果为 1。如果参数是 false,此结果为 +0。
Number结果等于输入的参数(不转换)。
String如果是纯数字形式,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN
Object应用下列步骤:
  1. 原始值为调ToPrimitive ( 输入参数 ,  数值类型)的结果。
  2. 返回 ToNumber ( 原始值 )。

Number(null) // 0
Number(undefined) // NaN
Number('10') // 10
Number('10a') // NaN
Number('') // 0 
Number(true) // 1
Number(false) // 0
Number([]) // 0
Number(['1']) // 1
Number({}) // NaN

ToBoolean 

ToBoolean指其他类型转换为布尔类型的操作。 

输入类型结果
Undefinedfalse
Nullfalse
Boolean结果等于输入的参数(不转换)。
Number如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。
String如果参数参数是空字符串(其长度为零),结果为 false,否则结果为 true。
Objecttrue
Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true

 总结:js中的假值只有falsenullundefined空字符0NaN,其它值转为布尔型都为true

ToPrimitive

ToPrimitive指对象类型类型(如:对象、数组)转换为原始类型的操作。

ToPrimitive(input[ , PreferredType])

input是要转换的值,PreferredType是可选参数,可以是Number或String类型。它只是一个转换标志,转化后的结果并不一定是这个参数所值的类型,但是转换结果一定是一个原始值(或者报错)。

PreferredType为Number

转换流程:

  1.  如果输入的值已经是一个原始值,则直接返回它;
  2. 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值;
  3. 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值;
  4. 否则,抛出TypeError异常。

PreferredType为String

转换流程:

  1.  如果输入的值已经是一个原始值,则直接返回它;
  2. 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值;
  3. 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值;
  4. 否则,抛出TypeError异常。

PreferredType参数不存在 

PreferredType的值会按照这样的规则来自动设置:

  1. 该对象为Date类型,则PreferredType被设置为String;
  2. 否则,PreferredType被设置为Number;
//  对于日期
var date = new Date();
console.log(date == date.toString());  // true


var obj = {
    valueOf() {
        return 20
    },
    toString() {
        return 30
    }
}

console.log(obj + 2);    // 22

var obj1 = {
    valueOf() {
        return 20
    },
    toString() {
        return []
    }
}

console.log(obj1 + 2);    // 22

var obj2 = {
    valueOf() {
        return []
    },
    toString() {
        return 30
    }
}

console.log(obj2 + 2);    // 32

var obj3 = {
    valueOf() {
        return {}
    },
    toString() {
        return []
    }
}

console.log(obj3 + 2);    // Uncaught TypeError: Cannot convert object to primitive value

宽松相等(==)比较时的隐式转换规则

ES5文档 == 运算规则的描述如下

比较运算`x`==`y`, 其中`x`和` y`是值,产生true或者false。这样的比较按如下方式进行:

1.  若Type(x)与Type(y)相同, 则
    a.  若Type(x)为Undefined, 返回true。
    b.  若Type(x)为Null, 返回true。
    c.  若Type(x)为Number, 则
        ①.  若x为NaN, 返回false。
        ②.  若y为NaN, 返回false。
        ③.  若x与y为相等数值, 返回true。
        ④.  若x 为 +0 且 y为−0, 返回true。
        ⑤.  若x 为 −0 且 y为+0, 返回true。
        ⑥.  返回false。

    d.  若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
    e.  若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
    f.  当x和y为引用同一对象时返回true。否则,返回false。

2.  若x为null且y为undefined, 返回true。

3.  若x为undefined且y为null, 返回true。

4.  若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。

5.  若Type(x) 为 String 且 Type(y)为Number,

6.  返回比较ToNumber(x) == y的结果。

7.  若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。

8.  若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。

9.  若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。

10.  若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。

11.  返回false

1. 布尔类型和其他类型的相等比较 

  • 只要布尔类型参与比较,该布尔类型的值首先会被转换为数字类型
  • 根据布尔类型ToNumber规则,true转为1false转为0
false == 0 // true
true == 1 // true
true == 2 // false

2. 数字类型和字符串类型的相等比较 

  • 数字类型字符串类型做相等比较时,字符串类型会被转换为数字类型
  • 根据字符串的ToNumber规则,如果是纯数字形式的字符串,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN
0 == '' // true
1 == '1' // true
1e21 == '1e21' // true
Infinity == 'Infinity' // true
true == '1' // true
false == '0' // true
false == '' // true

3. 对象类型和原始类型的相等比较

  • 对象类型原始类型做相等比较时,对象类型会依照ToPrimitive规则转换为原始类型
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true
[2] == 2 // true

数组[2]是对象类型,所以会进行ToPrimitive操作,也就是先调用valueOf再调用toString,根据数组ToString操作规则,会得到结果"2",而字符串"2"再和数字2比较时,会先转为数字类型,所以最后得到的结果为true 

[null] == 0 // true
[undefined] == 0 // true
[] == 0 // true

根据上文中提到的数组ToString操作规则,数组元素为nullundefined时,该元素被当做空字符串处理,而空数组[]也被转为空字符串,所以上述代码相当于

'' == 0 // true
'' == 0 // true
'' == 0 // true

空字符串会转换为数字0,所以结果为true

const a = {
  valueOf () {
    return 10
  },
  toString () {
    return 20
  }
}
a == 10 // true

 对象的ToPrimitive操作会先调用valueOf方法,并且avalueOf方法返回一个原始类型的值,所以ToPrimitive的操作结果就是valueOf方法的返回值10

4. null、undefined和其他类型的比较

首先得知道nullundefined的定义

null: 代表“空值”,代表一个空对象指针,使用typeof运算得到 "object",所以可以认为它是一个特殊的对象值。

undefined:声明了一个变量但并未为该变量赋值,此变量的值默认为undefined。

ECMAScript规范中规定nullundefined之间互相宽松相等(==),并且也与其自身相等,但和其他所有的值都不宽松相等(==)

  • nullundefined宽松相等的结果为true,这一点大家都知道
  • 其次,nullundefined都是假值
null == false // false
undefined == false // false

5. 对象和对象相比较

引用类型:是指存放在堆内存中的对象,变量名保存在栈内存里,而对应的值保存在堆内存里,这个变量在栈内存中实际保存的是:这个值在堆内存中的地址,也就是指针,指针指向堆内存中的地址。

比较的规则

  • 如果两个对象指向同一个对象,相等操作符返回 true,否则为false 
var a = {};
var b = {};
a == b  // false

var c = [];
var d = c;
c == d  // true
[] == ![]  // true
{} == !{}  // false

JS规定,逻辑非 (!) 的优先级高于相等操作符 ( == )

![]转换成boolean为false, 即比较 [] == false,  这下变成了对象类型布尔类型相比较了, 很容易的出比较变为'' == 0,再根据字符串与数字类型相比较规则,比较变为0 == 0,显然结果为true;

!{}转换成boolean为false, 即比较 {} == false,{}的valueOf()方法得到的不是原始类型,继续执行{}的toString()方法到的结果为"[object object]",比较变为"[object object]" == 0,再根据字符串与数字类型相比较规则,"[object object]"转为数字类型为NaN,比较变成NaN == 0,NaN不与任何值相等,显然结果为false

一道面试题

定义一个变量a,使得下面的表达式结果为true

  a == 1 && a == 2 && a == 3    // true

利用valueOf 

const a = {
  // 定义一个属性来做累加
  i: 1,
  valueOf () {
    return this.i++
  }
}
a == 1 && a == 2 && a == 3 // true

或者利用toString

const a = {
  // 定义一个属性来做累加
  i: 1,
  toString () {
    return this.i++
  }
}
a == 1 && a == 2 && a == 3 // true

利用Symbol.toPrimitive

const a = {
  // 定义一个属性来做累加
  i: 1,
  [Symbol.toPrimitive] () {
    return this.i++
  }
}
a == 1 && a == 2 && a == 3 // true

参考资料:

es5文档

从一道面试题说起—js隐式转换踩坑合集 - 掘金

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值