JavaScript模塊化和閉包

JS模塊化和閉包

js最初作為一個在瀏覽器中運行的腳本語言囱桨,設(shè)計的目標(biāo)是用來給html增加交互行為浙于,早期的網(wǎng)站都是在服務(wù)器端生成并返回給瀏覽器稻轨,js也只對單獨的一個html進(jìn)行操作政钟,所以模塊化并沒有在早期的JS中得到很好的考慮,隨著瀏覽器js引擎越發(fā)的快速潮秘,現(xiàn)在已經(jīng)有很多前端框架琼开,并不依賴與服務(wù)器生成html,而是自己直接通過js來生成枕荞,最典型的例子就是我們常聽到的webapp」窈颍現(xiàn)在所有的js庫都包裝的非常好了,我們今天看看一些js模塊化的基礎(chǔ)知識吧买猖。

名稱空間namespace

在js中如何實現(xiàn)命名空間改橘,我們來看一個例子。

<!DOCTYPE html>  
<html>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <body>

    <button id="mybtn" onclick="display();">點我加載列表</button>
    <h1>我想要不死族的英雄</h1>
    <ul id="mylist"></ul>   
    
    <script type="text/javascript" src="./namespacejs1.js"></script>    
    <script type="text/javascript" src="./namespacejs2.js"></script>    
    </body>  
</html>

我們先定義一個html文件玉控。點擊里面的按鈕飞主,顯示魔獸爭霸的軍隊。響應(yīng)按鈕的代碼分別定義在namespacejs1.js和namespacejs2.js兩個文件中高诺。
這是namespacejs1.js

var list = ['死亡騎士','巫妖','恐懼魔王'];
function display(){
    var mylist = document.getElementById("mylist"), 
    fragment = document.createDocumentFragment(), 
    element; 
    for(var i = 0, x = list.length; i<x; i++){ 
        element = document.createElement("li"); 
        element.appendChild( document.createTextNode( list[i]) ); 
        fragment.appendChild(element); 
    }
    console.log(importGlobalVariable);
    mylist.appendChild(fragment);
}

這是namespacejs2.js

var list = ['圣騎士','大法師','山丘之王'];

當(dāng)我們點擊按鈕的時候碌识,會顯示什么東東呢。我們期望顯示不死族的英雄(namespacejs1.js中的list)虱而,但是這里會顯示人類的英雄(namespacejs2.js中的list)筏餐。顯然,我們想要的數(shù)據(jù)被覆蓋了牡拇。怎么樣才能解決這個問題呢魁瞪?我們來加上名稱空間吧』莺簦看看改進(jìn)后的namespacejs1.js导俘。

var namespace1 = {
    list : ['死亡騎士','巫妖','恐懼魔王'],
    display : function display(){
        var mylist = document.getElementById("mylist"), 
        fragment = document.createDocumentFragment(), 
        element; 

        for(var i = 0, x = list.length; i<x; i++){ 
            element = document.createElement("li"); 
            element.appendChild( document.createTextNode( this.list[i]) ); 
            fragment.appendChild(element); 
        }

        mylist.appendChild(fragment);
    }
};

這時候點擊按鈕會沒有用,需要稍微改動一下

<button id="mybtn" onclick="namespace1.display();">點我加載列表</button>

可以看到display在namespace1的名稱空間下面了剔蹋。問題已經(jīng)圓滿解決旅薄。但是如果我們想擴展namespace1的功能怎么辦呢,難道只能修改namespace1.js的源文件嗎泣崩,還有l(wèi)ist很不安全啊少梁,誰都可以改動它,如何把它變成私有變量呢矫付?

js的封裝

在js中并沒有私有公有的直接支持凯沪,但是不代表js語言不能完成這個。我們看一下如何隱藏list买优。

