在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 立即調(diào)用的函數(shù)表達式
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計模式之狀態(tài)模式
JavaScript 核心(晉級高手必讀篇)
設(shè)計模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計模式之中介者模式
設(shè)計模式之裝飾者模式
設(shè)計模式之模板方法
設(shè)計模式之外觀模式
強大的原型和原型鏈
設(shè)計模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計模式之適配器模式
設(shè)計模式之組合模式
設(shè)計模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點
求值策略
閉包(Closures)
對象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計模式之代理模式
變量對象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計模式之迭代器模式
立即調(diào)用的函數(shù)表達式
設(shè)計模式之享元模式
設(shè)計模式之原型模式
根本沒有“JSON 對象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實現(xiàn)
全面解析 Module 模式
對象創(chuàng)建模式(下篇)
設(shè)計模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計模式之橋接模式
設(shè)計模式之策略模式
設(shè)計模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計模式之工廠模式

立即調(diào)用的函數(shù)表達式

前言

大家學(xué) JavaScript 的時候,經(jīng)常遇到自執(zhí)行匿名函數(shù)的代碼,今天我們主要就來想想說一下自執(zhí)行。

在詳細了解這個之前,我們來談了解一下“自執(zhí)行”這個叫法,本文對這個功能的叫法也不一定完全對,主要是看個人如何理解,因為有的人說立即調(diào)用,有的人說自動執(zhí)行,所以你完全可以按照你自己的理解來取一個名字,不過我聽很多人都叫它為“自執(zhí)行”,但作者后面說了很多,來說服大家稱呼為“立即調(diào)用的函數(shù)表達式”。

什么是自執(zhí)行?

在 JavaScript 里,任何 function 在執(zhí)行的時候都會創(chuàng)建一個執(zhí)行上下文,因為為 function 聲明的變量和 function 有可能只在該 function 內(nèi)部,這個上下文,在調(diào)用 function 的時候,提供了一種簡單的方式來創(chuàng)建自由變量或私有子 function。

// 由于該function里返回了另外一個function,其中這個function可以訪問自由變量i
// 所有說,這個內(nèi)部的function實際上是有權(quán)限可以調(diào)用內(nèi)部的對象。
function makeCounter() {
    // 只能在makeCounter內(nèi)部訪問i
    var i = 0;
    return function () {
        console.log(++i);
    };
}
// 注意,counter和counter2是不同的實例,分別有自己范圍內(nèi)的i。
var counter = makeCounter();
counter(); // logs: 1
counter(); // logs: 2
var counter2 = makeCounter();
counter2(); // logs: 1
counter2(); // logs: 2
alert(i); // 引用錯誤:i沒有defind(因為i是存在于makeCounter內(nèi)部)。

很多情況下,我們不需要 makeCounter 多個實例,甚至某些 case 下,我們也不需要顯示的返回值,OK,往下看。

問題的核心

當(dāng)你聲明類似 function foo(){}或 var foo = function(){}函數(shù)的時候,通過在后面加個括弧就可以實現(xiàn)自執(zhí)行,例如 foo(),看代碼:

// 因為想下面第一個聲明的function可以在后面加一個括弧()就可以自己執(zhí)行了,比如foo(),
// 因為foo僅僅是function() { /* code */ }這個表達式的一個引用
var foo = function(){ /* code */ }
// ...是不是意味著后面加個括弧都可以自動執(zhí)行?
function(){ /* code */ }(); // SyntaxError: Unexpected token (
//

上述代碼,如果甚至運行,第 2 個代碼會出錯,因為在解析器解析全局的 function 或者 function 內(nèi)部 function 關(guān)鍵字的時候,默認是認為 function 聲明,而不是 function 表達式,如果你不顯示告訴編譯器,它默認會聲明成一個缺少名字的 function,并且拋出一個語法錯誤信息,因為 function 聲明需要一個名字。

旁白:函數(shù)(function),括弧(paren),語法錯誤(SyntaxError)

有趣的是,即便你為上面那個錯誤的代碼加上一個名字,他也會提示語法錯誤,只不過和上面的原因不一樣。在一個表達式后面加上括號(),該表達式會立即執(zhí)行,但是在一個語句后面加上括號(),是完全不一樣的意思,他的只是分組操作符。

