Javascript基础:隐式类型转换

Javascript基础:隐式类型转换_第1张图片

前言

众所周知 javascript 是一种弱类型语言。强类型和弱类型主要是在变量类型处理的角度进行分类的。强类型是一旦指定数据类型,如果不经过强制转换,那么将永远是指定的这个类型。js 中无法声明数据类型,变量类型是根据实际值决定的,由编译器自动调用转换函数进行转换,这种方式称之为隐式转换,今天我们就来聊聊数据类型是如何隐式转换的吧!

小试牛刀

可能很多小伙伴都知道 JS隐式转换 ,但你是否真的懂它了呢?下面来试试吧!!

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

  a == 1 && a == 2 && a == 3

相信很多小伙伴都懵了,还能这样?别急,我们再来看看其他的!

[] == ![] // true

[] == 0 // true
  
[2] == 2 // true

['0'] == false // true

+[] // 0

+[1] // 1

+[1, 2] // NaN

这下好了,彻底懵了!!下面让我们来探讨一下呗~~

javascript隐式转换规则

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

  • ToString
  • ToNumber
  • ToBoolean
  • ToPrimitive

ToString

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

  • null:转为 "null"
  • undefined:转为 "undefined"
  • 布尔类型:truefalse 分别被转为 "true""false"
  • 数字类型:转为数字的字符串形式,如 10 转为 "10"1e21 转为 "1e+21"
  • 数组:相当于调用数组的 Array.prototype.join() 方法,如 [1, 2, 3] 转为 "1,2,3",空数组 [] 转为 '' 空字符串,数组中的 nullundefined ,会被当做 '' 空字符串处理
  • 普通对象:相当于直接使用 Object.prototype.toString(),返回 "[object Object]"
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(10) // '10'
String(1e21) // '1e+21'
String([1,2,3]) // '1,2,3' 相当于Array.prototype.join()
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Objecr]'  相当于Object.prototype.toString()

ToNumber

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

null: 转为 0

undefined:转为 NaN

字符串:如果是纯数字形式,则转为对应的数字,空字符转为 0 , 否则一律按转换失败处理,转为 NaN

布尔型:truefalse 被转为 10

数组:数组首先会被转为原始类型,也就是 ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理,关于 ToPrimitive,会在下文中讲到

对象:同数组的处理

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 指其他类型转换为布尔类型的操作

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

Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true

ToPrimitive

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

  • 当对象类型需要被转为原始类型时,它会先查找对象的 valueOf 方法,如果 valueOf 方法返回原始类型的值,则 ToPrimitive 的结果就是这个值
  • 如果 valueOf 不存在或者 valueOf 方法返回的不是原始类型的值,就会尝试调用对象的 toString 方法,也就是会遵循对象的 ToString 规则,然后使用 toString 的返回值作为 ToPrimitive 的结果

注意:对于不同类型的对象来说,ToPrimitive 的规则有所不同,比如 Date 对象会先调用toString,具体可以参考ECMA标准

如果 valueOftoString 都没有返回原始类型的值,则会抛出异常

Number([]) // 0
Number(['10']) //10
Number(['10', '10']) // NaN

const obj1 = {
	valueOf () {
		return 100
	},
	toString () {
		return 101
	}
}
Number(obj1) // 100

const obj2 = {
	toString () {
		return 102
	}
}
Number(obj2) // 102

const obj3 = {
	toString () {
		return {}
 	}
}
Number(obj3) // TypeError

前面说过,对象类型在 ToNumber 时会先 ToPrimitive,再根据转换后的原始类型 ToNumber

  • Number([]), 空数组会先调用 valueOf,但返回的是数组本身,不是原始类型,所以会继续调用 toString,得到空字符串,相当于 Number(''),所以转换后的结果为 "0"
  • 同理,Number(['10']) 相当于 Number('10'),得到结果 10
  • Number(['10', '10']) ,相当于 Number('10,10'),得到结果 NaN
  • obj1valueOf 方法返回原始类型 100,所以 ToPrimitive 的结果为 100
  • obj2 没有 valueOf,但存在 toString,并且返回一个原始类型,所以 Number(obj2) 结果为 102
  • obj3toString 方法返回的不是一个原始类型,无法 ToPrimitive,所以会抛出错误

运算符中的隐式类型转换

下面只介绍常用的几种:

  • 算术运算符 +
  • 关系运算符 ==

算术运算符 +

一元 +
+1  // 1

+1// 1

+-1// -1

+{} // NaN

+[] // 0;

+[1] // 1;

+[1, 2] // NaN
二元 +

二元加法运算符 + 可以对两个数字做加法,也可以做字符串连接操作,如果其中一个操作数是字符串或者隐式转换为字符串的对象,另外一个操作数将会转换为字符串,加法将进行字符串的连接操作

1 + 2 //  3: 加法

"1" + "2" //  "12": 字符串连接

"1" + 2 //  "12": 数字转换为字符串后进行字符串连接

1 + + '1' // 2 : 第二个+相当于数学中的正号

1 + {} //  "1[object Object]": 对象转换为字符串后进行字符串连接

'1' + true //  "1true": 布尔值转换为字符串后进行字符串连接

true + true //  2: 布尔值转换为数字后做加法

2 + null //  2: null转换为0后做加法

2 + undefined //  NaN: undefined转换为NaN后做加法

1 + 2 + " blind mice"; //  "3 blind mice"

1 +2 + " blind mice"; //  "12 blind mice"

//需要注意的是,“++”运算符从不进行字符串连接操作,它总是会将操作数转换为数字并增1。表达式++x并不总和x=x+1完全一样
let x = '1'
++x //  2: 

关系运算符 ==

规则:

  • 对于数字和字符串的抽象比较,将字符串进行 ToNumber 操作后再进行比较
  • 对于布尔值和其他类型的比较,将其布尔类型进行 ToNumber 操作后再进行比较
  • 对于对象和基础类型的比较,将对象进行 ToPrimitive 操作后在进行比较
  • 对象之间的比较,引用同一个对象则为 true,否则为 false

下面来看几个例子:

true == '1'       // true
/**
  * 布尔类型和其他类型比较适用规则2,true通过ToNumber操作转换为1
  * 这时候1 == '1',这时候适用规则1,将'1'通过ToNumber操作转换为1
  * 1 == 1 所以输出为true
  **/

var obj = {
    valueOf: function() { return '1' }
}

true == obj      // true
/**
  * 首先适用规则2,将true转换为1,此时1 == obj
  * 此时适用规则3,将obj转换为'1',此时1 == '1'
  * 此时适用规则1,将'1'转换为1,此时1 == 1,所以输出true
  **/

[] == ![]      // true

/**
  * 一般直觉这明细是false,但我们仔细看一下
  * ![]先对[]进行强制boolean转换,所以实际上应该是[] == false
  * 这样就又回到我们刚刚的规则上了,适用规则2所以[] == 0
  * 接着适用规则3,所以 '' == 0
  * 最后 ToNumber('')  == 0
  **/

// 特例
NaN == NaN        // false
null == undefined // true,属于ecma规范

最后

我们看看文章开头说的那道题目的解法:

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

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

这样就完美解决啦!!

你掌握了js数据类型隐式转换的方法了吗

参考文章:

https://juejin.cn/post/6844903694039777288

你可能感兴趣的:(Javascript,javascript,es6)