最近没事就捣鼓浏览器控制台,发现了一个奇怪的现象,因此引发了一些思考。
[] == 0 // true
{} == 0 // Uncaught SyntaxError: Unexpected token '=='
({}) == 0 //false
undefined == 0 // false
'' == 0 // true
null == 0 // false
false == 0 // true
new Number(0) == 0 // true
NaN == 0 //false
我们可以发现一个现象,那就是js内部,我们通常认为是空的东西,他们和0作’=='比较,有些为true,有些为false。众所周知, ’ == '的左右两边值会进行类型转换。那么它是一个什么流程呢?
本文引用《js红宝书》P71与《js权威指南(第七版)》P44~P51
‘==’ '!='转换规则
首先介绍’ == ’ 转换规则(!=与==规则一样)
ECMAScript在执行性’ == '操作时,如果操作数相等,则会返回true。如若两操作符不相等,则会根据如下规则进行强制类型转换:
-
如果任一操作数是布尔值,则将其转化为数值再比较是否相等。false转化为0,true转化为1.
-
如果一个操作数是字符串,另一个操作数是数值,则会尝试将字符串转化为数值,再比较是否相等。
-
如果一个操作数是对象,另一个不是,则使用隐式类型无偏号转换原则(见后文)获取其原始值,再根据前面的规则进行比较。
在比较时,有如下特殊情况,那就是存在undefined与null的情况: -
null和undefined相等
-
null和undefined不能转化为其他类型的值再进行比较。(那就是如果一边存在null和undefined,另一边若不是null或undefined,直接返回false)
-
若有任一操作数是NaN,则直接返回false。(另外,NaN是Number类型,但是是所有数据类型中唯一与自身不全等的数。在Object.is(NaN,NaN)中才会返回true。)
-
若两个操作数都是对象,则比较他们指向的是不是同一个对象。如果两个操作数都指向同一个对象,则会返回true,否则返回false。(因此深浅拷贝出来的对象在’=='情况下也不相等。)
下面总结了一些特殊情况下的比较结果:
表达式 | 结果 |
---|---|
null == undefined | true |
‘NaN’ == NaN | false |
5 == NaN | false |
NaN == NaN | false |
NaN != NaN | true |
false == 0 | true |
true == 1 | true |
true == 2 | false |
undefined == 0 | false |
null == 0 | false |
‘5’ == 5 | true |
上面介绍了’=='的执行流程,下面介绍一下数据类型间的相互转换: |
类型转换
我们还是先来通过几个例子来看一下:
10 + "object" // "10object"
"7" * "4" // 28
let n = 1 - "x" // NaN
n + "objects" // "NaNobjects"
js内部操作符隐式类型转换
在js中,某些操作符会执行隐式类型转换(后文介绍隐式类型转换规则),例如’+'操作符,如果该操作符作用于两个操作数,也就是a+b的方式,若其中存在一个操作数为String类型,会默认将另一个操作数也转化为String类型,但若该操作符只作用于一个操作数,如+a的情况,会将该操作数转化为Number类型。下面简单介绍几个该类操作符:
操作符 | 表达式 | 效果 |
---|---|---|
+ | x + “” | String(x) |
+ | +x | Number(x) |
- | x - 0 | Number(x) |
* | x * “1” | Number(x) |
!! | !!x | Boolean(x) |
js显式类型转换
日常在工作需求中,我们也可能会碰到需要进行类型转换的时候,这里我们可以采用显式类型转化:
最常用的显式类型转换方法有Number(),String(),Boolean()。这三个方法的作用想必一看就懂,就不多做介绍。当然需要提到的是:除undefined与null外所有值都有toString()方法(为避免歧义,部分在调用时需要加上(),后文详细介绍各个类上重写的toString()方法。),这与String()显式转换后得到的值一样。另外就是将这些转换方法作为构造函数时,会得到一个对象类型的值(并不是构造函数的类型)。
转换到布尔值规则
除
- null
- undefined
- “”
- NaN +0 -0
- false
为false外,其他均为真。
转换到数值规则
- Undefined 类型的值转换为 NaN
- Null类型转化值为0
- Boolean类型true为1,false为0
- String类型若只含数字则直接转化并保留有效数字,(利用parseInt(进行显式转化时,若选择进制,则字母等也会被认为是数值))若含非数字字符串(除’+‘,’-'外),返回NaN.
- Symbol类型不能转换为数字
转换到字符串规则
- Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”
- Boolean 类型,true 转换为 “true”,false 转换为 “false”
- Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。(这个转换若调用toString()转换还可以选择进制转换,2~36进制均可选择。)
- Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
这里附上常见值类型转换表:
值 | 转String | 转Number | 转布尔值 |
---|---|---|---|
undefined | “undefined” | NaN | false |
null | “null” | 0 | false |
true | “true” | 1 | |
false | “false” | 0 | |
“” | 0 | false | |
“1.2” | 1.2 | true | |
“one” | NaN | true | |
0 | “0” | false | |
-0 | “0” | ||
1 | “1” | true | |
Infinity | “Infinity” | true | |
-Infinity | “-Infinity” | true | |
NaN | “NaN” | false |
原始值
原始值是固定而简单的值,是存放在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置.详解可见js中的原始值是什么?
toString()与valueOf()
toString()方法的作用是返回对象的字符串表示。普通对象调用该方法会返回"[object Object]"。而很多类却封装了自己独有的toString()方法。例如(这里只介绍对象类型,基本类型前文已介绍):
- Array的toString方法其实就相当于
join(",")
,它是将数组的每个元素都转化为字符串,再使用逗号作为分隔符将他们连接起来; - Function的toString()方法就将用户定义的函数的转化为源代码的字符串;
- Date类定义toString()方法会返回一个人类友好(且js可解析)的日期和时间的字符串;
- RegExp类定义的toString()方法会将正则对象转化为一个看起来像正则字面量的字符串。
[1,2,3].toString() // "1,2,3"
(function() {}).toString() // "function () {}"
/\d+/g.toString() // "/\\d+/g"
let d = new Date(2022,2,25)
d.toString() // 'Fri Mar 25 2022 00:00:00 GMT+0800 (中国标准时间)'
valueOf()方法的作用对象转化为代表对象的原始值,一般普通对象调用该方法会返回对象本身,但对于以Number(),String(),Boolean()为构造函数的包装类对象会返回被包装的原始值;对于Array,Function,RegExp对象类型与普通对象类型处理一样。但对于Date类型而言,返回的是日期内部的日期表示,相当于调用getTime()
方法。
let d = new Date(2022,2,25)
d.valueOf() // 1648137600000
d.valueOf() === d.getTime() // true
对象(含Function类型)到原始值转换算法(注意这里与类型转换的区别)
对象转化为其他类型的时较为复杂,js内部共定义了三种对象到原始值的基本算法:
偏字符串
该算法返回原始值,只要有可能就返回字符串。
- 该方法会优先尝试调用toString()方法,若该方法有定义且返回的值为原始值,则使用该值。否则下一步;
- 尝试调用valueOf()方法,若该方法有定义且返回值为原始值,则使用该值,否则下一步;
- 抛出type error错误。
偏数值
该算法返回原始值,只要有可能就返回数值。
该方法与偏字符串算法类似,但是先调用valueOf()方法再调用toString()方法。
无偏好
该算法不倾向于任何原始值类型,而是由类自己定义转换规则,js内部除了Date类实现了偏字符串算法,其余都实现了偏数值算法。
对象各种类型隐式转换规则
对象到布尔值
这个其实已经在上文介绍过,除了那几个值外其余均为true,包括包装对象new Boolean(false)
。
对象到字符串
在将对象转化为字符串时,会先使用偏字符串算法先将该对象转化为一个原始值,再将该原始值转化为字符串。
对象转化为数值
在将对象转化为数值时,会先使用偏数值算法先将该对象转化为一个原始值,再将该原始值转化为数值。
例子解释
Number([]) // 0
Number([9]) // 9
Number(function a() {}) // NaN
Number({}) // NaN
let date = new Date(2022,2,25)
date == date.toString() // true
date == date.valueOf() // false
0 == new Number(0) // true
- 首先’[]'的类型为对象类型,会先使用偏数值算法转化原始值的方法,调用valueOf()方法,而Array继承了默认的valueOf()方法,返回的数组本身,并不是原始值,因此调用toString()方法;因此
[].toString()
是"“,[9].toString()
是"9”。再转化为Number类型,即 0 与 9. - 首先function是Function类型实例对象,会先使用偏数值算法转化原始值的方法,调用valueOf()方法,而Array继承了默认的valueOf()方法,返回函数本身,并不是原始值,因此调用toString()方法;是
"function a() {}"
,再转化为Number类型便是NaN。 - 首先’{}'的类型为对象类型,会先使用偏数值算法转化原始值的方法,调用valueOf()方法,而Obeject默认的valueOf()方法,返回的对象本身,并不是原始值,因此调用toString()方法;返回值为"[object Object]",再转化为数值自然就是NaN.
- 由于是’=='操作符,首先date为对象,使用无偏好原则,Date类型自带的偏向字符串原则,因此先调用
date
的toString()
方法,得出该值是原始值,因此转换类型返回的是date.toString()
,因此toString()
为true,valueOf()
为false
。 - 由于是’=='操作符,首先
new Number(0)
为对象类型,使用无偏好原则,Number
使用的是偏数值原则,因此先调用valueOf
方法,得出该值0
是原始值,因此转换类型返回的是0
,与左侧值相等,因此返回为true
。