最近有空可讓我靜下心來看看各類代碼,function與感嘆號的頻繁出現,讓我回想起2個月前我回杭州最後參加團隊會議的時候,@西子劍影拋出的同樣的問題:若是在function以前加上感嘆號 (!) 會怎麼樣?好比下面的代碼:javascript
!function(){alert('iifksp')}() // true
在控制檯運行後獲得的值時true,爲何是true這很容易理解,由於這個匿名函數沒有返回值,默認返回的就是undefined,求反的結果很天然的就是true。因此問題並不在於結果值,而是在於,爲何求反操做可以讓一個匿名函數的自調變的合法?java
平時咱們可能對添加括號來調用匿名函數的方式更爲習慣:chrome
(function(){alert('iifksp')})() // true
或者:express
(function(){alert('iifksp')}()) // true
雖然上述二者括號的位置不一樣,不過效果徹底同樣。編程
那麼,是什麼好處使得爲數很多的人對這種歎號的方式情有獨鍾?若是隻是爲了節約一個字符未免太沒有必要了,這樣算來即便一個100K的庫恐怕也節省不了多少空間。既然不是空間,那麼就是說也許還有時間上的考量,事實很難說清,文章的最後有提到性能。瀏覽器
回到核心問題,爲何能這麼作?甚至更爲核心的問題是,爲何必須這麼作?jsp
其實不管是括號,仍是感嘆號,讓整個語句合法作的事情只有一件,就是讓一個函數聲明語句變成了一個表達式。函數
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表現驚人,並且在其餘瀏覽器下也廣泛很快,相比感嘆號效果更好。
固然這只是個簡單測試,不能說明問題。但有些結論是有意義的:括號和加減號最優。
可是爲何這麼多開發者鍾情於感嘆號?我以爲這只是一個習慣問題,它們之間的優劣徹底能夠忽略。一旦習慣了一種代碼風格,那麼這種約定會使得程序從混亂變得可讀。若是習慣了感嘆號,我不得不認可,它比括號有更好的可讀性。我不用在閱讀時留意括號的匹配,也不用在編寫時粗心遺忘——