前言
在平時的開發(fā)中,我們經(jīng)常會遇到多種運算符在同一個表達式中出現(xiàn)的情況,尤其是在三元條件判斷運算符中。
三元條件判斷運算符雖然可以讓我們避免寫過多的if...else條件判斷,但多層三元運算符嵌套,其中又包含其他不同優(yōu)先級的運算符時,對于閱讀我們代碼的人來說,簡直就是噩夢。
今天我們就結(jié)合一個現(xiàn)實中經(jīng)常用到的工具函數(shù) isEmpty() 的實現(xiàn),來講解一下如何解讀復(fù)雜的運算符嵌套
isEmpty
isEmpty 是 著名的 loadsh 庫提供的一個工具方法,被應(yīng)用于判定一個javascript 對象是否為空對象。
空對象
對于空對象,loadsh 是這么解釋的:
如果【對象沒有自己的可枚舉字符串鍵控屬性】,則認為它們是空的,
如果參數(shù)、對象、緩沖區(qū)、字符串或類似于jquery的集合等【類似數(shù)組的值的長度為0】,則認為它們?yōu)榭铡?br> 類似地,如果【映射和集合的大小為0】,則認為它們是空的
isEmpty的實現(xiàn)
isEmpty = function (val) {
return !(!!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length : true : false);
}
解讀
運算符優(yōu)先級
javascript 的運算符優(yōu)先級可以參考MDN上的說明,如下圖:

