模塊

學(xué)習(xí)于廖雪峰的官方網(wǎng)站

在計(jì)算機(jī)程序的開發(fā)過程中忙干,隨著程序代碼越寫越多,在一個文件里代碼就會越來越長培遵,越來越不容易維護(hù)浙芙。

為了編寫可維護(hù)的代碼,我們把很多函數(shù)分組籽腕,分別放到不同的文件里嗡呼,這樣,每個文件包含的代碼就相對較少皇耗,很多編程語言都采用這種組織代碼的方式南窗。在Node環(huán)境中,一個.js文件就稱之為一個模塊(module)郎楼。

使用模塊有什么好處万伤?

最大的好處是大大提高了代碼的可維護(hù)性。其次呜袁,編寫代碼不必從零開始敌买。當(dāng)一個模塊編寫完畢,就可以被其他地方引用阶界。我們在編寫程序的時候虹钮,也經(jīng)常引用其他模塊,包括Node內(nèi)置的模塊和來自第三方的模塊膘融。

使用模塊還可以避免函數(shù)名和變量名沖突芙粱。相同名字的函數(shù)和變量完全可以分別存在不同的模塊中,因此氧映,我們自己在編寫模塊時春畔,不必考慮名字會與其他模塊沖突。

在上一節(jié)岛都,我們編寫了一個hello.js文件律姨,這個hello.js文件就是一個模塊,模塊的名字就是文件名(去掉.js后綴)疗绣,所以hello.js文件就是名為hello的模塊线召。

我們把hello.js改造一下,創(chuàng)建一個函數(shù)多矮,這樣我們就可以在其他地方調(diào)用這個函數(shù):

'use strict';

var s = 'Hello';

function greet(name) {
    console.log(s + ', ' + name + '!');
}

module.exports = greet;

函數(shù)greet()是我們在hello模塊中定義的缓淹,你可能注意到最后一行是一個奇怪的賦值語句,它的意思是塔逃,把函數(shù)greet作為模塊的輸出暴露出去讯壶,這樣其他模塊就可以使用greet函數(shù)了。

問題是其他模塊怎么使用hello模塊的這個greet函數(shù)呢湾盗?我們再編寫一個main.js文件伏蚊,調(diào)用hello模塊的greet函數(shù):

'use strict';

// 引入hello模塊:
var greet = require('./hello');

var s = 'Michael';

greet(s); // Hello, Michael!

注意到引入hello模塊用Node提供的require函數(shù):

var greet = require('./hello');

引入的模塊作為變量保存在greet變量中,那greet變量到底是什么東西格粪?其實(shí)變量greet就是在hello.js中我們用module.exports = greet;輸出的greet函數(shù)躏吊。所以氛改,main.js就成功地引用了hello.js模塊中定義的greet()函數(shù),接下來就可以直接使用它了比伏。

在使用require()引入模塊的時候胜卤,請注意模塊的相對路徑。因?yàn)閙ain.js和hello.js位于同一個目錄赁项,所以我們用了當(dāng)前目錄.:

var greet = require('./hello'); // 不要忘了寫相對目錄!

如果只寫模塊名:

var greet = require('hello');

則Node會依次在內(nèi)置模塊葛躏、全局模塊和當(dāng)前模塊下查找hello.js,你很可能會得到一個錯誤:

module.js
    throw err;
          ^
Error: Cannot find module 'hello'
    at Function.Module._resolveFilename
    at Function.Module._load
    ...
    at Function.Module._load
    at Function.Module.runMain

遇到這個錯誤悠菜,你要檢查:

  • 模塊名是否寫對了舰攒;
  • 模塊文件是否存在;
  • 相對路徑是否寫對了悔醋。

CommonJS規(guī)范

這種模塊加載機(jī)制被稱為CommonJS規(guī)范摩窃。在這個規(guī)范下,每個.js文件都是一個模塊芬骄,它們內(nèi)部各自使用的變量名和函數(shù)名都互不沖突偶芍,例如,hello.js和main.js都申明了全局變量var s = 'xxx'德玫,但互不影響匪蟀。

一個模塊想要對外暴露變量(函數(shù)也是變量),可以用module.exports = variable;宰僧,一個模塊要引用其他模塊暴露的變量材彪,用var ref = require('module_name');就拿到了引用模塊的變量。

Node模塊的原理

當(dāng)我們編寫JavaScript代碼時琴儿,我們可以申明全局變量:

var s = 'global';

在瀏覽器中段化,大量使用全局變量可不好。如果你在a.js中使用了全局變量s造成,那么显熏,在b.js中也使用全局變量s,將造成沖突晒屎,b.js中對s賦值會改變a.js的運(yùn)行邏輯喘蟆。

也就是說,JavaScript語言本身并沒有一種模塊機(jī)制來保證不同模塊可以使用相同的變量名鼓鲁。

那Node.js是如何實(shí)現(xiàn)這一點(diǎn)的蕴轨?

其實(shí)要實(shí)現(xiàn)“模塊”這個功能,并不需要語法層面的支持骇吭。Node.js也并不會增加任何JavaScript語法橙弱。實(shí)現(xiàn)“模塊”功能的奧妙就在于JavaScript是一種函數(shù)式編程語言,它支持閉包。如果我們把一段JavaScript代碼用一個函數(shù)包裝起來棘脐,這段代碼的所有“全局”變量就變成了函數(shù)內(nèi)部的局部變量斜筐。

請注意我們編寫的hello.js代碼是這樣的:

var s = 'Hello';
var name = 'world';

console.log(s + ' ' + name + '!');

Node.js加載了hello.js后,它可以把代碼包裝一下蛀缝,變成這樣執(zhí)行:

(function () {
    // 讀取的hello.js代碼:
    var s = 'Hello';
    var name = 'world';

    console.log(s + ' ' + name + '!');
    // hello.js代碼結(jié)束
})();

這樣一來奴艾,原來的全局變量s現(xiàn)在變成了匿名函數(shù)內(nèi)部的局部變量。如果Node.js繼續(xù)加載其他模塊内斯,這些模塊中定義的“全局”變量s也互不干擾。

所以像啼,Node利用JavaScript的函數(shù)式編程的特性俘闯,輕而易舉地實(shí)現(xiàn)了模塊的隔離。

但是忽冻,模塊的輸出module.exports怎么實(shí)現(xiàn)真朗?

這個也很容易實(shí)現(xiàn),Node可以先準(zhǔn)備一個對象module:

// 準(zhǔn)備module對象:
var module = {
    id: 'hello',
    exports: {}
};
var load = function (module) {
    // 讀取的hello.js代碼:
    function greet(name) {
        console.log('Hello, ' + name + '!');
    }
    
    module.exports = greet;
    // hello.js代碼結(jié)束
    return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);

可見僧诚,變量module是Node在加載js文件前準(zhǔn)備的一個變量遮婶,并將其傳入加載函數(shù),我們在hello.js中可以直接使用變量module原因就在于它實(shí)際上是函數(shù)的一個參數(shù):

module.exports = greet;

通過把參數(shù)module傳遞給load()函數(shù)湖笨,hello.js就順利地把一個變量傳遞給了Node執(zhí)行環(huán)境旗扑,Node會把module變量保存到某個地方。

由于Node保存了所有導(dǎo)入的module慈省,當(dāng)我們用require()獲取module時臀防,Node找到對應(yīng)的module,把這個module的exports變量返回边败,這樣袱衷,另一個模塊就順利拿到了模塊的輸出:

var greet = require('./hello');

以上是Node實(shí)現(xiàn)JavaScript模塊的一個簡單的原理介紹。

module.exports vs exports

很多時候笑窜,你會看到致燥,在Node環(huán)境中,有兩種方法可以在一個模塊中輸出變量:

  • 方法一:對module.exports賦值:
// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

module.exports = {
    hello: hello,
    greet: greet
};
  • 方法二:直接使用exports:
// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

function hello() {
    console.log('Hello, world!');
}

exports.hello = hello;
exports.greet = greet;

但是你不可以直接對exports賦值:

// 代碼可以執(zhí)行排截,但是模塊并沒有輸出任何變量:
exports = {
    hello: hello,
    greet: greet
};

如果你對上面的寫法感到十分困惑嫌蚤,不要著急,我們來分析Node的加載機(jī)制:

首先断傲,Node會把整個待加載的hello.js文件放入一個包裝函數(shù)load中執(zhí)行搬葬。在執(zhí)行這個load()函數(shù)前,Node準(zhǔn)備好了module變量:

var module = {
    id: 'hello',
    exports: {}
};

load()函數(shù)最終返回module.exports:

