深入理解JavaScript系列(3)

2018-06-09 15:54 更新

本文是深入理解JavaScript系列的第篇讀文筆記,博客原文在這里。

內(nèi)容簡(jiǎn)要

本文是整個(gè)深入理解JavaScript系列中第一篇稍微有點(diǎn)難啃的文章,特別是對(duì)新手來說。大叔這篇文章的寫作借鑒了下面兩篇文章,

主要參考的是前者。闡述的內(nèi)容就是模塊化模式。

首先,什么叫模塊化模式?它具有哪些特點(diǎn)?

模塊化模式(或者說模塊化編程)在JavaScript中是一個(gè)非常常見的編程技巧,它按照功能(或者業(yè)務(wù))將JavaScript代碼封裝在一起組成一個(gè)模塊,對(duì)外暴露特定的額方法和屬性。一般來說,模塊化模式具有如下幾個(gè)特點(diǎn),

  • 模塊化,可重用
  • 將一系列的方法及屬性封裝在一起,與其他模塊彼此獨(dú)立,不污染全局作用域,松耦合。(大叔這里說的松耦合的意思,我猜測(cè)應(yīng)該就是指模塊的獨(dú)立性)
  • 暴露特定的接口(方法或者屬性),模塊的其他方法外部不可訪問

BACKBONE

文章的主體將分別介紹模塊化模式的基本用法和高級(jí)用法。

基本用法

話不多說,老夫先把大叔的一段代碼拉出來溜溜,

var Calculator = function (eq) {
    /*
    這里可以聲明私有成員
    */
    var eqCtl = document.getElementById(eq);
    // 暴露公開的成員
    return {
        add: function (x, y) {
            var val = x + y;
            eqCtl.innerHTML = val;
        }
    };
};

我們可以通過如下的方式來調(diào)用:

var calculator = new Calculator('eq');
calculator.add(2, 2);

這里使用了new Calculator()把函數(shù)Calculator當(dāng)作一個(gè)構(gòu)造函數(shù)調(diào)用。有的同學(xué)可能會(huì)問,這里不使用new Calculator(),直接調(diào)用Calculator()函數(shù)好像也是可以的啊,那new Calculator()和不使用new到底啥區(qū)別?。?/p>

首先我在之前的文章中有闡述過new xxx()這個(gè)操作的本質(zhì),不太清楚的可以移步看看。

在大叔舉的這個(gè)例子中,使用new與不使用new都是沒有錯(cuò)的,下面的calculator.add方法都是可以調(diào)用的。

不過這兩者有自己適合的場(chǎng)景,

  • 使用new一定會(huì)返回一個(gè)object,其內(nèi)部的this指向object自身,且object中方法或者屬性可以通過this相互訪問。這種是使用JavaScript構(gòu)建OOP編程的基礎(chǔ)。
  • 不使用new的含義就是簡(jiǎn)單的調(diào)用函數(shù),其返回值根據(jù)函數(shù)的return語句來確定,且內(nèi)部的this都指向全局作用域。這種一般用于命名空間的管理,獨(dú)立模塊的封裝。

匿名閉包和全局變量

閉包是函數(shù)式編程語言具有的一種語法糖(暫且這么說吧,雖然不太準(zhǔn)備)。JavaScript中對(duì)閉包的應(yīng)用隨處可見,匿名閉包更是讓一切成為可能的基礎(chǔ),而這也是JavaScript最靈活的特性。

下面我們來創(chuàng)建一個(gè)最簡(jiǎn)單的閉包函數(shù),函數(shù)內(nèi)部的代碼一直存在于閉包內(nèi),在整個(gè)運(yùn)行周期內(nèi),該閉包都保證了內(nèi)部的代碼處于私有狀態(tài)。

(function () {
    // ...所有的變量和function都在這里聲明,并且作用域也只能在這個(gè)匿名閉包里
    // ...但是這里的代碼依然可以訪問外部全局的對(duì)象
})();

可以看出匿名閉包其實(shí)就是一個(gè)自執(zhí)行函數(shù)。關(guān)于自執(zhí)行函數(shù)在后面的文章中還會(huì)更加深入的討論。現(xiàn)在只需要知道自執(zhí)行函數(shù)的本質(zhì)是一個(gè)函數(shù)表達(dá)式,在運(yùn)行時(shí)將會(huì)產(chǎn)生一個(gè)封閉作用域(或者說私有作用域),這個(gè)封閉作用域可以訪問外部的全局變量,但是不能被外部訪問。