運算過程解讀
我們再看內(nèi)部實現(xiàn)代碼,其中val為要判斷是否為空對象的值:
return !(!!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length : true : false);
現(xiàn)在根據(jù)運算符優(yōu)先級一步一步解讀運算過程
- 我們知道
return后面應(yīng)該是一個表達式的值,我們假定這個值為X,則整個表達式可以看做:
var X = !(...);
return X;
- 那么接下來就要先對賦值符號(=)右邊的表達式求值,即:
!(...)
- 可以看到,這個表達式有兩個運算符——邏輯非和括號,按照優(yōu)先級,括號的優(yōu)先級高于邏輯非,所以這里邏輯非要等到括號內(nèi)的內(nèi)容運算出一個結(jié)果,然后才能對這個結(jié)果進行邏輯非運算,我們假定括號內(nèi)的內(nèi)容最終運算的結(jié)果為Y,則可以寫作:
X = !Y
- 現(xiàn)在來看一下Y表達式的內(nèi)容:
Y = !!val ? typeof val === 'object' ? Array.isArray(val) ? !!val.length : !!Object.keys(val).length :true :false
- 接下來就有點復(fù)雜了,按照優(yōu)先級,按照這些運算符里優(yōu)先級最高的應(yīng)該是成員屬性訪問(.),那應(yīng)該第一步運算結(jié)果是這樣:
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 最終的計算結(jié)果有多種可能,我用/號隔開了各種可能值,并且將他們放入同一個括號內(nèi),如果是正式的計算,我們傳入的val是一個確定的值,那么這些運算結(jié)果也會是一個確定值,并且也不會有括號。
- 接下來,邏輯非和
typeof運算符的優(yōu)先級都是16,是剩余運算符中最高的,所以對這兩種進行運算,結(jié)果如下:
Y = (true/false) ? (string/object/boolean/null/undefined)==='object' ? (true/false) ? (true/false) : (true/false) : true :false
這個地方比較繞,因為!! 會將后面的值強制轉(zhuǎn)換為布爾值,所以最后的結(jié)果幾乎都是由 true 或false 組成的了。
- 接下來,整個表達式就只剩全等運算符(===)和三元條件運算符(... ? ... : ...)了, 從上表可知,全等運算符的優(yōu)先級要高一點,所以結(jié)果如下:
Y = (true/false) ?(true/false) ? (true/false) ? (true/false) : (true/false) : true :false
- 現(xiàn)在,我們的表達式里就只剩三元條件運算符和布爾值了,問題是這里有多個三元運算符嵌套,我們該從哪個開始計算呢? 現(xiàn)在就可以看
結(jié)合性了,我們發(fā)現(xiàn)條件運算符的結(jié)合性是從右至左,那么我們的表達式就變成了:
Y = (true/false) ? ... : false
我們回溯以下這里面的 (true/false) 其實就是原始表達式中 !!val的運算結(jié)果,然而這里還無法運算出整個表達式的結(jié)果,因為 ... 所代表的那部分還不是一個最終值,還需要運算,記得最開始的做做法嗎?對于!(...)這個表達式,我們將括號內(nèi)的表達式用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。我們這個方法是用來判斷是否為空對象的,返回結(jié)果為true,就說明這個val是空對象。 - 我們可以延伸一下,符合
!!val === false的val都有哪些呢?0,"",false,null,undefined符合這個特征,我們發(fā)發(fā)現(xiàn),它們都是javascript中的‘假值’。 - 那么如果
!!val的值為true呢,則需要返回M表達式的結(jié)果,我們就需要繼續(xù)計算M表達式的值了。
- 如果
- 現(xiàn)在我們再看
M表達式的運算過程,依葫蘆畫瓢,我們可以得到:
M = (true/fasle) ? ... : true
通過回溯,我們可以知道,這里的(true/false) 其實就是原始表達式中的typeof val === 'object' 的最終運算結(jié)果。
同樣的,我們將 ... 內(nèi)的內(nèi)容使用字母 N 代替,結(jié)果如下:
N = (true/fasle) ? (true/fasle) : (true/false)
M = (true/false) ? N : true;
-
到這里,計算機又開始判斷:
如果
typeof val === 'object'的值為false, 即說明val不是對象類型,則直接返回true(冒號后面的值),不需要再運算N表達式的結(jié)果。此時 Y = true, 則 X= !Y=false, 最終值為false,說明val不是空對象。如果
typeof val === 'object'的值為true則需要返回N表達式的值作為結(jié)果,計算機需要計算運算N表達式的值。
對于
N表達式,其中有三個布爾值,通過回溯,我們也可以知道他們的原始表達式分別是:
Array.isArray(val)!!val.length!!Object.keys(val).length
那么我們知道,這一步當val 為對象類型時,則需要判斷它是數(shù)組還是非數(shù)組:
- 如果是數(shù)組,則拿到數(shù)組的長度值,對長度值做
!!操作- 如果長度為
0,則操作結(jié)果為false, 返回后,Y=false,X=!Y=true,說明 長度為0的數(shù)組為空對象 - 其它長度結(jié)果為
true,將結(jié)果返回后,Y=true, X=!Y=false,說明長度大于0的數(shù)組不屬于空對象
- 如果長度為
- 如果不是數(shù)組,則取它的可枚舉屬性的長度(
Object.keys(val).length),并對長度做!!操作- 如果長度為
0,則操作結(jié)果為false, 返回后,Y=false,X=!Y=true,說明 可枚舉屬性長度(個數(shù))為0的對象為空對象 - 其它長度結(jié)果為
true,將結(jié)果返回后,Y=true, X=!Y=false,說明可枚舉屬性長度大于0的對象不屬于空對象
- 如果長度為
總結(jié)
至此,我們按照程序執(zhí)行的順序步進似的完成了整個運算過程的模擬,我們學(xué)到了以下幾點:
- 代換法。當表達式非常復(fù)雜時,可以按照運算符優(yōu)先級,使用變量代換法代換優(yōu)先級比較低的運算,先將注意力集中到優(yōu)先級比較高的運算上。
- 回溯法。步進代換和運算到最后,再無可代換運算時,就要開始回溯,對應(yīng)到原始表達式,一步步求解。
- 結(jié)合性。以前我們偏重運算符優(yōu)先級的分析,從這個例子的條件運算符三層嵌套的應(yīng)用,我們看到,在復(fù)雜表達式的分析中,運算符的結(jié)合性也是非常重要的分辨運算順序的參照標準。
理解運算過程對我們理解整個程序的實現(xiàn)邏輯和作者的思維方式至關(guān)重要,希望以上分析過程可以在大家閱讀知名框架中大神級代碼時對大家有所幫助。
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!