var load = function (exports, module) {
    // hello.js的文件內(nèi)容
    ...
    // load函數(shù)返回:
    return module.exports;
};

var exported = load(module.exports, module);

也就是說艳悔,默認(rèn)情況下急凰,Node準(zhǔn)備的exports變量和module.exports變量實(shí)際上是同一個變量,并且初始化為空對象{},于是抡锈,我們可以寫:

exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };

也可以寫:

module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };

換句話說疾忍,Node默認(rèn)給你準(zhǔn)備了一個空對象{},這樣你可以直接往里面加?xùn)|西床三。

但是一罩,如果我們要輸出的是一個函數(shù)或數(shù)組,那么撇簿,只能給module.exports賦值:

module.exports = function () { return 'foo'; };

給exports賦值是無效的聂渊,因?yàn)橘x值后,module.exports仍然是空對象{}四瘫。

總結(jié)

  • 在Node環(huán)境中汉嗽,一個.js文件就稱之為一個模塊(module)。
  • module大大提高了代碼的可維護(hù)性找蜜;可以被其他地方引用饼暑;使用模塊還可以避免函數(shù)名和變量名沖突
  • 要在模塊中對外輸出變量,用:
module.exports = variable;

輸出的變量可以是任意對象洗做、函數(shù)弓叛、數(shù)組等等。

  • 引入其他模塊輸出的對象诚纸,用:
var foo = require('other_module');

引入的對象具體是什么撰筷,取決于引入模塊輸出的對象。

  • 兩種方法輸出變量
//第一種
module.exports = {
    hello: hello,
    greet: greet
};
//第二種
exports.hello = hello;
exports.greet = greet;
  • 直接對module.exports賦值畦徘,可以應(yīng)對任何情況闭专。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旧烧,隨后出現(xiàn)的幾起案子影钉,更是在濱河造成了極大的恐慌,老刑警劉巖掘剪,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件平委,死亡現(xiàn)場離奇詭異,居然都是意外死亡夺谁,警方通過查閱死者的電腦和手機(jī)廉赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匾鸥,“玉大人蜡塌,你說我怎么就攤上這事∥鸶海” “怎么了馏艾?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我琅摩,道長铁孵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任房资,我火速辦了婚禮蜕劝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轰异。我一直安慰自己岖沛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布搭独。 她就那樣靜靜地躺著婴削,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戳稽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天期升,我揣著相機(jī)與錄音惊奇,去河邊找鬼。 笑死播赁,一個胖子當(dāng)著我的面吹牛颂郎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播容为,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼乓序,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坎背?” 一聲冷哼從身側(cè)響起替劈,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎得滤,沒想到半個月后陨献,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懂更,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年眨业,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮协。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡龄捡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出慷暂,到底是詐尸還是另有隱情聘殖,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站就斤,受9級特大地震影響悍募,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洋机,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一坠宴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绷旗,春花似錦喜鼓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至角骤,卻和暖如春隅忿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邦尊。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工背桐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝉揍。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓链峭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親又沾。 傳聞我的和親對象是個殘疾皇子睦疫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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

  • 模塊通常是指編程語言所提供的代碼組織機(jī)制耗跛,利用此機(jī)制可將程序拆解為獨(dú)立且通用的代碼單元掏湾。所謂模塊化主要是解決代碼分...
    MapleLeafFall閱讀 1,165評論 0 0
  • 模塊 Node 有簡單的模塊加載系統(tǒng)剧包。在 Node 里,文件和模塊是一一對應(yīng)的滑燃。下面例子里曲横,foo.js加載同一個...
    保川閱讀 589評論 0 0
  • 為了編寫可維護(hù)的代碼,我們把很多函數(shù)分組不瓶,分別放到不同的文件里禾嫉,這樣,每個文件包含的代碼就相對較少蚊丐,很多編...
    想當(dāng)一個大頭兵閱讀 1,366評論 0 0
  • 模塊 在計(jì)算機(jī)程序的開發(fā)中,隨著代碼越寫越多,在一個文件里代碼就會越來越長,越來越不容易維護(hù).為了編寫可維護(hù)的代碼...
    _我和你一樣閱讀 242評論 0 0
  • 模塊 在計(jì)算機(jī)程序的開發(fā)中,隨著代碼越寫越多,在一個文件里代碼就會越來越長,越來越不容易維護(hù).為了編寫可維護(hù)的代碼...
    _我和你一樣閱讀 170評論 0 0