// 下面這個function在語法上是沒問題的,但是依然只是一個語句
// 加上括號()以后依然會報錯,因為分組操作符需要包含表達式
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
// 但是如果你在括弧()里傳入一個表達式,將不會有異常拋出
// 但是foo函數(shù)依然不會執(zhí)行
function foo(){ /* code */ }( 1 ); 
// 因為它完全等價于下面這個代碼,一個function聲明后面,又聲明了一個毫無關(guān)系的表達式: 
function foo(){ /* code */ } 
( 1 );

你可以訪問 ECMA-262-3 in detail. Chapter 5. Functions 獲取進一步的信息。

自執(zhí)行函數(shù)表達式

要解決上述問題,非常簡單,我們只需要用大括弧將代碼的代碼全部括住就行了,因為 JavaScript 里括弧()里面不能包含語句,所以在這一點上,解析器在解析 function 關(guān)鍵字的時候,會將相應(yīng)的代碼解析成 function 表達式,而不是 function 聲明。

// 下面2個括弧()都會立即執(zhí)行
(function () { /* code */ } ()); // 推薦使用這個
(function () { /* code */ })(); // 但是這個也是可以用的
// 由于括弧()和JS的&&,異或,逗號等操作符是在函數(shù)表達式和函數(shù)聲明上消除歧義的
// 所以一旦解析器知道其中一個已經(jīng)是表達式了,其它的也都默認為表達式了
// 不過,請注意下一章節(jié)的內(nèi)容解釋
var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();
// 如果你不在意返回值,或者不怕難以閱讀
// 你甚至可以在function前面加一元操作符號
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
// 還有一個情況,使用new關(guān)鍵字,也可以用,但我不確定它的效率
// http://twitter.com/kuvos/status/18209252090847232
new function () { /* code */ }
new function () { /* code */ } () // 如果需要傳遞參數(shù),只需要加上括弧()

上面所說的括弧是消除歧義的,其實壓根就沒必要,因為括弧本來內(nèi)部本來期望的就是函數(shù)表達式,但是我們依然用它,主要是為了方便開發(fā)人員閱讀,當(dāng)你讓這些已經(jīng)自動執(zhí)行的表達式賦值給一個變量的時候,我們看到開頭有括弧(,很快就能明白,而不需要將代碼拉到最后看看到底有沒有加括弧。

用閉包保存狀態(tài)

和普通 function 執(zhí)行的時候傳參數(shù)一樣,自執(zhí)行的函數(shù)表達式也可以這么傳參,因為閉包直接可以引用傳入的這些參數(shù),利用這些被 lock 住的傳入?yún)?shù),自執(zhí)行函數(shù)表達式可以有效地保存狀態(tài)。

// 這個代碼是錯誤的,因為變量i從來就沒背locked住
// 相反,當(dāng)循環(huán)執(zhí)行以后,我們在點擊的時候i才獲得數(shù)值
// 因為這個時候i操真正獲得值
// 所以說無論點擊那個連接,最終顯示的都是I am link #10(如果有10個a元素的話)
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + i);
    }, 'false');
}
// 這個是可以用的,因為他在自執(zhí)行函數(shù)表達式閉包內(nèi)部
// i的值作為locked的索引存在,在循環(huán)執(zhí)行結(jié)束以后,盡管最后i的值變成了a元素總數(shù)(例如10)
// 但閉包內(nèi)部的lockedInIndex值是沒有改變,因為他已經(jīng)執(zhí)行完畢了
// 所以當(dāng)點擊連接的時候,結(jié)果是正確的
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    (function (lockedInIndex) {
        elems[i].addEventListener('click', function (e) {
            e.preventDefault();
            alert('I am link #' + lockedInIndex);
        }, 'false');
    })(i);
}
// 你也可以像下面這樣應(yīng)用,在處理函數(shù)那里使用自執(zhí)行函數(shù)表達式
// 而不是在addEventListener外部
// 但是相對來說,上面的代碼更具可讀性
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', (function (lockedInIndex) {
        return function (e) {
            e.preventDefault();
            alert('I am link #' + lockedInIndex);
        };
    })(i), 'false');
}