var namespace1 = (function(){
    
    var importGlobalVariable = gv;
    var list = ['死亡騎士','巫妖','恐懼魔王'];

    function display(){
        var mylist = document.getElementById("mylist"), 
        fragment = document.createDocumentFragment(), 
        element; 
        for(var i = 0, x = list.length; i<x; i++){ 
            element = document.createElement("li"); 
            element.appendChild( document.createTextNode( list[i]) ); 
            fragment.appendChild(element); 
        }
        mylist.appendChild(fragment);
    }
    return {
        display:display
    };
})();

這段代碼返回了一個對象著洼,在這個對象中我們只能訪問display方法,是不是很牛逼呢樟遣,這樣就解決隱藏問題。

js模塊的擴展

我們知道js的繼承是用原型鏈來實現(xiàn)的身笤,但是這里要討論的是模塊的擴展,所以這邊不會說道繼承的問題葵陵。如何擴展namespace1的功能呢液荸。我么看一下下面的代碼。

var namespace1 = (function(n1){
    
    var listArmy = ['4個蜘蛛','2個食尸鬼','2個冰龍']

    n1.displayArmy = function(){
        n1.display();
        var mylist = document.getElementById("mylist"), 
            fragment = document.createDocumentFragment(), 
            element; 
        for(var i = 0, x = listArmy.length; i<x; i++){ 
            element = document.createElement("li"); 
            element.appendChild( document.createTextNode( listArmy[i]) ); 
            fragment.appendChild(element); 
        }
        mylist.appendChild(fragment);
    }

    return n1;
})(namespace1);

這段代碼我們用來增加一個displayArmy的方法脱篙,用來顯示不死族軍隊娇钱,也就是listArmy的數(shù)據(jù)吧。
我們看到上面的代碼把namespace1作為參數(shù)傳入到一個立刻調(diào)用函數(shù)中绊困,這樣在里面給它增加一個函數(shù)文搂。有沒有感覺js很強大啊。

閉包(Closures)

如果已經(jīng)習(xí)慣了C秤朗,C++煤蹭,C#或者Java,那么上面的實現(xiàn)簡直匪夷所思取视,感覺變量的作用域和生命周期都很奇怪硝皂。那么我們說說js中的一個重要概念閉包吧。

定義

Closures are functions that refer to independent (free) variables.
這里就不翻譯了作谭,因為翻譯過來實在是很奇怪稽物,比如,閉包是那些引用了獨立變量的函數(shù)折欠。那么按照定義是否可以認(rèn)為函數(shù)就是閉包呢贝或,為了搞清楚閉包的概念,我們需要了解函數(shù)對象锐秦,變量生命周期咪奖,和嵌套的作用域三個概念。

函數(shù)對象

什么是函數(shù)對象呢农猬,在C語言中和C++語言中赡艰,可以想函數(shù)那樣用()去調(diào)用,看起來和函數(shù)一樣的對象就叫函數(shù)對象斤葱。比如在C語言中:

typedef void (*func_t)(int); //定義函數(shù)指針
//定義一個函數(shù)
void f(int n){
    printf("node(?)=%d\n",n);
}
int main(){
    func_t pf = f;
    f(1);
}

可以看到f很像一個函數(shù)吧慷垮,但是呢,其實它只是一個函數(shù)指針而已揍堕。在C++語言中料身,我們也看一個例子。

class Foreach
{
private:
    struct node * myList;

public:
    Foreach(){
    }
    ~Foreach(){}
    void operator()(){
        //做點有意義的事情吧
    }
};
int main(){
    Foreach *foreach = new Foreach();
    (*foreach)();
}

可以看到foreach對象也很像函數(shù)衩茸。
我們在看一下js中的函數(shù)對象吧芹血。

var f = function(){};
f();

看看,是不是很簡單幔烛。

嵌套的作用域

我們已經(jīng)看到j(luò)s的函數(shù)對象的定義已經(jīng)很方便了啃擦,那么還有什么和傳統(tǒng)語言不一樣的地方呢?
我們看一下這個代碼饿悬。

var x = 10;
function f(){
    var y=15;
    function g(){
        var z=25;
        alert(x+y+z);
    }
    g();
}
f();//顯示50

