javascript 函式function前面的一元操作符, 感嘆號、小括號、一元操作符!()+-||
// 寫的最清晰易懂的一篇文章,贊
https://colobu.com/2014/10/13/javascript-function-and-unary-operators/
原來,使用括號包裹定義函式體,解析器將會以函式表示式的方式去呼叫定義函式。也就是說,任何能將函式變成一個函式表示式的作法,都可以使解析器正確的呼叫定義函式。而 ! 就是其中一個,而 + - || 都有這樣的功能。
另外,用 ! 可能更多的是一個習慣問題,不同的運算子,效能是不同的
segmentfault上的一個問題:
一般看JQuery外掛裡的寫法是這樣的:
(function($) { //... })(jQuery);
今天看到bootstrap的javascript元件是這樣寫的:
!function( $ ){
//...
}( window.jQuery );
為什麼要在前面加一個 " ! " 呢?
原來,使用括號包裹定義函式體,解析器將會以函式表示式的方式去呼叫定義函式。也就是說,任何能將函式變成一個函式表示式的作法,都可以使解析器正確的呼叫定義函式。而 ! 就是其中一個,而 + - || 都有這樣的功能。
另外,用 ! 可能更多的是一個習慣問題,不同的運算子,效能是不同的。
有一篇非常詳細的文章,特意轉載: http://swordair.com/function-and-exclamation-mark/
最近有空可以讓我靜下心來看看各種程式碼,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視窗沒有任何顯示。但是就程式碼本身而言,其執行沒有任何問題