理解Node.js中的module.exports和exports

Node.js編程中模狭,module是可共享和重復(fù)使用的自包含功能單元缩焦。它們使我們作為開(kāi)發(fā)人員的生活更輕松嫂粟,因?yàn)槲覀兛梢允褂盟鼈儊?lái)增強(qiáng)我們的應(yīng)用程序娇未,而無(wú)需自己編寫(xiě)功能。它們還允許我們組織和解耦我們的代碼星虹,從而使應(yīng)用程序更易于理解零抬、調(diào)試和維護(hù)。

不同的Node.js模塊格式

由于JavaScript最初沒(méi)有模塊的概念宽涌,隨著時(shí)間的推移出現(xiàn)了各種競(jìng)爭(zhēng)的格式平夜。以下是主要的幾種格式:

  • 異步模塊定義(AMD)格式在瀏覽器中使用,并使用define函數(shù)來(lái)定義模塊护糖。
  • CommonJSCJS)格式在Node.js中使用褥芒,并使用requiremodule.exports來(lái)定義依賴關(guān)系和模塊。npm生態(tài)系統(tǒng)是建立在這種格式之上的嫡良。
  • ES模塊(ESM)格式锰扶。從ES6(ES2015)開(kāi)始,JavaScript支持原生模塊格式寝受。它使用export關(guān)鍵字來(lái)導(dǎo)出模塊的公共API坷牛,使用import關(guān)鍵字來(lái)導(dǎo)入它。
    System.register格式設(shè)計(jì)用于支持ES5中的ES6模塊很澄。
  • 通用模塊定義(UMD)格式可以在瀏覽器和Node.js中使用京闰。當(dāng)模塊需要被多個(gè)不同的模塊加載器導(dǎo)入時(shí),這是非常有用的甩苛。

導(dǎo)入模塊

Node.js自帶一組內(nèi)置模塊蹂楣,我們可以在代碼中使用它們而無(wú)需安裝。為了做到這一點(diǎn)讯蒲,我們需要使用require關(guān)鍵字來(lái)導(dǎo)入模塊痊土,并將結(jié)果賦值給一個(gè)變量。然后可以使用該變量來(lái)調(diào)用模塊公開(kāi)的任何方法墨林。

例如赁酝,要列出目錄的內(nèi)容,可以使用文件系統(tǒng)模塊及其readdir方法:

const fs = require('fs');
const folderPath = '/home/jim/Desktop/';

fs.readdir(folderPath, (err, files) => {
  files.forEach(file => {
    console.log(file);
  });
});

請(qǐng)注意旭等,在CommonJS中酌呆,模塊是同步加載的,并按照它們出現(xiàn)的順序進(jìn)行處理搔耕。

創(chuàng)建并導(dǎo)出模塊

現(xiàn)在讓我們看看如何創(chuàng)建自己的模塊并將其導(dǎo)出以供在程序的其他地方使用隙袁。首先創(chuàng)建一個(gè)名為user.js的文件,并添加以下內(nèi)容:

const getName = () => {
  return 'Jim';
};

exports.getName = getName;

現(xiàn)在在同一文件夾中創(chuàng)建一個(gè)名為index.js的文件,并添加以下內(nèi)容:

const user = require('./user');
console.log(`User: ${user.getName()}`);

使用node index.js運(yùn)行程序藤乙,你應(yīng)該在終端看到以下輸出:

User: Jim

那么這里發(fā)生了什么呢猜揪?如果你看一下user.js文件惭墓,你會(huì)注意到我們定義了一個(gè)getName函數(shù)坛梁,然后使用exports關(guān)鍵字使其可以在其他地方導(dǎo)入。然后在index.js文件中腊凶,我們導(dǎo)入了這個(gè)函數(shù)并執(zhí)行它划咐。還要注意,在require語(yǔ)句中钧萍,模塊名以./為前綴褐缠,因?yàn)樗且粋€(gè)本地文件。還要注意风瘦,不需要添加文件擴(kuò)展名队魏。

