在平时的开发中,我们经常会遇到多种运算符在同一个表达式中出现的情况,尤其是在三元条件判断运算符中。
三元条件判断运算符虽然可以让我们避免写过多的if...else条件判断,但多层三元运算符嵌套,其中又包含其他不同优先级的运算符时,对于阅读我们代码的人来说,简直就是噩梦。
今天我们就结合一个现实中经常用到的工具函数 isEmpty() 的实现,来讲解一下如何解读复杂的运算符嵌套
isEmpty 是 著名的 loadsh 库提供的一个工具方法,被应用于判定一个javascript 对象是否为空对象。
对于空对象,loadsh 是这么解释的:
如果【对象没有自己的可枚举字符串键控属性】,则认为它们是空的,
如果参数、对象、缓冲区、字符串或类似于jquery的集合等【类似数组的值的长度为0】,则认为它们为空。
类似地,如果【映射和集合的大小为0】,则认为它们是空的
isEmpty = function (val) {
return !(!!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length : true : false);
}
javascript 的运算符优先级可以参考MDN上的说明,如下图:
我们再看内部实现代码,其中val
为要判断是否为空对象的值:
return !(!!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length : true : false);
现在根据运算符优先级一步一步解读运算过程
return
后面应该是一个表达式的值,我们假定这个值为X,则整个表达式可以看做: var X = !(...);
return X;
!(...)
X = !Y
Y = !!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length :true :false
Y = !!val ? typeof val === 'object' ? (true/false) ? !!(0/1/2/.../N) : !!(0/1/2/.../N) :true :false
因为我们这里对val
的值不一定,所以这里对 Array.isArray(val)
和 Object.keys(val).length
最终的计算结果有多种可能,我用/
号隔开了各种可能值,并且将他们放入同一个括号内,如果是正式的计算,我们传入的val
是一个确定的值,那么这些运算结果也会是一个确定值,并且也不会有括号。
typeof
运算符的优先级都是16,是剩余运算符中最高的,所以对这两种进行运算,结果如下: Y = (true/false) ? (string/object/boolean/null/undefined)==='object' ? (true/false) ? (true/false) : (true/false) : true :false
这个地方比较绕,因为!!
会将后面的值强制转换为布尔值,所以最后的结果几乎都是由 true
或false
组成的了。
Y = (true/false) ?(true/false) ? (true/false) ? (true/false) : (true/false) : true :false
结合性
了,我们发现条件运算符的结合性是从右至左,那么我们的表达式就变成了: Y = (true/false) ? ... : false
我们回溯以下这里面的 (true/false)
其实就是原始表达式中 !!val
的运算结果,然而这里还无法运算出整个表达式的结果,因为 ...
所代表的那部分还不是一个最终值,还需要运算,记得最开始的做做法吗?对于!(...)
这个表达式,我们将括号内的表达式用Y
来代替了,同样地,我们把这里 ...
所代表的表达式部分用一个字母M
来代表,即:
M = (true/false) ? (true/false) ? (true/false) : (true/false) : true;
Y = (true/false) ? M : false;
!!val
的值为false
,则直接返回 false
(即括号后面的值),后面的M中的表达式就不再运算了。那么此时 Y=false
, 而 !Y
相当于取反,X = !Y
的值就等于true
。我们这个方法是用来判断是否为空对象的,返回结果为true
,就说明这个val
是空对象。 !!val === false
的 val
都有哪些呢? 0
,""
,false
,null
,undefined
符合这个特征,我们发发现,它们都是javascript中的‘假值’。 !!val
的值为 true
呢,则需要返回M表达式的结果,我们就需要继续计算M表达式的值了。 M
表达式的运算过程,依葫芦画瓢,我们可以得到: M = (true/fasle) ? ... : true
通过回溯,我们可以知道,这里的(true/false)
其实就是原始表达式中的typeof val === 'object'
的最终运算结果。
同样的,我们将 ...
内的内容使用字母 N
代替,结果如下:
N = (true/fasle) ? (true/fasle) : (true/false)
M = (true/false) ? N : true;
* 如果`typeof val === 'object'` 的值为`false` , 即说明`val`不是对象类型,则直接返回`true`(冒号后面的值),不需要再运算N表达式的结果。此时 Y = true, 则 X= !Y=false, 最终值为`false` ,说明`val` 不是空对象。
* 如果`typeof val === 'object'` 的值为 `true` 则需要返回N表达式的值作为结果,计算机需要计算运算N表达式的值。
N
表达式,其中有三个布尔值,通过回溯,我们也可以知道他们的原始表达式分别是: Array.isArray(val)
!!val.length
!!Object.keys(val).length
那么我们知道,这一步当val
为对象类型时,则需要判断它是数组还是非数组:
!!
操作
0
,则操作结果为false
, 返回后,Y=false,X=!Y=true,说明 长度为0的数组为空对象 true
,将结果返回后,Y=true, X=!Y=false,说明长度大于0的数组不属于空对象 Object.keys(val).length
),并对长度做!!
操作
0
,则操作结果为false
, 返回后,Y=false,X=!Y=true,说明 可枚举属性长度(个数)为0的对象为空对象 true
,将结果返回后,Y=true, X=!Y=false,说明可枚举属性长度大于0的对象不属于空对象 至此,我们按照程序执行的顺序步进似的完成了整个运算过程的模拟,我们学到了以下几点:
理解运算过程对我们理解整个程序的实现逻辑和作者的思维方式至关重要,希望以上分析过程可以在大家阅读知名框架中大神级代码时对大家有所帮助。
本文由博客一文多发平台 OpenWrite 发布!