强制类型转换
JS中的类型转换
类型转换发生在静态类型语言编译时,强制类型转换发生在动态类型语言的运行时.因此,js
中的类型转换属于强制类型转换.
在本书中,作者将因为一些操作的副作用而引起的强制类型转换称为: 隐式强制类型转换这种类型转换是自动发生的.其他手动指定的类型转换称为显式强制类型转换
const a = 42;
const b = String(a); // 显示强制类型转换
const c = a + ""; // 隐式强制类型转换
基本的类型转换规则
在深入了解强制类型转换之前,需要先掌握: 字符串,布尔值,数字之间的类型转换规则.这些基本规则非常重要
ToPromitive
该操作表示将对象转换为基本数据类型(string,number,boolean,null,undefined,symbol,bigInt),具体方式为:
- 首先调用内置属性
Symbol.toPrimitive
,该方法如果存在必须返回一个原始值, - 如果没有该方法,则调用
valueOf()
方法,将对象拆封
获取原始值, - 如果返回的不是原始值,则调用
toString
,获取基本数据类型的值,- 对象重定义的
toString
方法会优先调用,否则调用Object.prototype.toSting
- 对象: 返回
[object Object]
- 数组: 重写了
toString
方法: 将元素转换为字符串后使用,
拼接起来
- 对象: 返回
- 对象重定义的
- 如果这两个方法都没有,或者返回的值都不是原始数据类型则抛出错误
TypeError
.
ToString
这个操作负责处理非字符串转换到字符串的强制类型转换,转换规则如下:
基本类型转换字符串
null
转换为"null"
undefined
转换为`"undefinetrue/false
转换为"true"/"false"
number
:54
转换为"54"
,但对于极大极小的数字,会转换成指数形式的字符串- 对象:首先通过
ToPrimitive
操作将对象转换为基本类型的值.转换完成后根据上述规则进行转换
JSON.stringify转换规则
-
所有安全的
JSON
值都可以通过JSON.stringify
转换为字符串,不安全的值在转换过程中会进行忽略,包括:-
undefined
-
function
-
symbol
-
循环引用
-
-
第二个参数
- 指定对象序列化过程中哪些属性需要被排除,参数可以是数组或者函数
- 数组: 必须是字符串数组,表示需要处理的属性名称,不包含在数组中的属性不会进行处理
- 函数: 序列化时,对对象本身调用一次,随后对对象中的每一个属性调用一次,如果想排除某个属性,则返回
undefined
,参数(k:属性名,v:值)
第一次调用时k
为undefined
- 指定对象序列化过程中哪些属性需要被排除,参数可以是数组或者函数
-
第三个参数
- 指定输出的格式,正数: 表示缩进的字符数,字符串: 缩进位置会使用该字符串替换
ToNumber
这个操作负责处理非数字转换到数字的强制类型转换,转换规则如下:
null
: 0undefined
: NaNtrue
: 1false
: 0- 字符串:
- 空字符串或者代表空格的字符串如:
'/n'
: 0 - 数字字符串: 对应的数字
- 其他字符串: NaN
- 空字符串或者代表空格的字符串如:
- 对象:执行
ToPrimitive
操作,将对象转换为基本数据类型,然后根据上述操作进行转换
ToBoolean
这个操作负责处理非布尔值转换到不布尔值的强制类型转换,转换规则如下:
假值列表
null
undefined
+0,-0
""
NaN
false
假值列表中的值在转换为布尔值
时,转换为false
,假值列表之外的值转换为布尔值时,转换为true
特别的: js中存在假值对象,如document.all,由DOM提供给js引擎,转换时转换为false,但这时个例
强制类型转换分类
章节开头已经提到过: 作者将强制类型转换分为显式
和隐式
.这里聊一下个人理解:
-
显式强制类型转换:手动明确在什么地方进行类型转换
const num = 1; const str = String(num) // 明确表示在这里需要将`num`强制类型转换为`string`
-
隐式强制类型转换: 由于一些操作的副作用引起的类型转换
const num = 1; const str = num + '2' // 由于加法操作,根据加法操作运算规则,自动将num转换为字符串,然后执行字符串拼接操作
显式强制类型转换
下面来看一下不同场景下,显式强制类型转换有哪些操作
字符串与数字转换
-
Number()
: 强制转换为数字 -
String()
: 强制转换为字符串 -
+
: 将字符串转换为数字-
const a = '3'; const b = +a; // b => 3
-
解析数字字符串
解析数字字符串和转换数字字符串式两个不同的概念,请注意不要混淆
js提供了从数字字符串中解析出可用数字的方法:
paserInt()
: 从字符串中解析出整数parseFloat()
:从字符串中解析出浮点数
const a = 42;
const b = '42px';
//从字符串中解析出数字
parseInt(a) // 42
paserInt(b) // 42
//将字符串转换为数字
Number(a) // 42
Number(b) // NaN
具体的用法请查看MDN
,这里提到是因为这个两个方法在使用时涉及强制类型转换:接收的第一个参数必须是字符串,否则会强制转换为字符串,然后再解析
举个例子:
parseInt(1 / 0, 19); //猜猜这里会返回什么?
答案是: 18
整个解析过程是这样的:
1.1/0求值为infinity
2.Infinity是字符串吗?显然不是,那么转换成字符串`Infinity`
3.根据第二个参数指定的机制,解析字符串`Infinity`,第一个字母为I,值为18,第二个字符为n,不在进制内,解析结束,返回18
下面还有些结果很奇怪,但分析起来又很合理的例子:
parseInt(0.00008) // 0
parseInt(0.00000008) //8(8来自8e-7)
parseInt([1] + []); // 1
parseInt(parseInt,16) // 15 (15来自functiuon的f)
显式转换为布尔值
Boolean()
- 一元运算符:
!
隐式强制类型转换
下面来看一下不同场景下,显式强制类型转换有哪些操作
字符串与数字
-
+
号运算符,运算规则:- 如果一个操作数是string,或者能通过
ToPrimitive
操作转换为string,则执行字符串拼接操作 - 否则执行数字加法
3 + "" => '3' [1,2] + 3 => '1,23'
- 如果一个操作数是string,或者能通过
-
-
号运算符,运算规则:- 将操作数转换为number进行计算
隐式转换为布尔值
if()
中的条件判断表达式for(...;...;...)
中的条件判断表达式while(),do..while()中的条件判断表达式
? :中的条件判断表达式
上述情况中,代码执行时,会将表达式隐式转换为boolean
,在进行判断,下面的|| ,&&
也会将操作数隐式转换为boolean
,但其返回值有些特殊,因此单独拎出来说:
||, &&
逻辑运算符||,&&
,在js中应该称为操作数选择运算符
跟贴切,为什么呢?因为在js中他们返回的并不是布尔值,而是具体的操作数
||
: 对左侧操作数隐式转换为boolean
,如果为真,返回左侧操作数的值,如果为假,返回右侧操作数的值- 常用场景: 为函数参数赋默认值,
arg1 = arg2 || 默认值
- 常用场景: 为函数参数赋默认值,
&&
: 对左侧操作数隐式转换为boolean
,如果为真,返回右侧操作数的值,如果为假返回左侧操作数的值- 常用场景: 作为守护运算符,让前面的表达式为后面的表达式把关,如
fn
函数存在才能调用:fn && fn()
- 常用场景: 作为守护运算符,让前面的表达式为后面的表达式把关,如
显然虽然转换你过程中涉及了隐式强制类型转换,但最终返回的确实表达式的值,并非布尔值
Symbol的强制类型转换
symbol
在类型转换时有几个注意点:
-
symbol
转换为string
时,允许显式强制类型转换,不允许隐式强制类型转换const symbol = Symbol("这是个符号") String(symbol); // 'symbol(这是个符号)' symbol + "" // 报错
-
symbol
不能转换为number
-
symbol
转换为布尔值,永远是true
宽松相等和严格相等
宽松相等==
与严格相等===
的常见误区: ==
检查值相等,===
检查值和类型相等.这个解释是不准确的.正确的解释应该是: ==允许在相等比较中进行强制类型转换,而===不允许,
虽然==
在日常开发中很少用到,但由于其中涉及隐式强制类型转换,并且作者也强调==
虽然广受诟病,不能因噎废食,所以这里将详细探讨一下宽松相等在不同情况下的具体实现:
抽象相等
抽象相等比较算法定义了==
在运算时的行为,比较规则如下:
- 类型相同: 比较值是否相等,特殊的:
NaN
不等于NaN
+0
等于-0
- 两个都是对象: 如果两个值指向同一个值,即视为相等,否则视为不相等,这里和严格相等的行为一致
- 两个值类型不同: 进行隐式强制类型转换,将一个或两个转换成相同类型之后进行比较,具体比较规则视情况而定,共有以下几种情况:
- 字符串和数字
- 布尔值和其他类型
null
,undefined
,其他类型比较- 对象和非对象比较
下面将对上述最后一种情况: 两个值类型不同,进行详细讲解:
字符串和数字
规则: 将两者中的字符串进行ToNumber
操作(上文中ToNumber详细讲解)转换为数字,然后进行比较
布尔值和其他类型
规则:将两者中的布尔值执行ToNumber
操作转换为数字,然后进行比较,如果类型仍不相同,视不同情况继续转换:
"42" == true // false
比较过程如下:
1.类型不同,对true执行toNumber操作,"42" == 1
2.类型仍然不同,属于字符串和数字的比较,则对字符串执行ToNumber操作: 42 == 1
3.现在类型相同了,比较两个操作数是否相同,42不等于1返回false
null,nudefined和其他类型比较
规则: null
,undefined
之间相等,初次之外null
和undefine
与其他类型都不相等
null == undefined // true
null == "" // false
undefined == 0 // false
对象和非对象比较
规则:如果一个操作数为数字或字符串,则对对象执行ToPrimitive
操作转换为基本数据类型进行比较
这里只提到数字和字符串是因为: 上文中已经说过与null
,undefined
,布尔值之间比较的规则了,如果是这几种类型,则按照该类型对应规则进行转换
const obj = Object('abc');
const abc = 'abc'
obj == abc //true
obj === abc //false
obj==abc比较过程如下:
1.obj是对象,abc为字符串,需要对对象进行`ToPromitice`操作
2.检查调用obj的valueOf方法,返回基本数据类型'abc',`ToPRimitive`操作完成: 'abc' == 'abc'
3.现在两者类型相同了,直接比较值
抽象关系比较
比较大小时a < b
,也涉及强制类型转换,而且js只有抽象关系比较,没有严格抽象关系比较,因此,抽象关系比较的转换规则,我们一定要熟悉.毕竟js中只有这一种比较方式
比较规则如下:
- 双方都是字符串: 按照字母顺序进行比较
- 其他情况: 先进行
ToPrimitive
操作,如果出现非字符串,则进行ToNumber
转换为数字比较
[42] < ['43'] // true 两边执行ToPrimitive转换成基本数据类型 '42' < '43',都是字符串,然后按照字母顺序比较
{a:42} < {b:43} // false两边执行ToPrimitive转换成基本数据类型 '[object Object]' < '[object Object]',明显相同,返回false
>=和<=
根据规范a<=b
被处理为b< a
,然后将结果取反,因此a <= b
表示的不是a小于等于b
而是a不大于b
,来看个有趣的情况:
const obj1 = {a:1};
const obj2 = {a:2};
obj1 < obj2 //false
obj1 > obj2 //false
obj1 == obj2 // false
obj1 <= obj2 //true
obj1 >= obj2 //true
仔细观察你会发现: obj1
即不大于也不小于也不等于obj2
,这就很奇怪,我们来分析以下比较过程:
obj1 < obj2
两边执行ToPrimitive
操作,转换为:'[object Object]' < '[object Object]'
,显然应该返回false
obj1 > obj2
两边执行ToPrimitive
操作,转换为:'[object Object]' > '[object Object]'
,显然应该返回false
obj1 == obj2
两边都是对象,比较两个变量的值是不是指向同一个,显然这两个是单独定义的,所以返回false
obj1 <= obj2
,这个比较实际上是obj1 > obj2
的值取反,上述obj1 > obj2
比较结果为false,所以这里返回true
obj1 >= obj2
,这个比较实际上是obj1 < obj2
的值取反,所以返回true
比较关系符由于没有对应的严格关系比较,因此当类型不同的数据进行比较的时候,无法避免隐式强制类型转换的发生,在比较时,最好显示强制类型转换为同一种类型后再进行比较
总结
js的类型强制转换非常复杂,在处理时要清楚: 基础类型转换规则(ToPrimitive,ToNumber,ToString
),在不同情况下这些基础规则运用的情况不同,因此处理时,要知其然知其所以然.