導(dǎo)出多個(gè)方法和值

我們可以以相同的方式導(dǎo)出多個(gè)方法和值:

const getName = () => {
  return 'Jim';
};

const getLocation = () => {
  return 'Munich';
};

const dateOfBirth = '12.01.1982';

exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;

然后在index.js中:

const user = require('./user');
console.log(
  `${user.getName()} lives in ${user.getLocation()} and was born on ${user.dob}.`
);

以上代碼產(chǎn)生了如下輸出:

Jim lives in Munich and was born on 12.01.1982.

請(qǐng)注意,我們給導(dǎo)出的dateOfBirth變量起的名稱(chēng)可以是任意的(在這種情況下是dob)万搔。它不必與原始變量名相同胡桨。

語(yǔ)法的變化

我還應(yīng)該提一下,可以在文件中逐步導(dǎo)出方法和值瞬雹,而不僅僅是在文件末尾昧谊。

例如:

exports.getName = () => {
  return 'Jim';
};

exports.getLocation = () => {
  return 'Munich';
};

exports.dob = '12.01.1982';

由于解構(gòu)賦值的存在,我們可以挑選我們想要導(dǎo)入的內(nèi)容:

const { getName, dob } = require('./user');
console.log(
  `${getName()} was born on ${dob}.`
);

正如你所期望的那樣酗捌,這會(huì)輸出:

Jim was born on 12.01.1982.

導(dǎo)出默認(rèn)值

在上面的例子中呢诬,我們單獨(dú)導(dǎo)出函數(shù)和值。這對(duì)于可能在整個(gè)應(yīng)用程序中需要的輔助函數(shù)來(lái)說(shuō)非常方便胖缤,但是當(dāng)您有一個(gè)僅導(dǎo)出一個(gè)東西的模塊時(shí)尚镰,更常見(jiàn)的是使用module.exports

class User {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  getUserStats() {
    return `
      Name: ${this.name}
      Age: ${this.age}
      Email: ${this.email}
    `;
  }
}

module.exports = User;

然后在index.js中:

const User = require('./user');
const jim = new User('Jim', 37, 'jim@example.com');

console.log(jim.getUserStats());

以上代碼輸出:

Name: Jim
Age: 37
Email: jim@example.com

module.exports和exports之間有什么區(qū)別?

在網(wǎng)上您可能會(huì)遇到以下語(yǔ)法:

module.exports = {
  getName: () => {
    return 'Jim';
  },

  getLocation: () => {
    return 'Munich';
  },

  dob: '12.01.1982',
};

在這里哪廓,我們將要導(dǎo)出的函數(shù)和值分配給了module上的一個(gè)exports屬性 - 當(dāng)然狗唉,這也完全可行:

const { getName, dob } = require('./user');
console.log(
  `${getName()} was born on ${dob}.`
);

這將輸出:

Jim was born on 12.01.1982.

那么module.exportsexports之間有什么區(qū)別呢?一個(gè)只是另一個(gè)的便捷別名嗎撩独?

嗯敞曹,有點(diǎn),但并非完全如此…

為了說(shuō)明我的意思综膀,讓我們將index.js中的代碼更改為打印module的值:

console.log(module);

這會(huì)產(chǎn)生:

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/home/jim/Desktop/index.js',
  loaded: false,
  children: [],
  paths:
   [ '/home/jim/Desktop/node_modules',
     '/home/jim/node_modules',
     '/home/node_modules',
     '/node_modules' ] }

您可以看到澳迫,module有一個(gè)exports屬性。讓我們向其中添加一些內(nèi)容:

// index.js
exports.foo = 'foo';
console.log(module);
這會(huì)輸出:

javascript
Copy code
Module {
  id: '.',
  exports: { foo: 'foo' },
  ...

exports分配屬性也會(huì)將它們添加到module.exports中剧劝。這是因?yàn)椋ㄖ辽僭陂_(kāi)始時(shí))exports是對(duì)module.exports的引用橄登。

應(yīng)該使用哪一個(gè)呢?

