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

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

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

前言

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

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

什么是自執(zhí)行?

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

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

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

問題的核心

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

// 因?yàn)橄胂旅娴谝粋€(gè)聲明的function可以在后面加一個(gè)括弧()就可以自己執(zhí)行了,比如foo(),
// 因?yàn)閒oo僅僅是function() { /* code */ }這個(gè)表達(dá)式的一個(gè)引用
var foo = function(){ /* code */ }
// ...是不是意味著后面加個(gè)括弧都可以自動(dòng)執(zhí)行?
function(){ /* code */ }(); // SyntaxError: Unexpected token (
//

上述代碼,如果甚至運(yùn)行,第 2 個(gè)代碼會(huì)出錯(cuò),因?yàn)樵诮馕銎鹘馕鋈值?function 或者 function 內(nèi)部 function 關(guān)鍵字的時(shí)候,默認(rèn)是認(rèn)為 function 聲明,而不是 function 表達(dá)式,如果你不顯示告訴編譯器,它默認(rèn)會(huì)聲明成一個(gè)缺少名字的 function,并且拋出一個(gè)語法錯(cuò)誤信息,因?yàn)?function 聲明需要一個(gè)名字。

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

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

// 下面這個(gè)function在語法上是沒問題的,但是依然只是一個(gè)語句
// 加上括號(hào)()以后依然會(huì)報(bào)錯(cuò),因?yàn)榉纸M操作符需要包含表達(dá)式
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
// 但是如果你在括弧()里傳入一個(gè)表達(dá)式,將不會(huì)有異常拋出
// 但是foo函數(shù)依然不會(huì)執(zhí)行
function foo(){ /* code */ }( 1 ); 
// 因?yàn)樗耆葍r(jià)于下面這個(gè)代碼,一個(gè)function聲明后面,又聲明了一個(gè)毫無關(guān)系的表達(dá)式: 
function foo(){ /* code */ } 
( 1 );

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

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

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

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

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

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

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

// 這個(gè)代碼是錯(cuò)誤的,因?yàn)樽兞縤從來就沒背locked住
// 相反,當(dāng)循環(huán)執(zhí)行以后,我們?cè)邳c(diǎn)擊的時(shí)候i才獲得數(shù)值
// 因?yàn)檫@個(gè)時(shí)候i操真正獲得值
// 所以說無論點(diǎn)擊那個(gè)連接,最終顯示的都是I am link #10(如果有10個(gè)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');
}
// 這個(gè)是可以用的,因?yàn)樗谧詧?zhí)行函數(shù)表達(dá)式閉包內(nèi)部
// i的值作為locked的索引存在,在循環(huán)執(zhí)行結(jié)束以后,盡管最后i的值變成了a元素總數(shù)(例如10)
// 但閉包內(nèi)部的lockedInIndex值是沒有改變,因?yàn)樗呀?jīng)執(zhí)行完畢了
// 所以當(dāng)點(diǎn)擊連接的時(shí)候,結(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ù)表達(dá)式
// 而不是在addEventListener外部
// 但是相對(duì)來說,上面的代碼更具可讀性
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');
}

其實(shí),上面 2 個(gè)例子里的 lockedInIndex 變量,也可以換成 i,因?yàn)楹屯饷娴?i 不在一個(gè)作用于,所以不會(huì)出現(xiàn)問題,這也是匿名函數(shù)+閉包的威力。

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

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

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

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

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

最后的旁白:Module 模式

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

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

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

更多閱讀

希望上面的一些例子,能讓你對(duì)立即調(diào)用的函數(shù)表達(dá)(也就是我們所說的自執(zhí)行函數(shù))有所了解,如果你想了解更多關(guān)于 function 和 Module 模式的信息,請(qǐng)繼續(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