這篇文章帶大家了解一下CommonJs規格和Node的模組機制,介紹一下Node實作CommonJs規範的基本流程,希望對大家有幫助!
在CommonJs規格提出之前,Javascript是沒有模組系統的,這意味著我們很難開發大型的應用,因為程式碼的組織會比較困難。
首先CommonJS不是Node獨有的東西,CommonJs是一種模組規範,定義如何引用和匯出模組,Nodejs只是實作了這個規範,CommonJS模組規範主要分為模組引用、模組定義和模組識別三個部分。
模組引用
模組引用就是我們可以透過require
引入其它的模組。
const { add } = require('./add'); const result = add(1 ,2);
模組定義
一個檔案就是一個模組,模組裡會提供兩個變量,分別為module和exports。 module為目前模組本身,exports為要導出的內容,同時exports為module的一個屬性,即exports為module.exports。其他模組透過require導入的內容即為module.exports的內容。
// add.js exports.add = (a, b) => { return a + b; }
模組識別
模組識別即為require裡面的內容,例如require('./add')
,則模組標識為./add
。
透過CommonJS建構的這套模組導入導出機制使得使用者完全無需考慮變數污染,可以方便的建構大型應用。
Node的模組實作
#Node實作了CommonJs規範,並且增加了一些自己需要的特性。 Node為了實作CommonJs規格主要做了以下三件事:
路徑分析
檔定位
編譯執行
路徑分析
#當執行require()的時候,require接收的參數即為模組標識符,node透過模組標識符來進行路徑分析。路徑分析的目的就是為了透過模組標識符找到這個模組所在的路徑。首先,node的模組分為兩類,分別是核心模組和檔案模組。核心模組是node自帶的模組,檔案模組是使用者編寫的模組。同時文件模組又分為相對路徑形式的文件模組、絕對路徑形式的文件模組和非路徑形式的文件模組(如express)。
當node找到一個檔案模組之後,會將這個模組編譯執行並且快取起來,大致原理是將這個模組的完整路徑作為key,編譯後的內容作為值,後續再第二次引入這個模組的時候就不需要再進行路徑分析檔定位編譯執行這幾個步驟了,可以直接從快取讀取編譯好的內容。
// 缓存的模块示意: const cachedModule = { '/Usr/file/src/add.js': 'add.js编译后的内容', 'http': 'Node自带的http模块编译后的内容', 'express': '非路径形式自定义文件模块express编译后的内容' // ... }
當要尋找require導入的模組時,查找模組的順序是先查看快取裡是否已經有該模組,如果快取裡面沒有再查看核心模組,然後再找找檔案模組。其中路徑形式的文件模組比較好查找,根據相對或絕對路徑就可以得到完整的文件路徑。非路徑形式的自訂檔案模組查找起來會相對麻煩一些,Node會從node_modules這個資料夾裡去查找是否有這個檔案。
node_modules這個目錄在哪裡呢,比如說我們目前執行的檔案為/Usr/file/index.js;
/** * /Usr/file/index.js; */ const { add } = require('add'); const result = add(1, 2);
這個模組裡我們有引入了一個add模組,這個add不是一個核心模組也不是一個路徑形式的檔案模組,那麼這時候要如何找到這個add模組呢。
module有一個paths的屬性,找尋add模組的路徑在paths這個屬性裡,我們可以把這個屬性打出來看一下:
/** * /Usr/file/index.js; */ console.log(module.paths);
我們在file目錄下執行node index. js可以印出paths的值。 paths裡的值是一個數組,如下:
[ '/Usr/file/node_modules', '/Usr/node_modules', '/node_modules', ]
即Node會依序從上面的目錄裡尋在是否包含add這個模組,原理和原型鏈類似。先從目前執行的檔案的同級目錄的node_modules資料夾裡開始找,如果沒找到或沒有node_modules這個目錄,則繼續往上級查找。
檔案定位
路徑分析和檔案定位是搭配一起使用的,檔案標識符可以是不帶後綴的,也可能透過路徑分析找到的是一個目錄或一個包,這個時候要定位到具體的檔案需要一些額外的處理。
檔案副檔名分析
const { add } = require('./add');
比如上面这段代码,文件标识符是不带扩展名的,这个时候node会依次查找是否存在.js、.json、.node文件。
目录和包分析
同样是上面这段代码,通过./add
查找到的可能不是一个文件,可能是一个目录或者包(通过判断add文件夹下是否有package.json文件来判断是目录还是包)。这个时候文件定位的步骤是这样的:
如果package.json里没有main字段,那么也会将index作为文件,然后进行扩展名分析找到对应后缀的文件。
模块编译
我们开发中主要遇到的模块为json模块和js模块。
json模块编译
当我们require一个json模块的时候,实际上Node会帮我们使用fs.readFilcSync去读取对应的json文件,得到json字符串,然后调用JSON.parse解析得到json对象,再赋值给module.exports,然后给到require。
js模块编译
当我们require一个js模块的时候,比如
// index.js const { add } = require('./add');
// add.js exports.add = (a, b) => { return a + b; }
这个时候发生了什么呢,为什么我们可以直接在模块里使用module、exports、require这些变量。这是因为Node在编译js模块的时候对模块的内容进行了首尾的包装。
比如add.js这个模块,实际编译的时候是会被包装成类似这样的结构:
(function(require, exports, module) { exports.add = (a, b) => { return a + b; } return module.exports; })(require, module.exports, module)
即我们编写的js文件是会被包装成一个函数,我们编写的只是这个函数里的内容,Node后续的包装的过程对我们隐藏了。这个函数支持传入一些参数,其中就包括require、exports和module。
当编译完js文件后,就会执行这个文件,node会将对应的参数传给这个函数然后执行,并且返回module.exports值给到require函数。
以上就是Node实现CommonJs规范的基本流程。
更多node相关知识,请访问:nodejs 教程!
以上是深入了解Node的模組機制,聊聊模組實作流程的詳細內容。更多資訊請關注PHP中文網其他相關文章!