首頁 web前端 js教程 Node.js模組載入詳解

Node.js模組載入詳解

Dec 26, 2016 am 09:32 AM

JavaScript是世界上使用頻率最高的程式語言之一,它是Web世界的通用語言,並被所有瀏覽器所使用。 JavaScript的誕生要追溯到Netscape那個時代,它的核心內容被倉促的開發出來,用以對抗Microsoft,參與當時白熱化的瀏覽器大戰。由於過早的發布,無可避免的造成了它的一些不太好的特性。

儘管它的開發時間很短,但是JavaScript依然具備了許多強大的特性,不過,每個腳本共享一個全域命名空間這個特性除外。

一旦Web頁面載入了JavaScript程式碼,它就會被注入到全域命名空間,會和其他所有已載入的腳本公用同一個位址空間,這會導致很多安全性問題,衝突,以及一些常見問題,讓bug即難以追蹤又很難解決。

不過謝天謝地,Node為伺服器端JavaScript定了一些規範,也實作了CommonJS的模組標準,在這個標準裡,每個模組都有自己的上下文,和其他模組相區分。這意味著,模組不會污染全域作用域,因為根本沒有所謂的全域作用域,模組之間也不會互相干擾。

本章,我們將了解幾種不同的模組以及如何載入它們。

把程式碼拆分成一系列定義良好的模組可以幫你掌控你的應用程序,下面我們將學習如何創建和使用你自己的模組。

了解Node如何載入模組

Node裡,可以透過檔案路徑來引用模組,也可以透過模組名引用,如果用名稱引用非核心模組,Node最後會把模組名影射到對應的模組檔案路徑。而那些包含了核心函數的核心模組,會在Node啟動時預先載入。

非核心模組包括使用NPM(Node Package Manager)安裝的第三方模組,以及你或你的同事創建的本機模組。

每個被目前腳本導入的模組都會向程式設計師揭露一組公開API,使用模組前,需要用require函式來導入它,像這樣:

var module = require(‘module_name')
登入後複製

上面的程式碼會導入一個名為module_name的模組,它可能是個核心模組,也可以是用NPM安裝的模組,require函數傳回一個包含模組所有公共API的物件。隨模組的不同,傳回的物件可能是任何JavaScript值,可以是一個函數,也可以是一個包含了一系列屬性(函數,陣列或任何JavaScript物件)的物件。

導出模組

CommonJS模組系統是Node下文件間共享物件和函數的唯一方式。對於一個很複雜的程序,你應該把一些類,物件或函數重構成一系列良好定義的可重複使用模組。對於模組使用者來說,模組僅對外暴露出那些你指定的程式碼。

在下面的例子裡你將會了解到,在Node里文件和模組是一一對應的,我們創建了一個叫circle.js的文件,它僅對外導出了Circle建構子。

function Circle(x, y, r) {
       function r_squared() {
              return Math.pow(r, 2);
       }
       function area() {
              return Math.PI * r_squared();
       }
       return {area: area};
}
module.exports = Circle;
登入後複製

程式碼裡最重要的是最後一行,它定義了模組對外導出了什麼內容。 module是個特殊的變量,它代表當前模組自身,而module.exports是模組對外導出的對象,它可以是任何對象,在這個例子裡,我們把Circle的建構子導出了,這樣模組使用者就可以用這個模組來創建Circle實例。

你也可以導出一些複雜的對象,module.exports被初始化成一個空對象,你把任何你想暴露給外界的內容,作為module.exports對象的屬性來導出。例如,你設計了一個模組,它對外暴露了一組函數:

                  function printA() {
         console.log('A');
}
function printB() {
         console.log('B');
}
function printC() {
         console.log('C');
}
module.exports.printA = printA;
module.exports.printB = printB;
module.exports.pi = Math.PI;
登入後複製

這個模組導出了兩個函數(printA和printB)和一個數字(pi),呼叫程式碼看起來像這樣:

var myModule2 = require('./myModule2');
myModule2.printA(); // -> A
myModule2.printB(); // -> B
console.log(myModule2.pi); // -> 3.141592653589793
登入後複製

載入模組

前面提到過,你可以使用require函數來載入模組,不用擔心在程式碼裡呼叫require會影響全域命名空間,因為Node裡就沒有全域命名空間這個概念。如果模組存在且沒有任何語法或初始化錯誤,require函數就會傳回這個模組對象,你也可以這個物件賦值給任何一個局部變數。

模組有幾種不同的類型,大概可以分為核心模組,本地模組和透過NPM安裝的第三方模組,根據模組的類型,有幾種引用模組的方式,下面我們就來了解下這些知識。

載入核心模組

Node有一些被編譯到二進位檔案裡的模組,稱為核心模組,它們不能透過路徑來引用,只能用模組名稱。核心模組擁有最高的載入優先權,即使已經有了一個同名的第三方模組,核心模組也會被優先載入。

例如,如果你想載入和使用http核心模組,可以這樣做:

         var http = require('http');
登入後複製

這將傳回一個包含了http模組對象,它包含了Node API文件裡定義的那些htpp模組的API。

載入檔案模組

你也可以使用絕對路徑從檔案系統載入模組:

var myModule = require('/home/pedro/my_modules/my_module');
登入後複製

也可以用一個基於目前檔案的相對路徑:

var myModule = require('../my_modules/my_module');

var myModule2 = require('./lib/my_module_2');
登入後複製

注意上面的代码,你可以省略文件名的扩展名,如果Node找不到这个文件,会尝试在文件名后加上js后缀再次查找(译者注:其实除了js,还会查找json和node,具体可以看官网文档),因此,如果在当前目录下存在一个叫my_module.js的文件,会有下面两种加载方式:

var myModule = require('./my_module');
var myModule = require('./my_module.js');
登入後複製

加载目录模块

你还可以使用目录的路径来加载模块:

var myModule = require('./myModuleDir');
登入後複製

Node会假定这个目录是个模块包,并尝试在这个目录下搜索包定义文件package.json。

如果没找到,Node会假设包的入口点是index.js文件(译者注:除了index.js还会查找index.node,.node文件是Node的二进制扩展包,具体见官方文档),以上面代码为例,Node会尝试查找./myModuleDir/index.js文件。

反之,如果找到了package.json文件,Node会尝试解析它,并查找包定义里的main属性,然后把main属性的值当作入口点的相对路径。以本例来说,如果package.json定义如下:

                   {
                            "name" : "myModule",
                            "main" : "./lib/myModule.js"
                   }
登入後複製

Node就会尝试加载./myModuleDir/lib/myModule.js文件

从node_modules目录加载

如果require函数的参数不是相对路径,也不是核心模块名,Node会在当前目录的node_modules子目录下查找,比如下面的代码,Node会尝试查找文件./node_modules/myModule.js:

var myModule = require('myModule.js');
登入後複製

如果没找到,Node会继续在上级目录的node_modules文件夹下查找,如果还没找到就继续向上层目录查找,直到找到对应的模块或者到达根目录。


你可以使用这个特性来管理node_modules目录的内容或模块,不过最好还是把模块的管理任务交给NPM(见第一章),本地node_modules目录是NPM安装模块的默认位置,这个设计把Node和NPM关联在了一起。通常,作为开发人员不必太关心这个特性,你可以简单的使用NPM安装,更新和删除包,它会帮你维护node_modules目录

缓存模块

模块在第一次成功加载后会被缓存起来,就是说,如果模块名被解析到同一个文件路径,那么每次调用require(‘myModule')都确切地会返回同一个模块。

比如,有一个叫my_module.js的模块,包含下面的内容:

console.log('module my_module initializing...');
module.exports = function() {
         console.log('Hi!');
};
console.log('my_module initialized.');
登入後複製

然后用下面的代码加载这个模块:

var myModuleInstance1 = require('./my_module');
登入後複製

它会产生下面的输出:

module my_module initializing...
my_module initialized
登入後複製
登入後複製

如果我们两次导入它:

var myModuleInstance1 = require('./my_module');
var myModuleInstance2 = require('./my_module');
登入後複製
登入後複製

如果我们两次导入它:

var myModuleInstance1 = require('./my_module');
var myModuleInstance2 = require('./my_module');
登入後複製
登入後複製

输出依然是:

module my_module initializing...
my_module initialized
登入後複製
登入後複製

也就是说,模块的初始化代码仅执行了一次。当你构建自己的模块时,如果模块的初始化代码里含有可能产生副作用的代码,一定要特别注意这个特性。

小结

Node取消了JavaScript的默认全局作用域,转而采用CommonJS模块系统,这样你可以更好的组织你的代码,也因此避免了很多安全问题和bug。可以使用require函数来加载核心模块,第三方模块,或从文件及目录加载你自己的模块

还可以用相对路径或者绝对路径来加载非核心模块,如果把模块放到了node_modules目录下或者对于用NPM安装的模块,你还可以直接使用模块名来加载。

译者注:

建议读者把官方文档的模块章节阅读一遍,个人感觉比作者讲得更清晰明了,而且还附加了一个非常具有代表性的例子,对理解Node模块加载会很有很大帮助。下面把那个例子也引用过来:

用require(X) 加载路径Y下的模块
1. 如果X是核心模块,
   a. 加载并返回核心模块
   b. 结束
2. 如果X以 './' or '/' or '../ 开始'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. 抛出异常:"not found"
LOAD_AS_FILE(X)
1. 如果X是个文件,把 X作为JavaScript 脚本加载,加载完毕后结束
2. 如果X.js是个文件,把X.js 作为JavaScript 脚本加载,加载完毕后结束
3. 如果X.node是个文件,把X.node 作为Node二进制插件加载,加载完毕后结束
LOAD_AS_DIRECTORY(X)
1. 如果 X/package.json文件存在,
   a. 解析X/package.json, 并查找 "main"字段.
   b. 另M = X + (main字段的值)
   c. LOAD_AS_FILE(M)
2. 如果X/index.js文件存在,把 X/index.js作为JavaScript 脚本加载,加载完毕后结束
3. 如果X/index.node文件存在,把load X/index.node作为Node二进制插件加载,加载完毕后结束
LOAD_NODE_MODULES(X, START)
1. 另DIRS=NODE_MODULES_PATHS(START)
2. 对DIRS下的每个目录DIR做如下操作:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. 另PARTS = path split(START)
2. 另ROOT = index of first instance of "node_modules" in PARTS, or 0
3. 另I = count of PARTS - 1
4. 另DIRS = []
5. while I > ROOT,
   a. 如果 PARTS[I] = "node_modules" 则继续后续操作,否则下次循环
   c. DIR = path join(PARTS[0 .. I] + "node_modules")
   b. DIRS = DIRS + DIR
   c. 另I = I - 1
6. 返回DIRS
登入後複製

更多Node.js模块加载详解相关文章请关注PHP中文网!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

JavaScript難以學習嗎? JavaScript難以學習嗎? Apr 03, 2025 am 12:20 AM

學習JavaScript不難,但有挑戰。 1)理解基礎概念如變量、數據類型、函數等。 2)掌握異步編程,通過事件循環實現。 3)使用DOM操作和Promise處理異步請求。 4)避免常見錯誤,使用調試技巧。 5)優化性能,遵循最佳實踐。

如何實現視差滾動和元素動畫效果,像資生堂官網那樣?
或者:
怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? 如何實現視差滾動和元素動畫效果,像資生堂官網那樣? 或者: 怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? Apr 04, 2025 pm 05:36 PM

實現視差滾動和元素動畫效果的探討本文將探討如何實現類似資生堂官網(https://www.shiseido.co.jp/sb/wonderland/)中�...

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

See all articles