由于module.exportsexports都指向同一個(gè)對(duì)象,通常使用哪一個(gè)都無(wú)所謂拢锹。例如:

exports.foo = 'foo';
module.exports.bar = 'bar';

這段代碼會(huì)導(dǎo)出模塊的對(duì)象為{ foo: 'foo', bar: 'bar' }谣妻。

然而,需要注意一點(diǎn)卒稳。無(wú)論您將module.exports分配給什么蹋半,最終導(dǎo)出的都是您的模塊。

所以充坑,看下面的例子:

exports.foo = 'foo';
module.exports = () => { console.log('bar'); };

這只會(huì)導(dǎo)出一個(gè)匿名函數(shù)减江。foo變量會(huì)被忽略。

其他module.exportsexports的常見(jiàn)問(wèn)題

  • module.exportsexportsNode.js中有什么區(qū)別捻爷?

module.exports和exports都用于從模塊中導(dǎo)出值辈灼。exports本質(zhì)上是對(duì)module.exports的引用,所以您可以任選其一使用也榄。然而巡莹,通常建議使用module.exports以避免潛在問(wèn)題。

  • 我可以在同一個(gè)模塊文件中同時(shí)使用module.exportsexports嗎甜紫?

是的降宅,您可以在同一個(gè)模塊中同時(shí)使用兩者。但在這樣做時(shí)要小心棵介,最好保持一致性钉鸯,堅(jiān)持使用一種慣例。

  • 在使用module.exportsexports時(shí)有哪些常見(jiàn)問(wèn)題邮辽?

一個(gè)常見(jiàn)的是直接重新賦值exports唠雕,這可能導(dǎo)致意想不到的結(jié)果。最好使用module.exports以獲得更好的一致性吨述,并避免潛在的問(wèn)題岩睁。

  • module.exportsexports之間是否有性能差異?

兩者之間沒(méi)有明顯的性能差異揣云。選擇它們更多地取決于編碼風(fēng)格和約定捕儒。

  • 我可以在瀏覽器中使用module.exportsexports嗎,還是它們只適用于Node.js邓夕?

module.exportsexports是特定于Node.js的刘莹,在瀏覽器的JavaScript中不可用。在瀏覽器中焚刚,通常使用其他機(jī)制点弯,如ES6模塊進(jìn)行導(dǎo)出和導(dǎo)入。

總結(jié)

module已經(jīng)成為JavaScript生態(tài)系統(tǒng)的一個(gè)重要組成部分矿咕,使我們能夠?qū)⒋笮统绦蚪M合成較小的部分抢肛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狼钮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捡絮,更是在濱河造成了極大的恐慌熬芜,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件福稳,死亡現(xiàn)場(chǎng)離奇詭異涎拉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)灵寺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)曼库,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)区岗,“玉大人略板,你說(shuō)我怎么就攤上這事〈鹊蓿” “怎么了叮称?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)藐鹤。 經(jīng)常有香客問(wèn)我瓤檐,道長(zhǎng),這世上最難降的妖魔是什么娱节? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任挠蛉,我火速辦了婚禮,結(jié)果婚禮上肄满,老公的妹妹穿的比我還像新娘谴古。我一直安慰自己,他們只是感情好稠歉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布掰担。 她就那樣靜靜地躺著,像睡著了一般怒炸。 火紅的嫁衣襯著肌膚如雪带饱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天阅羹,我揣著相機(jī)與錄音勺疼,去河邊找鬼。 笑死捏鱼,一個(gè)胖子當(dāng)著我的面吹牛执庐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播穷躁,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼耕肩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼因妇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起猿诸,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤婚被,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梳虽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體址芯,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年窜觉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谷炸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禀挫,死狀恐怖旬陡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情语婴,我是刑警寧澤描孟,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站砰左,受9級(jí)特大地震影響匿醒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缠导,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一廉羔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧僻造,春花似錦憋他、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蔬螟,卻和暖如春此迅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旧巾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工耸序, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲁猩。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓坎怪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親廓握。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搅窿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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