node 的模塊運行機制簡單瞭解。 涉及大概流程,略過的底層系統區別。javascript
CommonJS 的規範,包括模塊引用,模塊定義,模塊標識,3個部分html
模塊引用: 模塊經過require方法來同步加載所依賴的模塊java
模塊定義: 在node中一個文件就是一個模塊,提供exports對象導出當前模塊的方法或變量node
模塊標識: 模塊標識傳遞給require()方法的參數,能夠是按小駝峯(camelCase)命名的字符串,也能夠是文件路徑。c++
模塊內容導出兩種方式:git
a.js的內容以下,github
方式一:可將須要導出的變量或函數掛載到 exports 對象的屬性上json
// node.js 每個文件都是一個單獨模塊
// Node對獲取的Javascript文件的內容進行了包裝,以傳入以下變量
console.log(exports, require, module, __filename, __dirname);
// 可將須要導出的變量或函數掛載到 exports 對象的屬性上,
exports.name = 'luoxiaobu';
exports.age = '18'複製代碼
方式二:使用 module.exports 對象總體導出一個變量對象或者函數bootstrap
// node.js 每個文件都是一個單獨模塊
// Node對獲取的Javascript文件的內容進行了包裝,以傳入以下變量
console.log(exports, require, module, __filename, __dirname);
let name = 'luoxiaobu';
let age = '18'
// 使用 module.exports 對象總體導出一個變量對象或者函數,
module.exports = {name,age};複製代碼
模塊的引用的方式: 按照引用模塊的來源來分緩存
// 核心模塊的引入 node本身的模塊
let crypto = require('crypto')
// 用戶本身編寫的模塊引入
let aModule = require('./a.js')
// 第三方,別人實現發佈的模塊(其實也是其餘用戶編寫)
let proxy = require('http-proxy');複製代碼
// 非 node NativeModule
function Module(id = '', parent) {
this.id = id;
this.path = path.dirname(id);
this.exports = {};
this.parent = parent;
updateChildren(parent, this, false);
this.filename = null;
this.loaded = false;
this.children = [];
}複製代碼
// Set up NativeModule.
function NativeModule(id) {
this.filename = `${id}.js`;
this.id = id;
this.exports = {};
this.module = undefined;
this.exportKeys = undefined;
this.loaded = false;
this.loading = false;
this.canBeRequiredByUsers = !id.startsWith('internal/');
}複製代碼
// Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call
// `NativeModule.prototype.compileForPublicLoader()` and return the exports.
// 3. Otherwise, create a new module for the file and save it to the cache.
// Then have it load the file contents before returning its exports
// object.
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
...
}
// 查找文件具體位置
const filename = Module._resolveFilename(request, parent, isMain);
// 存在緩存,則不須要再次執行 返回緩存
const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
if (!cachedModule.loaded)
return getExportsForCircularRequire(cachedModule);
return cachedModule.exports;
}
// 加載node原生模塊,原生模塊loadNativeModule
// 若是有 且能被用戶引用 返回 mod.exports(這包括node模塊的編譯建立module對象,將模塊運行結果保存在module對象上)
const mod = loadNativeModule(filename, request);
if (mod && mod.canBeRequiredByUsers) return mod.exports;
// 建立一個模塊
// Don't call updateChildren(), Module constructor already does.
const module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
// 緩存模塊
Module._cache[filename] = module;
if (parent !== undefined) {
relativeResolveCache[relResolveCacheIdentifier] = filename;
}
// 加載執行新的模塊
module.load(filename);
return module.exports;
};複製代碼
大概源碼結構:(只標註了部分感興趣的)
Node Bindings: 是溝通JS 和 C++的橋樑,將V8 引擎暴露的c++ 接口轉換成JS API
V8: JavaScript的引擎,提供JavaScript運行環境
c++ 模塊的引用大概流程
// This file creates the internal module & binding loaders used by built-in// modules. In contrast, user land modules are loaded using// lib/internal/modules/cjs/loader.js (CommonJS Modules) or// lib/internal/modules/esm/* (ES Modules).//// This file is compiled and run by node.cc before bootstrap/node.js// was called, therefore the loaders are bootstraped before we start to// actually bootstrap Node.js. It creates the following objects:
Process.binding / InternalBinding 其實是C++函數,是用於將Node標準庫中C++端和Javascript端鏈接起來的橋樑。
此類模塊的大概加載流程:
路徑分析
const filename = Module._resolveFilename(request, parent, isMain);複製代碼
是否有緩存
const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}複製代碼
建立module對象
const module = new Module(filename, parent);
// 緩存 module 對象
Module._cache[filename] = module;複製代碼
文件定位根據後綴編譯執行
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
if (experimentalModules && filename.endsWith('.js')) {
const pkg = readPackageScope(filename);
if (pkg && pkg.type === 'module') {
throw new ERR_REQUIRE_ESM(filename);
}
}
const content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
if (manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
// Native extension for .node
Module._extensions['.node'] = function(module, filename) {
if (manifest) {
const content = fs.readFileSync(filename);
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
// Be aware this doesn't use `content`
return process.dlopen(module, path.toNamespacedPath(filename));
};複製代碼
返回module.exports 對象。
node 的模塊運行機制簡單瞭解。 涉及大概流程,略過的底層系統區別。
文章整理了相關資料,記錄了部分實踐和本身的理解,理解不許確之處,還請教正。歡迎一塊兒討論學習。
參考資料: