nodejs的模塊系統(實例分析exprots和module.exprots)


前言:工欲善其事,必先利其器。模塊系統是nodejs組織管理代碼的利器也是調用第三方代碼的途徑,本文將詳細講解nodejs的模塊系統。在文章最后實例分析一下exprots和module.exprots。

nodejs的模塊

什么是模塊?

node.js通過實現CommonJS的Modules/1.0標准引入了模塊(module)概念,模塊是Node.js的基本組成部分.一個node.js文件就是一個模塊,也就是說文件和模塊是一一對應的關系.這個文件可以是JavaScript代碼,JSON或者編譯過的C/C++擴展.

Node.js的模塊分為兩類,一類為原生(核心)模塊,一類為文件模塊。

在文件模塊中,又分為3類模塊。這三類文件模塊以后綴來區分,Node.js會根據后綴名來決定加載方法。

  • .js。通過fs模塊同步讀取js文件並編譯執行。
  • .node。通過C/C++進行編寫的Addon。通過dlopen方法進行加載。
  • .json。讀取json文件,調用JSON.parse解析加載。

Node提供了exports和require兩個對象,其中exports是模塊公開的接口,require用於從外部獲取一個模塊接口,即所獲取模塊的exports對象


require和exports

require

require函數用於在當前模塊中加載和使用別的模塊,傳入一個模塊名,返回一個模塊導出對象。require方法接受以下幾種參數的傳遞:

  • http、fs、path等。原生模塊。
  • ./mod或../mod。相對路徑的文件模塊。
  • /a/mod,絕對路徑的文件模塊。
  • mod,非原生模塊的文件模塊。

exports

exports對象是當前模塊的導出對象,用於導出模塊公有方法和屬性。別的模塊通過require函數使用當前模塊時得到的就是當前模塊的exports對象。

module

通過module對象可以訪問到當前模塊的一些相關信息,但最多的用途是替換當前模塊的導出對象

  • module.exports :{Object}類型,模塊系統自動產生。

  • module.require(id)

id {String} 
Return: {Object} 已解析模塊的 module.exports 
這個方法提供了一種像 require() 一樣從最初的模塊加載一個模塊的方法。

    • module.id:{String}類型,用於區別模塊的標識符。通常是完全解析后的文件名

    • module.filename:{String}類型,模塊完全解析后的文件名。

    • module.loaded:{Boolean}類型,判斷該模塊是否加載完畢。

    • module.parent:{Module Object}類型,返回引入了本模塊的其他模塊。

    • module.children:{Array}類型,該模塊所引入的其他子模塊。

demo1 module.exports的使用

sayHello.js:

function sayHello() {
    console.log('hello');
}

module.exports = sayHello;



app.js:

var sayHello = require('./sayHello');
sayHello();

//hello

代碼講解:

定義一個sayHello模塊,模塊里定義了一個sayHello方法,通過替換當前模塊exports對象的方式將sayHello方法導出。

在app.js中加載這個模塊,得到的是一個函數,調用該函數,控制台打印hello。

 

demo2 匿名替換

sayWorld.js

module.exports = function () {
    console.log('world');
}

app.js

var sayWorld = require('./sayWorld');
sayWorld();

//world

代碼講解

與上面稍有不同,這次是匿名替換。

demo3 替換為字符串

不僅可以替換為方法,也可以替換為字符串等。

stringMsg.js

module.exports = 'i am a string msg!';

app.js

var string = require('./stringMsg');
console.log(string);

//i am a string msg!


demo4 exports導出多個變量

當要導出多個變量怎么辦呢?這個時候替換當前模塊對象的方法就不實用了,我們需要用到exports對象。

useExports.js

exports.a = function () {
    console.log('a exports');
}

exports.b = function () {
    console.log('b exports');
}

 

app,js

var useExports = require('./useExports');
useExports.a();
useExports.b();
//a exports
//b exports
當然,將useExports.js改成這樣也是可以的:
module.exports.a = function () {
    console.log('a exports');
}

module.exports.b = function () {
    console.log('b exports');
}

 

下面通過gif圖進行演示:

這里寫圖片描述

module.exports和exports在文章的最后會進行詳細講解。

 

模塊初始化

一個模塊中的JS代碼僅在模塊第一次被使用時執行一次,並在執行過程中初始化模塊的導出對象。之后,緩存起來的導出對象被重復利用。

