从isEmpty()方法深刻理解javascript运算符优先级

前言

在平时的开发中,我们经常会遇到多种运算符在同一个表达式中出现的情况,尤其是在三元条件判断运算符中。

三元条件判断运算符虽然可以让我们避免写过多的if...else条件判断,但多层三元运算符嵌套,其中又包含其他不同优先级的运算符时,对于阅读我们代码的人来说,简直就是噩梦。

今天我们就结合一个现实中经常用到的工具函数 isEmpty() 的实现,来讲解一下如何解读复杂的运算符嵌套

isEmpty

isEmpty 是 著名的 loadsh 库提供的一个工具方法,被应用于判定一个javascript 对象是否为空对象。

空对象

对于空对象,loadsh 是这么解释的:

如果【对象没有自己的可枚举字符串键控属性】,则认为它们是空的,
如果参数、对象、缓冲区、字符串或类似于jquery的集合等【类似数组的值的长度为0】,则认为它们为空。
类似地,如果【映射和集合的大小为0】,则认为它们是空的

isEmpty的实现

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);

现在根据运算符优先级一步一步解读运算过程

  1. 我们知道return 后面应该是一个表达式的值,我们假定这个值为X,则整个表达式可以看做:
var X = !(...);
return X;
  1. 那么接下来就要先对赋值符号(=)右边的表达式求值,即:
!(...)
  1. 可以看到,这个表达式有两个运算符——逻辑非和括号,按照优先级,括号的优先级高于逻辑非,所以这里逻辑非要等到括号内的内容运算出一个结果,然后才能对这个结果进行逻辑非运算,我们假定括号内的内容最终运算的结果为Y,则可以写作:
X = !Y
  1. 现在来看一下Y表达式的内容:
Y = !!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length :true :false
  1. 接下来就有点复杂了,按照优先级,按照这些运算符里优先级最高的应该是成员属性访问(.),那应该第一步运算结果是这样:
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是一个确定的值,那么这些运算结果也会是一个确定值,并且也不会有括号。

  1. 接下来,逻辑非和typeof运算符的优先级都是16,是剩余运算符中最高的,所以对这两种进行运算,结果如下:
Y = (true/false) ? (string/object/boolean/null/undefined)==='object' ? (true/false) ? (true/false) : (true/false) : true :false

这个地方比较绕,因为!! 会将后面的值强制转换为布尔值,所以最后的结果几乎都是由 truefalse 组成的了。

  1. 接下来,整个表达式就只剩全等运算符(===)和三元条件运算符(... ? ... : ...)了, 从上表可知,全等运算符的优先级要高一点,所以结果如下:
Y = (true/false) ?(true/false) ? (true/false) ? (true/false) : (true/false) : true :false
  1. 现在,我们的表达式里就只剩三元条件运算符和布尔值了,问题是这里有多个三元运算符嵌套,我们该从哪个开始计算呢? 现在就可以看 结合性 了,我们发现条件运算符的结合性是从右至左,那么我们的表达式就变成了:
Y = (true/false) ? ... : false

我们回溯以下这里面的 (true/false) 其实就是原始表达式中 !!val的运算结果,然而这里还无法运算出整个表达式的结果,因为 ... 所代表的那部分还不是一个最终值,还需要运算,记得最开始的做做法吗?对于!(...)这个表达式,我们将括号内的表达式用Y 来代替了,同样地,我们把这里 ... 所代表的表达式部分用一个字母M 来代表,即:

M = (true/false) ? (true/false) ? (true/false) : (true/false) : true;
Y = (true/false) ? M : false;
  1. 到这里,计算机就开始判断了:
    • 如果!!val 的值为false ,则直接返回 false (即括号后面的值),后面的M中的表达式就不再运算了。那么此时 Y=false, 而 !Y 相当于取反,X = !Y 的值就等于true 。我们这个方法是用来判断是否为空对象的,返回结果为true,就说明这个val 是空对象。
    • 我们可以延伸一下,符合 !!val === falseval 都有哪些呢? 0""false ,nullundefined 符合这个特征,我们发发现,它们都是javascript中的‘假值’。
    • 那么如果!!val 的值为 true呢,则需要返回M表达式的结果,我们就需要继续计算M表达式的值了。
  2. 现在我们再看M表达式的运算过程,依葫芦画瓢,我们可以得到:
M = (true/fasle) ? ... : true

通过回溯,我们可以知道,这里的(true/false) 其实就是原始表达式中的typeof val === 'object' 的最终运算结果。

同样的,我们将 ... 内的内容使用字母 N 代替,结果如下:

N = (true/fasle) ? (true/fasle) : (true/false)
M = (true/false) ? N : true;
  1. 到这里,计算机又开始判断:

    • 如果typeof val === 'object' 的值为false , 即说明val不是对象类型,则直接返回true(冒号后面的值),不需要再运算N表达式的结果。此时 Y = true, 则 X= !Y=false, 最终值为false ,说明val 不是空对象。

    • 如果typeof val === 'object' 的值为 true 则需要返回N表达式的值作为结果,计算机需要计算运算N表达式的值。

  2. 对于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 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容