這篇,咱們來學習下自定義函數以及即時函數的內容。瀏覽器
4、自定義函數閉包
函數能夠動態定義,也能夠分配給變量。若是建立了一個新函數,而且將其分配給保存了另外函數的同一個變量,那麼就以一個新函數覆蓋了舊函數。在某種程度上,回收了舊函數指針以指向一個新函數。而這一切發生在舊函數體的內部。在這種狀況下,該函數以一個新的實現覆蓋並從新定義了自身。函數
var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; // 使用自定義函數 scareMe(); scareMe(); scareMe();
當您的函數有一些初始化準備工做要作,而且僅須要執行一次,那麼這種模式就很是有用。由於並無理由去執行本能夠避免的重複工做,即該函數的一些部分可能並再也不須要。在這種狀況下,自定義函數(self-defining function)能夠更新自身的實現。性能
這裏多說下,我的以爲,這裏的自定義,並不是單純的指開發者建立的非內置函數,而是指本身定義本身的函數,也就是self-defining。因此,這裏要尤爲注意一下。學習
使用此模式能夠顯著地幫助您提高應用程序的性能,這是因爲從新定義的函數僅執行了更少的工做。this
這種函數的另外一個名稱是「惰性函數定義」(lazy function definition),由於該函數直到第一次使用時才被正確的定義,而且其具備後向惰性執行了更少的工做。spa
該模式的其中一個缺點在於,當它重定義自身時已經添加到原始函數的任何屬性都會丟失。此外,若是該函數使用了不一樣的名稱,好比分配給不一樣的變量或者以對象的方法來使用,那麼重定義部分將永遠不會發生,而且將會執行原始函數體。指針
下面的例子,咱們將上面的scareMe()函數以第一類對象的使用方式來使用:code
var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; // 一、添加一個新的屬性 scareMe.property = "propertly"; // 二、賦值給另外一個不一樣名稱的變量 var prank = scareMe; // 三、做爲一個方法使用 var spooky = { boo:scareMe }; // calling with a new name prank(); //輸出「Boo!」 prank(); //輸出「Boo!」 console.log(prank.property); // 輸出「properly」 // 做爲一個方法來調用 spooky.boo(); //輸出「Boo!」 spooky.boo(); //輸出「Boo!」 console.log(spooky.boo.property); //輸出「properly」 // 使用自定義函數 scareMe(); //輸出「Double Boo!」 scareMe(); //輸出「Double Boo!」 console.log(scareMe.property); //輸出undefined
正如上面代碼所示,當將該函數分配給一個新的變量時,如預期的那樣,函數的自定義(self-definition)並無發生。每次當調用prank()時,它都通知"Boo!"消息,同時它還覆蓋了全局scareMe()函數,可是prank()自身保持了舊函數的可見,其中還包括屬性。當該函數以spooky對象當boo()方法使用時,也發生了一樣的狀況。全部這些調用不斷的重寫全局scareMe()指針,以致於當它最終被調用時,他才第一次具備更新函數主體並通知「Double boo」消息的權利。此外,它也不能訪問scareMe.property屬性。對象
再多說兩句,我的理解:
// 咱們先來看,爲何上面的代碼訪問不到property屬性。 // 咱們把代碼簡化一下: var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; scareMe.property = "propertly"; console.log(scareMe.property); //輸出「propertly」 scareMe(); //輸出「Boo!」 console.log(scareMe.property); //輸出「undefined」 scareMe(); //輸出「Double Boo!」 console.log(scareMe.property); //輸出undefined
這是爲何呢?在第一次執行scareMe()方法後,就找不到property屬性了。由於第一次執行後,綁定的是外層變量的指針,此時在綁定屬性的時候,是綁定在這個指針上的。而當函數執行了一次後,內部的scareMe()函數,替換了原來的函數指針。它已經不是曾經的它了!因此property屬性是綁定在外層的,那固然再就找不到了被。
那麼,因爲它被覆蓋了。而且,之後不會再有新的東西覆蓋掉這個「新函數指針」,因此,之後每次執行都不會執行舊的內容。因此,之後每次的執行都會打印"Double Boo!"。那麼,咱們再看代碼:
// 咱們先來看,爲何上面的代碼訪問不到property屬性。 // 咱們把代碼簡化一下: var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); scareMe = function () { alert("Third Boo!"); } }; }; scareMe.property = "propertly"; console.log(scareMe.property); //輸出「propertly」 scareMe(); //輸出「Boo!」 console.log(scareMe.property); //輸出「undefined」 scareMe(); //輸出「Double Boo!」 console.log(scareMe.property); //輸出undefined scareMe(); //輸出「Third Boo!」 scareMe(); //輸出「Third Boo!」 scareMe(); //輸出「Third Boo!」
咱們來看這段代碼,我自覺得是的又加了一層,因而,我但願不用我說,你也已經懂了。
最後,再說一下,爲何賦值給一個其它名字的變量以及用對象的方法來使用的時候,重定義永遠沒有發生。我的理解,由於你每次在執行的時候,賦值的動做是有的,可是並無把我覆蓋,因此,每次都是重定義,每次都沒法執行新的內部邏輯。因此,在最開始的那個例子裏,當你第一次調用scareMe()的時候,就走了Double Boo!語句。由於前面prank()或者spooky.boo()的每一次執行,都從新定義了scareMe()。但願我說的,你理解了。
5、即時函數
即時函數模式(Immediate Function pattern)是一種能夠支持在定義函數後當即執行該函數的語法。
(function() { alert('watch out!'); }());
這種模式本質上只是一個函數表達式(不管是命名仍是匿名的),該函數會在建立後馬上執行。在ECMAScript標準中並無定義術語「即時函數(immediate function)」,可是這種模式很是簡潔。
該模式由一下幾部分組成:
(function() { alert('watch out!'); })();
這樣的語法也能夠,可是JSLint偏好第一種。
這種模式是很是有用的,由於它爲初始化代碼提供了一個做用域沙箱。好比:當頁面加載時,代碼必須初始化執行一些設置任務,好比附加事件處理程序、建立對象等諸如此類的任務。全部這些工做僅須要執行一次,所以沒有理由去建立一個可複用的命名函數。可是代碼也還須要一些臨時變量,而在初始化階段完成後就再也不須要這些變量。然而,以全局變量形式建立全部哪些變量是一個差勁的方法。這就是爲何須要一個即時函數的緣由,用以將全部代碼包裝到它的局部做用域中,且不會將任何變量泄露到全局做用域中;
(function () { var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], today = new Date(), msg = 'Today is' + days[today.getDay()] + ', ' + today.getDate(); alert(msg); }());
若是上面的代碼沒有包裝在即時函數中,那麼days、today和msg等變量將會成爲全局變量,並遺留在初始化代碼中。
即時函數的參數
也能夠將參數傳遞到即時函數中:
(function (who,when) { console.log("I met " + who + " on " + when); }("Zaking",new Date()));
通常狀況下,全局對象是以參數方式傳遞給即時函數的,以便於在不使用window指定全局做用域限定的狀況下能夠在函數內部訪問該對象,這樣將使得代碼在瀏覽器環境以外時具備更好的操做性。
(function (global) { // 經過global訪問全局變量 }(this));
請注意,通常來講,不該該傳遞過多的參數到即時函數中,由於這樣將迅速成爲一種閱讀負擔,致使在理解代碼運行流程時須要不斷地滾動到該函數的頂部和底部。
即時函數的返回值
正如任何其餘函數同樣,即時函數能夠返回值,而且這些返回值也能夠分配給變量:
var result = (function() { return 2 + 2; }());
另外一種方式也能夠達到效果,即忽略包裝函數的括號,由於將即時函數的返回值分配給一個變量時並不須要這些括號:
var result = function() { return 2 + 2; }();
這個語法雖然比較簡單,可是看起來可能有點使人誤解。在沒有注意到該函數尾部的括號時,一些閱讀代碼的人可能會認爲result變量指向一個函數。實際上,result指向由即時函數返回的值。
另外一種語法也能夠獲得一樣的結果:
var result = (function() { return 2 + 2; })();
實際上,即時函數不只能夠返回原始值,還能夠返回任意類型的值,包括另一個函數。所以,可使用即時函數的做用域以存儲一些私有數據,而這特定於返回的內部函數。
var getResult = (function() { var res = 2 + 2; return function () { return res; } }());
上面這段代碼,即時函數返回的值是一個函數,它將分配給變量getResult,而且將簡單的返回res值,該值被預計算並存儲在即時函數的閉包中。
當定義對象屬性時也可使用即時函數。想象一下,若是須要定義一個在對象生存期內永遠都不會改變的屬性,可是在定義它以前須要執行一些工做以找出正確的值。此時,可使用一個即時函數包裝這些工做,而且即時函數的返回值將會成爲屬性值。
var o = { message:(function () { var who = "me", what = "call"; return what + " " + who; }()), getMsg:function () { return this.message; } }; console.log(o.getMsg()) console.log(o.message)
這個例子中,message是一個字符串屬性,而不是一個函數,可是它須要一個在腳本加載時執行的函數來幫助定義該o.message屬性。
優勢和用法
即時函數模式獲得了普遍的使用。它能夠幫助包裝許多想要執行的工做,且不會在後臺留下任何全局變量。定義的全部這些變量將會是用於自調用函數的局部變量,而且不用擔憂全局空間被臨時變量所污染。
還可使用即時函數模式來定義模塊(固然ES6中以及由模塊的概念了,可是這樣的方法仍舊有學習的地方):
// 文件module1.js中定義的模塊module1 (function() { //模塊1的全部代碼 }());
使用這種方式,能夠編寫其餘模塊。而後,將該代碼發佈到在線站點時,能夠決定哪些功能準備應用於黃金時間,而且使用構建腳本將對應文件合併。
這篇文章就到這裏了。後面還有...