在javaScript中,函數是一個很基礎的對象,同樣也是非常隨意,定義起來很隨意,用起來那是更加隨意。以下說明在javaScript中聲明一個函數那是多麽的隨意
function fun1() {//聲明一個函數}function() { //聲明一個匿名函數}var fun2 = function() {//聲明一個變數指向一個匿名的函數運算式}var fun3 = function fun4() {//聲明一個變數指向一個非匿名的函數運算式}function fun5() { return function() { //返回一個匿名函數 }}
那function和var的區別有哪些?我們不討論深奧的,我們看下哪些運行結果
alert(typeof fun1); //functionalert(typeof fun2); //undefinedalert(typeof fun3); //undefinedalert(typeof fun4); //functionalert(typeof fun5); //functionfunction fun1() {//聲明一個函數}function() { //聲明一個匿名函數}var fun2 = function() {//聲明一個變數指向一個匿名的函數運算式}var fun3 = function fun4() {//聲明一個變數指向一個非匿名的函數運算式}function fun5() { return function() { //返回一個匿名函數 }}
我們可以看到function比var還要優先。
我們之前說變數的時候已經說過,一個變數可以被反覆賦值,對於函數來說,這可以嗎?
對於習慣在靜態語言下寫代碼的人,看到如下代碼估計要氣憤了,這個是啥破代碼?
function fun1() { alert("Y");}function fun1() { alert("N");}
但事實上,這個代碼是可以執行的,執行的結果暗示我們函數的名稱會被自動的作為對象的一個屬性,函數體就是屬性的值了
fun1(); // nthis["fun1"](); // nthis.fun1(); // nwindow["fun1"](); // nwindow.fun1(); // n
現在我們把兩個因素合起來看看,function在所有執行代碼前先編譯,且後面的function會覆蓋前面的function定義,那麼,如下的代碼執行的是?
var input = 5;switch (input) { case 10: function fun1() { alert(10); } break; case 5: function fun1() { alert(5); } break; default: function fun1() { alert("default"); } break;}
令人惱怒的是,在chrome和IE下執行的是我們前面的推斷,function在所有執行代碼前先編譯,且後面的function會覆蓋前面的function定義結果是
fun1(); //default
而FireFox把if中的語句作為執行快,運行時才編譯,所以他的結果是
fun1(); //5
為瞭解決這個問題,你需要吧動態分配的函數以函數的運算式來執行,避免編譯器對函數還沒有執行到就已經分配了,比如
var input = 5;switch (input) { case 10: fun1 = function() { alert(10); } break; case 5: fun1 = function() { alert(5); } break; default: fun1 = function() { alert("default"); } break;}
說到函數的運算式,那什麼是函數的運算式呢?簡單的說,在右邊的都是函數的運算式
var fun1 = function() {//函數運算式}var fun1 = function fun2() {//函數運算式}var obj = { Do: function() {//函數運算式 }}new function() {//函數運算式}function fun3() { return function() { //函數運算式 }}
還有兩種就是在()內和【】內,原諒我為了看的清楚了些,用了中文的【】,具體看下代碼就ok了
(function() { });[function() { } ];
函數運算式和一般的運算式的值一樣,都是執行到的時候才編譯,不過有一個例外,就是非匿名的函數運算式
var fun3 = function fun4() {//聲明一個變數指向一個非匿名的函數運算式}
上面的代碼如果你沒有特定的含義,求你最好不要這麼寫,因為會出現你想不到的情況
alert(fun3 === fun4);
上面的結果是無論如何都超過了初學者的想法:在IE中式false,在Chrome或FireFox中是錯誤
對於Chrome或FireFox原因比較明顯,也就是說,fun4被賦值後就立即拋棄了
alert(typeof fun3); //functionalert(typeof fun4); //undefined
在IE中
alert(typeof fun3); //functionalert(typeof fun4); //function
這一點是IE的錯誤,但是從結果可以得知:命名函數運算式的名字在外部範圍是無效的。哪怕如下,你執行下fun3還是不行的
var fun3 = function fun4() {//聲明一個變數指向一個非匿名的函數運算式}fun3();alert(typeof fun3); //functionalert(typeof fun4); //undefined
雖然IE是有錯誤,但是IE透露了一個資訊,非匿名的函數運算式由於函數有名稱,被優先的分配了。看看我們最上面的函數定義聲明的案例中的fun4
這麼一來,如果你一不小心的話
function fun1() { alert("A");}var count = 2;var input = 10;switch (input) { case 5: function fun1() { alert(5); } break; case 10: if (count % 2 == 0) { function fun1() { alert("odd"); } } else { function fun1() { alert("even"); } }}fun1();
這些代碼的執行會不會出乎你的意料呢?注意上面的代碼在不同的瀏覽器中得到的是不同的。再看看下面的代碼
var fun1 = function(x) { if (x < 10) { return function fun2() { alert("min"); } } return function fun2() { alert("max"); }}fun1(4)();fun1(10)();
你是不是認為執行的結果又min也有max在不同的瀏覽器中,錯了,這次他們都很正常的預期。說明return將後面的非匿名函數運算式有效讓編譯器作為運算式而不是聲明來看待了。
那麼非匿名的函數運算式有什麼問題嘛?
我們來看一個demo
var fun1 = function fun2() { alert("OK");}fun1(); // OKfun2(); //在IE中彈出OK,在Chrome和FireFox中錯誤
既然只有IE支援,那我們在IE上繼續點實現,學習一個種語言,千萬不要被書上的條條框框限制,最好是多多多多的天馬行空的亂想點不正經的代碼出來,有靜態語言經驗的人都會被下面的代碼的執行結果嚇死
var fun1 = function fun2() { alert("OK");}fun1 = function() { alert("fun1");}fun1(); //fun1fun2(); //OK
fun1和fun2竟然不是指向同一個對象,或者說記憶體位址。所以,如果說這個是IE的bug,那麼我們幸好Chrome和FireFox不支援非匿名的函數運算式具有實在的意義,上帝保佑,這樣我們儘可能的不要寫出非匿名的函數運算式就可以避免很多問題了。不過也有說法是說,非匿名的函數運算式在遞迴的時候有用,以下代碼告訴我們匿名函數運算式也一樣可以遞迴的哦,親。
一個匿名函數使用遞迴的階乘demo
alert((function(x) { if (x > 1) { return x * arguments.callee(--x); } else { return 1; }} (10)));// 3628800
以上說了一大堆,就是為了要告訴你一個關鍵事實:函數運算式只能在代碼執行階段建立而且不存在於變數對象中,換個更通俗的說法是:函數運算式只有當執行到的時候,其才存在,否則是當他不存在的。
我們用匿名函數除了是return和內部賦值外,還常常用來做一次性執行的自執行函數。以下就匿名函數自執行的demo
(function() { alert("OK");})();(function() { alert("OK");} ());
上面的兩種寫法在所有瀏覽器中都可以執行,隨你喜歡什麼方式,只要固定就好,有些書推薦使用方式2,我不清楚為什麼,反正對我來說很隨意的使用了。
匿名函數也可以傳參
(function(x) { x--; alert(x);} (10));
匿名函數往往非常好用,我們可以把匿名函數作為一個參數來傳遞,有時候,我們傳遞的的確是函數的本身,有時候我們傳遞的是函數的結果,如果是函數的結果,那麼函數的自調用就非常cool了
先看看傳遞函數,以下的代碼會產生一組li元素,並且背景色是依據前一個li的背景色加一點點,這樣看上去就是一個漸層地區。當然,真的畫漸層地區不必這樣處理。
$(function() { $(":button").click( function() { for (var i = 0; i < 10; i++) { var ol = $("<li>").css({ width: 30, height: 30 }). addClass("left"). appendTo("ol"); ol.css("backgroundColor", function(index, value) { index = $("ol>li").index(this); var r, g, b = 0, colorValue = ""; if (index == 0) { r = parseInt(Math.random() * 256); g = parseInt(Math.random() * 256); b = parseInt(Math.random() * 256); } else { colorValue = $("ol>li").eq(index - 1).css("backgroundColor"). toString().replace("rgb(", "").replace(")", "") r = parseInt(colorValue.split(",")[0]); g = parseInt(colorValue.split(",")[1]); b = parseInt(colorValue.split(",")[2]) + 5; } return "rgb(" + r + "," + g + "," + b + ")"; } ); } } );});
我們再看一個例子,匿名函數的自調用在傳參的使用的用處。我們點擊一個div的時候,我建立了一個新的div,這個div的樣式完全的複製了被點擊的div的樣式,注意我的css函數中傳遞了一個匿名的自調用函數,這個函數返回了一個對象
$(function() { $("div").click( function() { $("<div>").appendTo("body").css( (function(e) { var styleObj = {}; for (var item in e.style) { if (e.style[item] != "") { styleObj[item] = e.style[item]; } } return styleObj; } (this)) ); } );});