這里可能會(huì)遇到一個(gè)問題,比如下面的代碼,

var name = 'a';
(function() {
    console.log(name); // a
    name = 'b'; // 這里不帶var的賦值,將會(huì)將name隱式的提升為全局變量,且覆蓋了閉包外部的name
    console.log(name); // b
})();
console.log(name); // b

可見,由于JavaScript中存在隱式全局變量這樣一種東西,當(dāng)在閉包中需要訪問外部的全局變量時(shí),萬一操作不當(dāng),就會(huì)隱式的聲明一個(gè)你不知道的全局變量。

現(xiàn)在流行的JavaScript庫(kù),比如JQuery,都采用這樣一種方式來達(dá)到從閉包內(nèi)部訪問外部的全局變量的目的,

(function (window, undefined) {
    // JQuery的源碼其實(shí)就是一個(gè)大閉包?。?})(window);

可見,我們可以將全局變量當(dāng)成一個(gè)參數(shù)傳入到匿名函數(shù)然后使用,相比隱式全局變量,它又清晰又快。

有時(shí)候可能不僅僅要使用全局變量,而是也想聲明全局變量,如何做呢?我們可以通過將匿名函數(shù)的返回值賦值給這個(gè)全局變量,代碼如下,


上面的代碼聲明了一個(gè)全局變量blogModule,并且?guī)в?個(gè)可訪問的屬性:blogModule.AddTopicblogModule.Name,除此之外,其它代碼都在匿名函數(shù)的閉包里保持著私有狀態(tài)。

高級(jí)用法

下面將會(huì)闡述幾種稍微高級(jí)一點(diǎn)的用法,其實(shí)都是對(duì)上面所說的基本用法的擴(kuò)展。

擴(kuò)展

前面的基本用法可能不太適合大型的項(xiàng)目。因?yàn)榇笮偷捻?xiàng)目往往會(huì)有多人協(xié)作開發(fā),每個(gè)人負(fù)責(zé)的模塊不盡相同,這時(shí)候就需要把一個(gè)模塊分割到不同的文件中去。

看下面的代碼,

var blogModule = (function(my) {
    my.AddPhoto = function () {
        //添加內(nèi)部代碼
    };
    return my;
})(blogModule);

發(fā)現(xiàn)了沒有,我們將blogModule自身作為參數(shù)傳遞給自執(zhí)行函數(shù)。這個(gè)自執(zhí)行函數(shù)執(zhí)行完畢后,blogModule上就會(huì)多處一個(gè)方法AddPhoto。試想一下,多人開發(fā)中每個(gè)人都采用這種模式,那么完全就可以彼此獨(dú)立的給blogModule添加自己所需的方法和屬性。

一般來說,我們還會(huì)做一些異常判斷,因?yàn)槎嗳藚f(xié)作的過程中,每個(gè)人都不知道自己拿到的blogModule究竟有哪些東西,甚至都不知道這個(gè)對(duì)象是否為undefined的,改進(jìn)如下,

var blogModule = (function(my) {
    my.AddPhoto = function () {
        //添加內(nèi)部代碼
    };
    return my;
})(blogModule || {}); // 這里是重點(diǎn)

如上面的代碼,我們?cè)趥鬟f參數(shù)的時(shí)候,其實(shí)傳入的是一個(gè)判斷表達(dá)式,這樣就保證了在閉包內(nèi)部blogModule必定是不為undefined,是不是很巧妙? :)

然而,有的時(shí)候我們?cè)跀U(kuò)展的時(shí)候需要對(duì)某一些方法進(jìn)行重寫,那么該怎么辦呢?看如下的代碼,

var blogModule = (function (my) {
    var oldAddPhotoMethod = my.AddPhoto;
    my.AddPhoto = function () {
        // 重寫方法,依然可通過oldAddPhotoMethod調(diào)用舊的方法
    };
    return my;
})(blogModule);

