function與感嘆號之插件開發

最近有空可以讓我靜下心來看看各種代碼,function與感嘆號的頻繁出現,讓我回想起2個月前我回杭州最後參加團隊會議的時候,@西子劍影拋出的一樣的問題:如果在function之前加上感嘆號 (!) 會怎麼樣?比如下面的代碼:

!function(){alert('iifksp')}()        // true

在控制檯運行後得到的值時true,爲什麼是true這很容易理解,因爲這個匿名函數沒有返回值,默認返回的就是undefined,求反的結果很自然的就是true。所以問題並不在於結果值,而是在於,爲什麼求反操作能夠讓一個匿名函數的自調變的合法?

平時我們可能對添加括號來調用匿名函數的方式更爲習慣:

(function(){alert('iifksp')})()        // true

或者:

(function(){alert('iifksp')}())        // true

雖然上述兩者括號的位置不同,不過效果完全一樣。

那麼,是什麼好處使得爲數不少的人對這種歎號的方式情有獨鍾?如果只是爲了節約一個字符未免太沒有必要了,這樣算來即使一個100K的庫恐怕也節省不了多少空間。既然不是空間,那麼就是說也許還有時間上的考量,事實很難說清,文章的最後有提到性能。

回到核心問題,爲什麼能這麼做?甚至更爲核心的問題是,爲什麼必須這麼做?

其實無論是括號,還是感嘆號,讓整個語句合法做的事情只有一件,就是讓一個函數聲明語句變成了一個表達式

function a(){alert('iifksp')}        // undefined  

這是一個函數聲明,如果在這麼一個聲明後直接加上括號調用,解析器自然不會理解而報錯:

function a(){alert('iifksp')}()        // SyntaxError: unexpected_token  

因爲這樣的代碼混淆了函數聲明和函數調用,以這種方式聲明的函數 a,就應該以 a(); 的方式調用。

但是括號則不同,它將一個函數聲明轉化成了一個表達式,解析器不再以函數聲明的方式處理函數a,而是作爲一個函數表達式處理,也因此只有在程序執行到函數a時它才能被訪問。

所以,任何消除函數聲明和函數表達式間歧義的方法,都可以被解析器正確識別。比如:

var i = function(){return 10}();        // undefined  
1 && function(){return true}();        // true  
1, function(){alert('iifksp')}();        // undefined  

賦值,邏輯,甚至是逗號,各種操作符都可以告訴解析器,這個不是函數聲明,它是個函數表達式。並且,對函數一元運算可以算的上是消除歧義最快的方式,感嘆號只是其中之一,如果不在乎返回值,這些一元運算都是有效的

!function(){alert('iifksp')}()        // true
+function(){alert('iifksp')}()        // NaN
-function(){alert('iifksp')}()        // NaN
~function(){alert('iifksp')}()        // -1

甚至下面這些關鍵字,都能很好的工作:

void function(){alert('iifksp')}()        // undefined  
new function(){alert('iifksp')}()        // Object  
delete function(){alert('iifksp')}()        // true  

最後,括號做的事情也是一樣的,消除歧義纔是它真正的工作,而不是把函數作爲一個整體,所以無論括號括在聲明上還是把整個函數都括在裏面,都是合法的:

(function(){alert('iifksp')})()        // undefined
(function(){alert('iifksp')}())        // undefined

說了這麼多,實則在說的一些都是最爲基礎的概念——語句,表達式,表達式語句,這些概念如同指針與指針變量一樣容易產生混淆。雖然這種混淆對編程無表徵影響,但卻是一塊絆腳石隨時可能因爲它而頭破血流。

最後討論下性能。我在jsperf上簡單建立了一個測試:http://jsperf.com/js-funcion-expression-speed ,可以用不同瀏覽器訪問,運行測試查看結果。我也同時將結果羅列如下表所示(由於我比較窮,測試配置有點丟人不過那也沒辦法:奔騰雙核1.4G,2G內存,win7企業版):

Option Code Ops/sec
Chrome 13 Firefox 6 IE9 Safari 5
! !function(){;}() 3,773,196 10,975,198 572,694 2,810,197
+ +function(){;}() 21,553,847 12,135,960 572,694 1,812,238
- -function(){;}() 21,553,847 12,135,960 572,694 1,864,155
~ ~function(){;}() 3,551,136 3,651,652 572,694 1,876,002
(1) (function(){;})() 3,914,953 12,135,960 572,694 3,025,608
(2) (function(){;}()) 4,075,201 12,135,960 572,694 3,025,608
void void function(){;}() 4,030,756 12,135,960 572,694 3,025,608
new new function(){;}() 619,606 299,100 407,104 816,903
delete delete function(){;}() 4,816,225 12,135,960 572,694 2,693,524
= var i = function(){;}() 4,984,774 12,135,960 565,982 2,602,630
&& 1 && function(){;}() 5,307,200 4,393,486 572,694 2,565,645
|| 0 || function(){;}() 5,000,000 4,406,035 572,694 2,490,128
& 1 & function(){;}() 4,918,209 12,135,960 572,694 1,705,551
| 1 | function(){;}() 4,859,802 12,135,960 572,694 1,612,372
^ 1 ^ function(){;}() 4,654,916 12,135,960 572,694 1,579,778
, 1, function(){;}() 4,878,193 12,135,960 572,694 2,281,186

可見不同的方式產生的結果並不相同,而且,差別很大,因瀏覽器而異。

但我們還是可以從中找出很多共性:new方法永遠最慢——這也是理所當然的。其它方面很多差距其實不大,但有一點可以肯定的是,感嘆號並非最爲理想的選擇。反觀傳統的括號,在測試裏表現始終很快,在大多數情況下比感嘆號更快——所以平時我們常用的方式毫無問題,甚至可以說是最優的。加減號在chrome表現驚人,而且在其他瀏覽器下也普遍很快,相比感嘆號效果更好。

當然這只是個簡單測試,不能說明問題。但有些結論是有意義的:括號和加減號最優。

但是爲什麼這麼多開發者鍾情於感嘆號?我覺得這只是一個習慣問題,它們之間的優劣完全可以忽略。一旦習慣了一種代碼風格,那麼這種約定會使得程序從混亂變得可讀。如果習慣了感嘆號,我不得不承認,它比括號有更好的可讀性。我不用在閱讀時留意括號的匹配,也不用在編寫時粗心遺忘——

當我也這麼幹然後嚷嚷着這居然又節省了一個字符而沾沾自喜的時候,卻忘了自己倉皇翻出一本卷邊的C語言教科書的窘迫和荒唐......任何人都有忘記的時候,當再撿起來的時候,撿起的就已經不單單是忘掉的東西了。

2011-10-31更新:如果你使用aptana,那麼在使用(!+-)時要注意一點,它們會讓aptana的解析失效,導致Outline窗口沒有任何顯示。但是就代碼本身而言,其運行沒有任何問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章