這段代碼在C語言中沒法實現(xiàn)令蛉,原因是C語言的變量無法訪問當(dāng)前作用域外的變量。也就是說函數(shù)g里面訪問不了y狡恬,函數(shù)f也訪問不了x珠叔。但是js卻能做到。我們看一下這樣有什么強大的地方弟劲。

#include <stdio.h>
#include <stdlib.h>

struct node{
    struct node *next;
    int val;
};

// 函數(shù)指針祷安,JS中的函數(shù)對象
typedef void (*func_t)(int); 

void foreach(struct node *list, func_t func){
    while(list){
        func(list->val);
        list = list->next;
    }
}
void f(int n){
    printf("node(?)=%d\n",n);
}
int main(){
    func_t pf = f;
    f(1);
    
    // bool b = false;
    // b = true;
    // b = "zifuchuan";

    struct node * list = 0, *l;
    int i;

    for(i=0; i<4; i++){
        l = malloc(sizeof(struct node));
        l->val = i;
        l->next = list;
        list = l;
    }

    i=0;l=list;
    //這個循環(huán)可以打印出index,也就是i兔乞,如下是打印結(jié)果
    //node(0) = 3
    //node(1) = 2
    //node(2) = 1
    //node(3) = 0
    while(l){
        printf("node(%d) = %d\n", i++, l->val);
        l = l->next;
    }

    //foreach里面再調(diào)用函數(shù)f汇鞭,就不能訪問i了,如下是打印結(jié)果
    //node(?)=3
    //node(?)=2
    //node(?)=1
    //node(?)=0
    foreach(list,f);
}

我們看到C語言的高階函數(shù)(函數(shù)調(diào)用函數(shù))是沒法訪問外部變量的报嵌。那么js寫這段代碼怎么弄呢虱咧?

function foreach(list, func){
    while(list){
        func(list.val);
        list=list.next;
    }
}

var i = 0;
//這里可以使用變量i
foreach(list,function(n){
    console.log("node("+ i +") = " +n);
    i++;
});

我們發(fā)現(xiàn)js的變量作用域比C語言要牛逼一點吧。

生命周期

下面再看看js閉包的更牛逼的地方吧锚国。

function extent(){
    var n=0;
    return function (){
        n++;
        console.log("n="+n);
    }
}
f = extent();
f();//=>n=1
f();//=>n=2

這里腕巡,當(dāng)extent函數(shù)執(zhí)行完畢后,n變量應(yīng)該掛了才對血筑,但是绘沉,我們通過結(jié)果看到,n變量還活的好好的呢豺总。

總結(jié)

屬于外部作用域的局部變量车伞,被函數(shù)對象給“封閉”在里面了。閉包(“closure”)這個詞原本就是封閉的意思喻喳。被封閉起來的變量的壽命另玖,與封閉它的函數(shù)對象的壽命相等。也就是說表伦,當(dāng)封閉這個變量的函數(shù)對象不再被訪問谦去,被垃圾回收器回收掉時,這個變量才掛蹦哼。
現(xiàn)在大家明白閉包的定義了吧鳄哭。在函數(shù)對象中,將局部變量封閉起來的結(jié)構(gòu)被叫做閉包纲熏。因此妆丘,C語言的函數(shù)指針并不是閉包锄俄,JavaScript中的函數(shù)對象才是閉包。另外C++等傳統(tǒng)面向?qū)ο笳Z言勺拣,也加入了對函數(shù)式編程的支持奶赠,其中一方面就是Lambda表達(dá)式,也有閉包的概念药有。

全局變量的導(dǎo)入

現(xiàn)在大家理解閉包的概念了车柠,我們看看全局變量導(dǎo)入的問題。因為閉包中內(nèi)部變量會一直持有外部變量塑猖,所以我們最好把外部變量當(dāng)做參數(shù)傳遞給我們要使用的內(nèi)部函數(shù),這樣會節(jié)省內(nèi)存和查找變量的時間谈跛。因為找到一個外部變量羊苟,需要從內(nèi)部往外部一層層的查找,很費時間(對解析器來說)感憾。另外蜡励,一起開發(fā)代碼的人也會很迷惑這個變量到底在那里定義的,容易出錯阻桅,我們來看一個例子凉倚。

