今天在空间timeline上看见一张图片,描述了JavaScript作为一门“神奇”的语言有着许许多多所谓的“特性“,下面让我们来一一探索一下。(参考书:犀牛书)
1. 抽风的数字类型
1.1. NaN
> typeof NaN
<· "number"
NaN
是js预定义的全局变量,用以表示0/0的结果。而其被定义为number类型。也可以通过
new Number(NaN)
这样的操作来构建NaN对象。
补充说明:经过评论区指出,NaN的定义是由IEEE754浮点数标准直接给出的,这个和js半毛钱关系都没有。
关于typeof,还有一个有趣的结论应该就是
> typeof null
<· "object"
这是由于null的开头第一位的内部编码和object一样的原因,不然就会是undefined
。
1.2. 迷之相等
> 9999999999999999
<· 10000000000000000
> 0.5+0.1==0.6
<· true
> 0.1+0.2==0.3
<· false
首先,js真的不能进行精确的计算,如果涉及精确的计算,建议自行设计高精度算法。要问为什么,那就是因为js没有专门的整数类型,它的所有number类型(NaN等奇怪的东西)除外,都是浮点数。
js完全采用IEEE754标准的64位方案来表示其数字,从而,根据犀牛书所指出的(也可以根据IEEE754标准推算),其能够准确表示的整数范围只到9007 1992 5474 0992 (这里正好16个数),而16个9,就有着被表示成1*10^16的危险。然而,在js它parseNumber的时候,在后面的幂大于20的时候才会显示成科学计数法。
> 999999999999999990000
<· 1e+21
整数部分尚且可以在二进制下精确表达,但是小数部分,则是真的很容易产生误差。二进制下的小数部分,只能精确表示能被拆分成(1/2)^n的和的部分,剩下的部分,就或多或少有着偷工减料了。
> 0.5+0.1
<· 0.6
> 0.1+0.2
<· 0.30000000000000004
这同时也意味着在处理小数是否相等时,js中需要进行的是差值在多少范围内的比较,而不能用相等直接比较。
1.3. 代劳了的初始化
> Math.max()
<· -Infinity
> Math.min()
<· Infinity
这个还真的不能算bug,平时题目做多了的人可能就注意到了这个用意,一般而言,最大最小值还真就是这么初始化的。为了方便得进行后续取最大和最小值的比较和判断。
2. 迷之类型转化
后面一整串,其实在犀牛书关于js的类型自动转化之中都有详细解答。所以都列在这里了:
> []+[]
<· ""
> []+{}
<· "[object Object]"
> {}+[]
<· 0
> true+true+true===3
<· true
> true-true
<· 0
> true==1
<· true
> true===1
<· false
> (!+[]+[]+![]).length
<· 9
> 9+"1"
<· "91"
> 91-"1"
<· 90
> []==0
<· true
但是首先,我们要来讲讲运算符。
+
加号在js中可以用作number类型的加法运算,也可以用来拼接string。所以参与加法运算的两个参数都需要将数据类型转化为对应的number或者是string。
而js中,只要参与运算的对象不全是number类型,就会发生各种迷之操作。
首先,对象会去调用各自的valueOf(),用valueOf的结果来继续运算(valueOf的结果各种各样,但是会是js的四种基本类型 string、bool、number、object之一),但是不幸的是,一般对象都不会有这个玩意儿,而可以进行valueOf()并且产生number返回值(就是那个距离创世日的毫秒数)
的日期对象,在这里会强制调用toString(),所以一般都会直接进入调用各自的toString()然后进行字符串拼接。(如果调用完valueOf()之后,加法两端可以进行数学意义上的加法,那么就会进行啦,不然还是字符串拼接)
js面对加法,没有不择手段的进行数学加法运算,而是不择手段的进行字符串拼接。
那么我们根据以上法则,一个个看过去。
[]+[]
[]
数组的valueOf()会直接返回""空字符串,所以结果是空字符串,没有任何毛病。
[]+{}
[]的valueOf()依然会返回空字符串,这没有任何问题。
而{}
作为object其valueOf()的结果依然是object,因此就会调用toString(),产生返回值"[object Object]"。从而拼接字符串,产生结果。
{}+[]
那么你来说说,这个反过来加怎么就可以得到数字了呢。这个我做了下实验,发现如果是对对象进行赋值的再做相加操作的话,正着和倒着的结果是一样的。但是直接输入以上的式子,结果确实是0。那么这里的{}
到底被当成什么了呢?花括号
,在这里的意思已经不是对象了,而是我们平时用来框语句块的花括号,代表的是一组空白表达式
。所以这里已经不是加法的世界了。相当于是在计算:
+[]
而+作为一元运算符的时候,右边的参数优先转化为数字,而[]会转化为0。
> true+true+true===3
<· true
> true-true
<· 0
> true==1
<· true
> true===1
<· false
这一串其实,这么看下来应该很好解释,true在做加法运算的时候能够转化成数字(能够成功的valueOf)从而得到数字1。那么1+1+1自然是严格等于3的(毕竟类型转化了,大家都是number)。所以其实这组其实是非常正常的运算结果。
java的相等==
是会考虑valueOf的,而===
严格相等是不容含糊的(虽然也会出现各种神奇的结果)。
那么下面这个式子就自己品味了吧。
> []==0
<· true
而下面这组式子也很好解释:
> 9+"1"
<· "91"
> 91-"1"
<· 90
加法优先考虑字符串连接,减法自然只存在强行计算。如果字符串转化成number失败,计算结果会是NaN。
那么最后来看看这个式子:
(!+[]+[]+![]).length
到底发生了什么
首先,逻辑非!
有着极高的优先级,所以首先计算的是!+[]和![]。
+[]的结果我们刚刚已经知道了是数字0,对其做逻辑非运算会将数字0直接转化为false
,然后再得到true
。
而对[]直接去逻辑非的结果呢?则是对[]直接转化为true
再求逻辑非,得到false
那么式子就变成了
(true+[]+false).length
之后如何计算就无需多言了。
只是觉得其中有的内容确实不错,但是有的内容过于钓鱼,所以甘愿咬勾,写下这篇文字。
工作中摸鱼,但愿不会被炒。