其實,上面 2 個例子里的 lockedInIndex 變量,也可以換成 i,因為和外面的 i 不在一個作用于,所以不會出現(xiàn)問題,這也是匿名函數(shù)+閉包的威力。

自執(zhí)行匿名函數(shù)和立即執(zhí)行的函數(shù)表達式區(qū)別

在這篇帖子里,我們一直叫自執(zhí)行函數(shù),確切的說是自執(zhí)行匿名函數(shù)(Self-executing anonymous function),但英文原文作者一直倡議使用立即調(diào)用的函數(shù)表達式(Immediately-Invoked Function Expression)這一名稱,作者又舉了一堆例子來解釋,好吧,我們來看看:

// 這是一個自執(zhí)行的函數(shù),函數(shù)內(nèi)部執(zhí)行自身,遞歸
function foo() { foo(); }
// 這是一個自執(zhí)行的匿名函數(shù),因為沒有標(biāo)示名稱
// 必須使用arguments.callee屬性來執(zhí)行自己
var foo = function () { arguments.callee(); };
// 這可能也是一個自執(zhí)行的匿名函數(shù),僅僅是foo標(biāo)示名稱引用它自身
// 如果你將foo改變成其它的,你將得到一個used-to-self-execute匿名函數(shù)
var foo = function () { foo(); };
// 有些人叫這個是自執(zhí)行的匿名函數(shù)(即便它不是),因為它沒有調(diào)用自身,它只是立即執(zhí)行而已。
(function () { /* code */ } ());
// 為函數(shù)表達式添加一個標(biāo)示名稱,可以方便Debug
// 但一定命名了,這個函數(shù)就不再是匿名的了
(function foo() { /* code */ } ());
// 立即調(diào)用的函數(shù)表達式(IIFE)也可以自執(zhí)行,不過可能不常用罷了
(function () { arguments.callee(); } ());
(function foo() { foo(); } ());
// 另外,下面的代碼在黑莓5里執(zhí)行會出錯,因為在一個命名的函數(shù)表達式里,他的名稱是undefined
// 呵呵,奇怪
(function foo() { foo(); } ());

希望這里的一些例子,可以讓大家明白,什么叫自執(zhí)行,什么叫立即調(diào)用。

注:arguments.callee在 ECMAScript 5 strict mode 里被廢棄了,所以在這個模式下,其實是不能用的。

最后的旁白:Module 模式

在講到這個立即調(diào)用的函數(shù)表達式的時候,我又想起來了 Module 模式,如果你還不熟悉這個模式,我們先來看看代碼:

// 創(chuàng)建一個立即調(diào)用的匿名函數(shù)表達式
// return一個變量,其中這個變量里包含你要暴露的東西
// 返回的這個變量將賦值給counter,而不是外面聲明的function自身
var counter = (function () {
    var i = 0;
    return {
        get: function () {
            return i;
        },
        set: function (val) {
            i = val;
        },
        increment: function () {
            return ++i;
        }
    };
} ());
// counter是一個帶有多個屬性的對象,上面的代碼對于屬性的體現(xiàn)其實是方法
counter.get(); // 0
counter.set(3);
counter.increment(); // 4
counter.increment(); // 5
counter.i; // undefined 因為i不是返回對象的屬性
i; // 引用錯誤: i 沒有定義(因為i只存在于閉包)

關(guān)于更多Module模式的介紹,請訪問我的上一篇帖子:深入理解JavaScript系列(2):全面解析 Module 模式 。

更多閱讀

希望上面的一些例子,能讓你對立即調(diào)用的函數(shù)表達(也就是我們所說的自執(zhí)行函數(shù))有所了解,如果你想了解更多關(guān)于 function 和 Module 模式的信息,請繼續(xù)訪問下面列出的網(wǎng)站:

  1. ECMA-262-3 in detail. Chapter 5. Functions. - Dmitry A. Soshnikov
  2. Functions and function scope - Mozilla Developer Network
  3. Named function expressions - Juriy “kangax” Zaytsev
  4. 全面解析 Module 模式- Ben Cherry(大叔翻譯整理)
  5. Closures explained with JavaScript - Nick Morgan