var globalVariable = "我是外層變量,從disply函數(shù)找到我需要很久很久";

var namespace1 = (function(gv){
    
    var importGlobalVariable = gv;
    var list = ['死亡騎士','巫妖','恐懼魔王'];

    function display(){
        var mylist = document.getElementById("mylist"), 
        fragment = document.createDocumentFragment(), 
        element; 
        for(var i = 0, x = list.length; i<x; i++){ 
            element = document.createElement("li"); 
            element.appendChild( document.createTextNode( list[i]) ); 
            fragment.appendChild(element); 
        }
        console.log(importGlobalVariable);
        mylist.appendChild(fragment);
    }
    return {
        display:display
    };
})(globalVariable);

這個例子把globalVariable當(dāng)成參數(shù)傳入,這樣他就在匿名function函數(shù)的作用域里面了嫂沉。

我們不希望解析器去一直往上查找稽寒,所以幫幫它嘍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趟章,一起剝皮案震驚了整個濱河市杏糙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚓土,老刑警劉巖宏侍,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蜀漆,居然都是意外死亡谅河,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門确丢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绷耍,“玉大人,你說我怎么就攤上這事蠕嫁∠翘欤” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵剃毒,是天一觀的道長病袄。 經(jīng)常有香客問我搂赋,道長,這世上最難降的妖魔是什么益缠? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任脑奠,我火速辦了婚禮,結(jié)果婚禮上幅慌,老公的妹妹穿的比我還像新娘宋欺。我一直安慰自己,他們只是感情好胰伍,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布齿诞。 她就那樣靜靜地躺著,像睡著了一般骂租。 火紅的嫁衣襯著肌膚如雪祷杈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天渗饮,我揣著相機與錄音但汞,去河邊找鬼。 笑死互站,一個胖子當(dāng)著我的面吹牛私蕾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胡桃,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼踩叭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了标捺?” 一聲冷哼從身側(cè)響起懊纳,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亡容,沒想到半個月后嗤疯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡闺兢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年茂缚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屋谭。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡脚囊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桐磁,到底是詐尸還是另有隱情悔耘,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布我擂,位于F島的核電站衬以,受9級特大地震影響缓艳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜看峻,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一阶淘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧互妓,春花似錦溪窒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灼狰,卻和暖如春惜浅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伏嗜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伐厌,地道東北人承绸。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像挣轨,于是被迫代替她去往敵國和親军熏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 閉包(closure)是Javascript語言的一個難點卷扮,也是它的特色荡澎,很多高級應(yīng)用都要依靠閉包實現(xiàn)。 一晤锹、變量...
    zock閱讀 1,074評論 2 6
  • 作用域和閉包是 JavaScript 最重要的概念之一摩幔,想要進(jìn)一步學(xué)習(xí) JavaScript,就必須理解 Java...
    劼哥stone閱讀 1,170評論 1 13
  • 閉包(closure)是Javascript語言的一個難點鞭铆,也是它的特色或衡,很多高級應(yīng)用都要依靠閉包實現(xiàn)。 一车遂、變量...
    zouCode閱讀 1,270評論 0 13
  • 2015年封断,本科大二下到大三上。 一月期末備考舶担,二月系統(tǒng)增肌坡疼,三月開學(xué),四五月刷脂衣陶,七月法院實習(xí)柄瑰,八月系統(tǒng)減脂闸氮,訓(xùn)...
    雙文閱讀 310評論 1 1
  • 昨晚,與先生去散步狱意。一出門湖苞,就看到一輪眀月掛在天空。 這真是始料未及详囤,因為早上還曾是霧都茫茫财骨! 這樣的月色在家鄉(xiāng)時...
    暖香閣主閱讀 422評論 0 1