javascript这门语言的类型系统从来没有它表面看起来的那样和善,虽然比起java、c#等一众强类型语言,它的弱类型使用起来似乎是如此便利,但正因为它极高的自由度,所以才会衍生出令人摸不着头脑的荒诞行为。
举个例子,虽然我们都知道一个包含内容的字符串会被认为是“真值 truthy”(因为除了空字符串之外任何字符串在js里都被认为真值),但当你做如下比较的时候,你会得到一个惊掉下巴的结果
const a = "18";
const b = true;
a == b // false
什么鬼,一个被通常理解成真值的值,竟然无法与布尔真值松散相等?
为了能拨开javascript类型的迷雾,让头铁的我们一点一点理顺javascript整个类型系统的工作逻辑。
读者可以根据自己对js类型系统的掌握程度,选择性的阅读这篇博客
类型基础
javascript有以下八大类型,除了object
类型,其他都为基本类型
number
string
boolean
null
undefined
object
symbol
bigint
他们的类型都可以直接被typeof
识别,特例是
typeof null
为"object"
虽然它是null
类型的值typeof function(){}
为"function"
虽然它理论上是object
类型的
虽然你可能已经为这种特例所不解,但其实这才刚开始,大的还在后面
类型转换
转换为数字
javascript内部有一套抽象的数字转换机制叫tonumber
,这套机制在隐式转换或者部分显式转换其他类型值到数字时会被调用。虽然你可能会被恶心到,但我还是要向你介绍这套机制的规则为
string
若为数字表达式则转换为其对应数字,否则返回nan
undefined
转换为nan
null
转换为0
true
转换为数字1
false
转换为数字0
object
类型会依次调用toprimitive()
、valueof()
和tostring()
来获取值,并利用上面的规则获取其数字值
tonumber
的转换规则会在以下情况下使用,当然这些情况也可以称作转换数字的“技巧”,看你怎么理解它了:
number(value)
value
math.floor(value)
value * x
,将一个值做乘法运算
没错,
math.floor(true)
、true
、true * 1
都等于1,是不是觉得很荒诞?
但
'5' 3
或者5 '3'
结果都是字符串'53'
,因为在有两个操作值,且其中一个为字符串时,会直接做字符串拼接。所以虽然
'3'
结果为数字3,但5 '3'
的结果不是8
parseint()
或parsefloat()
只接受string
类型,所以转换规则与tonumber
转换机制下的string
类型情况相似,但是在处理字符时采取从左到右的扫描直到失败为止的方法,所以parseint("123hello")
结果为123
转换为布尔
javascript还有一套针对布尔类型的抽象转换机制叫toboolean
。因为对于前端逻辑编写来讲,判断一个值是否为真实在太重要了,javascript里变量像薛定谔的猫一样,处于存在与不存在、真和假的中间态,所以我们js开发者都有一个奇怪的脑回路,当看到一个字面量值的时候就开始评估它是“真值”(trusy value)还是“假值”(falsy value)。
可是,与物理学里薛定谔的猫现象相反,javascript里对真假值的定义其实很简单,以下的值均为假值:
undefined
null
false
0
、-0
、0n
和nan
""
其他均为真值,任何非空字符串、非0数字、对象都是真值。
转换其他类型值为布尔类型的方法:
!!value
boolean(value)
!
,可将值转换为布尔类型,但是真假结果相反
会隐式的将其他类型值转换为布尔的情况:
&&
||
if (value)
while (value)
for (...; value; ...)
,for循环的第二个测试表达式? :
,三元操作符
恭喜你勇士,读到这里就代表马上你就能知道为什么"18" != true
了!!!
等价性
我们都知道,js当中有松散判断的弱等价==
,和严格判断的强等价===
两种判断等价方式。强等价要求两个操作值必须为同一类型,且值本身也相等,其行为非常容易预测。弱等价在比较值是否相等前会尝试做一些类型转换,尽可能的让可能为不同类型的两个值变得可以判断。
造成文章开头神奇判断结果的原因就在弱等价==
时的类型转换策略上,听我将弱等价的转换规则为你娓娓道来
- 数字与字符串比较,则将字符串转换为数字
- 布尔值与任何其他值作比较,都先将布尔值转换为数字
null
与undefined
松散比较结果相等- 对象与数字或字符串比较,先调用对象的
toprimitive()
获取原始类型值后进行比较
所以,"18" == true
进行比较时
- 由于布尔值的比较规则为将布尔值转换为数字,
tonumber(true)
结果为数字1
,表达式变为"18" == 1
- 又因为字符串与数字比较时字符串应转换为数字,则
tonumber("18")
结果为数字18
- 最后表达式实际比较
18 == 1
,结果为假false
所以没错,以下表达式均为真:
"1" == true
,"0" == false
,false == ""
结语
了解这个例子后你可能会对javascript语言行为设计混乱表达不满,但是上述边界条件可以通过引入typescript,避免弱等价判断转而使用严格等价,在隐式类型转换之前使用可预测行为的转换方法对值提前进行处理。
如果你无法采用上面列举的解决办法,那我的建议就是多多关注我的博客,如果觉得不错可以推荐给你的朋友或同事,说不定就能在身边慢慢带动大家采用更令人舒心的写法嘞。