完全原创!码字好累啊!自己记录一下。
主要是解析,不会讲太多基础知识,不熟悉隐式转换的同学建议先阅读这篇文章:
先来道难题,解释以下结果返回值为何为 '10'
?看完全文,相信你能够解答。
++[[]][+[]]+[+[]]
复制代码
只要是前端方向的同学,应该看过这张JS作者的表情包:
接下来,我将一一分析这其中的原理。
不会很详细的讲解的知识点,而是设计到哪个说哪个。这其中大部分是JS的隐式转换问题。不了解这部分的同学,建议栈内搜索:JS隐式转换。
好了,我们开始。
-
typeof NaN
这个表达式返回结果为number
,其实就是 ECMAScript 的规定,也符合 IEEE 754 浮点运算标准的规定:(规定嘛,没啥好说的)# 4.4.27 NaN Number value that is an “Not-a-Number” value 复制代码
来自ECMA-262 文档:tc39.es/ecma262/#se…
-
9999999999999999
几个9
?不用数了,我替你们数好了,一共16
位十进制,为什么 16 位的9999999999999999
返回的结果是 17 位的10000000000000000
呢?这就涉及到 JS 的最大整数表示范围了,也就是再这个范围内进行运算可以保证精确。根据 IEEE754 标准,JS 的最大安全整数是2^53 - 1
,也就是Number.MAX_SAFE_INTEGER
。2^53-1
计算结果为9007199254740991
,一共是16
位十进制,而 16 位的9...9
显然已经超过了这个范围。 超过数字表示范围的计算来谈计算的都是流氓!// JS 最大安全整数 Number.MAX_SAFE_INTEGER === 2**53 - 1 复制代码
-
0.1+0.2==0.3
返回结果为false
,这个是比较经典的浮点数精度问题。很多文章都讲了这个,也是面试常考,我就简单说一下。
计算机内部以二进制存储数字,JS在计算数字加法的时候,会先转换成二进制再进行计算,而,0.1
和0.2
的二进制相当于一个无限循环小数,两者相加后,计算机只能在精确到某一位,然后舍弃后面的位数,然后这两个数的二进制在相加后又要进行舍弃,就不等于0.3
了,而是0.30000000000000004
。 -
Math.max()
对于外行人,肯定一脸蒙,最大值居然返回无穷小?
但对于学JS的人,我们都知道Math.max()
接收一个数组,然后返回这个数组中最大的数值。那么当参数为空的时候为什么返回-Infinity
呢?看起来很疑惑,求最大值却返回一个无穷小。
这也是 JS 底层设计的,就是说编写这门语言的时候就是这么规定的。其实仔细想想也挺合理。例如我们要求一个最大值,我们一般设置一个变量_max
,遍历待求的数字序列中,如果有比_max
大的就替换掉_max
。那么如何保证后面的数字第一次和_max
比较的时候能比_max
大呢?这时候就想到了如果_max
是一个无穷大的值,那么它未来必定会被替换掉。(有点强行解释的味道hhh)
至于Math.min()
返回Infinity
,理由同上。 -
[]+[]
好家伙,两个空数组相加,返回一个空字符串""
。 且听我慢慢分析。在JS中,两个对象相加,会触发 隐式转换。
这个隐式转换肯定要有规则,对象的隐式转换规则就是
ToPrimitive
规则。这里不展开说 ToPrimitive 规则了,不了解的自行站内搜索。现在你只需要知道,两个对象在进行+
相加(可理解为拼接)运算的时候会根根据这个规则进行隐式转换。ToPrimitive 规则:则对象强制转为原始类型,首先查找对象自身是否有
valueOf
方法,若有则执行该方法;若没有则执行toString
方法(有些情况例如 Date 对象先执行toString
)。对于空数组
[]
,执行[].valueof()
,返回结果依然为[]
,不是一个原始类型,则执行[].toString()
方法,JS 底层会根据ToString
规则(注意不是toString
,而是其他类型转字符串类型的规则),结果会返回""
。那么结果很明显了,两个空字符串相加,结果自然为""
。后面很多分析都要用到隐式转换及 ToPrimitive 转换规则,不了解的可以自行搜索。
-
[]+{}
计算结果为"[object Object]"
。分析步骤与上一个类似:两对象相加,触发ToPrimitive
转换,首先执行valueOf()
方法(如果有),空对象的valueOf()
依然返回{}
,则执行toString()
,根据ToString
规则,一个普通对象转换为一个字符串的结果为"[object Object]"
。
经上述分析,[]
隐式转换为""
,{}
转换为"[object Object]"
,字符串一拼接,结果即为"[object Object]"
-
{}+[]
这个和前面就换了个位置,结果就变为0
了?此时你肯定要非常想吐槽 JS 是什么垃圾语言。其实这个是 JS 解析策略的问题,对于一行代码开头的{}
,JS 可以解析为一个空对象,也可以解析为一块 代码块,这块代码块为空,什么都不做。于是表达式就可以简化为+[]
,关键来了,+
这个操作符在做二元操作符时,表示相加或者拼接,在做单目运算符的时候表示正的,例如+0
,也可以把一个非整数类型转换为一个整数,相当于Number()
方法。单目运算符+
会触发 ToNumber 隐式转换。
现在来看+[]
。ToNumber 规则规定,对象或者数组转为数字类型,首先要经过
ToPrimitive
规则转换。对于空数组
[]
的 ToPrimitive 转换,我们已经分析过了,结果为空串""
。那么空串的 ToNumber 转换(也就是Number("")
)结果是啥呢?根据 ToNumber 规则,结果为0
,也即最终结果。 -
true+true+true
结果为3
。我们现在逐渐熟悉这种分析过程了。非数值的原始类型进行相加,JS也设定了相应的转换规则(这里主要讲数字、布尔、字符串、null、undefined这五类。):- 布尔、数字、null、undfined类型之间相加,全部转换为数字类型再相加(
Number(undefined)
为NaN
) - 字符串和其他类型相加,全部转换为字符串,再拼接
true+true+true
满足第一条,全部转换为数字类型:1+1+1===3
。
其实类似的还有null+true===1
、null+'123'===null123
,都可以使用上述规则解。 - 布尔、数字、null、undfined类型之间相加,全部转换为数字类型再相加(
-
true-true
结果为0
。这里涉及到减法了。JS 对于非数值的原始类型(主要讲数字、布尔、字符串、null、undefined)之间的减法也有特殊规则:对于非数值的原始类型之间的减法,只需要记住一点:
先全部转换为数值类型,然后再做减法。例如'100'-true === 100 - 1 === 99
注意Number(null)===0
,Number(undefine)
为NaN
。true-true
=> 全部转换为数值:1 - 1 === 0
-
true==1
结果为true
。同样的,在等于比较==
时候,会触发隐式转换,等于号隐式转换规则:- 布尔类型和其他类型的相等比较,布尔先转为数字类型
- 数字类型和字符串类型的相等比较,字符串类型先转数字类型
- 对象类型和原始类型的相等比较,对象类型会依照
ToPrimitive
规则转换为原始类型 null
、undfined
和其他值比较null
和undfined
以及自身相等==
,和其他值不相等。(ECMAScript 规定不再触发转换)undfined
和null
以及自身相等==
,和其他值不相等。
true==1
=>1==1
=>true
-
true === 1
结果为false
。全等号===
没有隐式转换,直接比较类型和值。 -
(!+[]+[]+![]).length
结果为9
。咱们按照ToPrimitive
规则一步一步分析。
取反操作符!
和弹目运算符+
优先级高,先运算。结果:// +[] 为 0,![] 为 false 原式 => !0 + [] + false => true + [] + false => true + '' + false => 'truefalse' 复制代码
最后,结果为一个字符串
'truefalse'
,长度为9
。 -
对于
9+"1"
和"91"-1
,看上述第8、9条规则。 -
[]==0
结果为true
。这种情况属于对象和原始类型之间的==
比较。
[]
进行 ToPrimitive 隐式转换,结果为""
现在进行原始类型之间的比较:""==0
。
根据规则:数字与字符串进行比较时,会尝试将字符串转换为数字值。
""==0
=>0==0
=>true
宽松等于比较
==
规则:MDN ==
看吧,看起来不合理的东西,只是你不了解它罢了。
欢迎纠错!!