代碼中,我們使用私有變量oldAddPhotoMethod緩存了原先的AddPhoto方法,然后重寫了AddPhoto方法。通過這種方式,我們達(dá)到了重寫的目的,當(dāng)然如果你想在繼續(xù)在內(nèi)部使用原有的屬性,你可以調(diào)用oldAddPhotoMethod來用。

克隆和繼承

var blogModule = (function (old) {
    var my = {},
        key;
    for (key in old) {
        if (old.hasOwnProperty(key)) {
            my[key] = old[key];
        }
    }
    var oldAddPhotoMethod = old.AddPhoto;
    my.AddPhoto = function () {
        // 克隆以后,進(jìn)行了重寫,當(dāng)然也可以繼續(xù)調(diào)用oldAddPhotoMethod
    };
    return my;
})(blogModule);

說實(shí)話,我有點(diǎn)沒太看懂這部分的內(nèi)容,代碼中的for in循環(huán)其實(shí)是將old對(duì)象克隆了一份賦值給my對(duì)象。但是這個(gè)克隆過程中,old對(duì)象中的object以及function類型的數(shù)據(jù)其實(shí)并沒有發(fā)生克隆,只是多了個(gè)my對(duì)象的相應(yīng)引用而已。

我不太明白的是,這個(gè)跟JavaScript的模塊化有什么關(guān)系呢?望大神解惑。

跨文件共享私有對(duì)象

說實(shí)話,這部分的我看了很久才弄明白,別看代碼量就10來行,但是真的不是那么好理解的。(也可能是我比較菜。)

這里的需求是這樣的,一個(gè)module分割到多個(gè)文件中,我們想要各個(gè)文件中的私有變量能夠交叉訪問,該怎么做呢?

var blogModule = (function (my) {
    var _private = my._private = my._private || {},
        _seal = my._seal = my._seal || function () {
            delete my._private;
            delete my._seal;
            delete my._unseal;
        },
        _unseal = my._unseal = my._unseal || function () {
            my._private = _private;
            my._seal = _seal;
            my._unseal = _unseal;
        };
    return my;
})(blogModule || {});

任何文件都可以對(duì)他們的局部變量_private設(shè)置,并且此設(shè)置對(duì)其他的文件也立即生效。一旦這個(gè)模塊加載結(jié)束,應(yīng)用會(huì)調(diào)用blogModule._seal()進(jìn)行“上鎖”,這會(huì)阻止外部接入內(nèi)部的_private。如果這個(gè)模塊需要再次增生,應(yīng)用的生命周期內(nèi),任何文件都可以調(diào)用_unseal()進(jìn)行“開鎖”,然后再加載新文件,加載新文件后又會(huì)初始化_pravite、_seal_unseal,然后再次調(diào)用_seal()“上鎖”。

這個(gè)過程很巧妙。其實(shí)我覺得,各個(gè)文件中的私有變量能夠交叉訪問這個(gè)需求簡(jiǎn)直就是個(gè)奇葩,為什么各個(gè)文件中的私有變量要交叉訪問呢?這不是破壞了模塊的獨(dú)立性么?有人說其實(shí)這里的多文件其實(shí)仍然描述的是同一個(gè)模塊,那我想說,這種情況下使用子模塊應(yīng)該是一種更優(yōu)的選擇。

子模塊

子模塊是最簡(jiǎn)單也是最經(jīng)常使用的設(shè)計(jì)思路,

blogModule.subModule = (function () {
    var my = {};
    // ...
    return my;
})();

其實(shí)我個(gè)人覺得,子模塊+模塊擴(kuò)展就足夠應(yīng)付一般的模塊化模式需求了。使用過多的高級(jí)用法,必定給代碼增加復(fù)雜度,給日后的維護(hù)和升級(jí)肯定為增加難度。

總結(jié)

就我目前的經(jīng)驗(yàn)來說,模塊化模式經(jīng)常用到的幾種方法包括,松耦合擴(kuò)展,私有作用域以及子模塊。這幾種就是最常用的方式。

不過現(xiàn)在業(yè)界出現(xiàn)了CMD、AMD等規(guī)范后,JavaScript代碼的模塊化管理更趨向于代碼級(jí)別而不再是設(shè)計(jì)級(jí)別。所以本文中所描述的模塊化模式,我猜測(cè)日后將會(huì)越來越淡化,越來越輕量化,比如用在配置中,工具方法的管理上等等。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)