舉個例子,count,js:

var i = 0;

function count() {
    return ++i;
}

exports.count = count;

  

app.js

var c1 = require('./count');
var c2 = require('./count');

console.log(c1.count());
console.log(c2.count());
console.log(co2.count());
//1
//2
//3

可以看到,count.js並沒有因為被require了兩次而初始化兩次。

主模塊

通過命令行參數傳遞給NodeJS以啟動程序的模塊被稱為主模塊。主模塊負責調度組成整個程序的其它模塊完成工作。例如通過以下命令啟動程序時,我們剛剛一直使用的app.js就是主模塊。

二進制模塊

雖然一般我們使用JS編寫模塊,但NodeJS也支持使用C/C++編寫二進制模塊。編譯好的二進制模塊除了文件擴展名是.node外,和JS模塊的使用方式相同。雖然二進制模塊能使用操作系統提供的所有功能,擁有無限的潛能,但對於不熟悉C/C++的人而言編寫過於困難,並且難以跨平台使用,因此本文不作講解。

模塊的加載優先級

由於Node.js中存在4類模塊(原生模塊和3種文件模塊),盡管require方法極其簡單,但是內部的加載卻是十分復雜的,其加載優先級也各自不同,下面是require加載的邏輯圖:

這里寫圖片描述

原生模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,加載的速度最快。另一類文件模塊是動態加載的,加載速度比原生模塊慢。但是Node.js對原生模塊和文件模塊都進行了緩存,於是在第二次require時,是不會有重復開銷的。

exports與module.exports

這里可能是最容易混淆的地方了。

我們先來看一個例子:

modOne.js

exports.hello = function () {
    console.log("hello");
}

module.exports = function () {
    console.log('world');
}

app.js

var one = require('./modOne');

//one.hello(); //執行這句話會報錯one.hello is not a function

one() //打印world

這是為什么呢?我們得先從exports 與module.exports 說起。

其實,exports 是module.exports的一個引用,exports 的地址指向module.exports。

而我們的modOne.js中通過module.exports = function的方式將module.exports給替換掉了。

而require方法所返回的是module.exports這個實實在在的對象,但是它已經被替換成了function,這就導致了exports指向了空,所以,你所定義的exports.hello是無效的。

用一個通俗易懂的例子來重新解釋一遍。

比如你在電腦的D盤下新建了一個exports文本文檔,然后你右鍵->發送到桌面快捷方式。

D盤就相當於nodejs中的module,這個exports文本文檔就相當於nodejs中模塊的exports對象,快捷方式就相當於nodejs中指向exports對象引用

D:/exportes.txt ==> module.exportes 
exportes.txt快捷方式 ==> exportes

然后,你看exportes.txt不爽,把它給刪了,然后新建了一個word文檔–exports.docx。

這個時候你桌面上的快捷方式就沒用了,雖然也叫exports,但是你是訪問不到這個新的word文件的。

對於nodejs也一樣,當你把module.exportes對象覆蓋了,換成了其他東西的時候,exportes這個引用就失效了。

同樣,我們還可以用這個例子來理解為什么exportes也可以用來導出模塊。

我們是這樣使用exportes的:

exports.hello = function () {
    console.log("hello");
}

這段代碼其實等同於:

module.exports.hello = function () {
    console.log("hello");
}

怎么理解呢。還是剛才的txt文件,這次沒有刪除。

D:/exportes.txt ==> module.exportes 
exportes.txt快捷方式 ==> exportes

你在桌面打開了exportes.txt快捷方式,然后在里面輸入hello,然后保存,關閉。

你再打開D:/exportes.txt,你會發現你可以看到剛剛寫的hello,你又在后面添加了一句“world”,保存關閉。

返回桌面,打開快捷方式,你會看到helloworld。

所以說你使用’exports.屬性’和’module.exportes.屬性’是等同的。

這也就能很好的解釋下面這個問題了:

exports = function() {
   console.log('hello');
}

//這樣寫會報錯

這樣相當於把exprots這個引用覆蓋掉了,你把txt文件的快捷方式改成docx的快捷方式還能打開原來的txt文件么?顯然是不能的。

最后做一個總結:

當我們想讓模塊導出的是一個對象時, 使用exports 和 module.exports 都可以(但 exports 也不能重新覆蓋為一個新的對象),而當我們想導出非對象接口時,就必須也只能覆蓋 module.exports 。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM