[Node.js] require 的機制
最近在面試的時候, 寫了一些基本的題目, 但是卻有些地方是我自以為清楚,
但是不清楚的地方, 那就是這次要寫的 require!
其實一般人使用應該都會知道 require 完後, Node.js 會把 require 的物件,
會快取在記憶體內, 但是由於我真的太久沒寫, 所以在沒有理解的情況下,
也會忘記是不是真的每次 require 後就會存在記憶體中,
所以這次來分析一下原始碼
https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
從上面就可以看出, 關鍵是在 Module._load
那我們再看一下 Module._load 的程式碼
這樣就可以很清楚, require 的 執行過程了!
但是不清楚的地方, 那就是這次要寫的 require!
其實一般人使用應該都會知道 require 完後, Node.js 會把 require 的物件,
會快取在記憶體內, 但是由於我真的太久沒寫, 所以在沒有理解的情況下,
也會忘記是不是真的每次 require 後就會存在記憶體中,
所以這次來分析一下原始碼
https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
// Loads a module at the given file path. Returns that module's
// `exports` property.
Module.prototype.require = function(id) {
validateString(id, 'id');
if (id === '') {
throw new ERR_INVALID_ARG_VALUE('id', id,
'must be a non-empty string');
}
requireDepth++;
try {
return Module._load(id, this, /* isMain */ false);
} finally {
requireDepth--;
}
};
從上面就可以看出, 關鍵是在 Module._load
那我們再看一下 Module._load 的程式碼
// Check the cache for the requested file.
// 1. 假如一個 module 已經存在快取中, 直接返回其 exports 的物件
// 2. 假如 module 是原生系統內部的 module (ex: file, path),
// 呼叫 NativeModule.prototype.compileForPublicLoader() 並且返回其 exports 的物件
// 3. 否則, 針對這個檔案路徑產生一個新的 module 並且存到快取中
// 然後先將檔案內容載入後, 才回傳其 exports 物件
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
// Fast path for (lazy loaded) modules in the same directory. The indirect
// caching is required to allow cache invalidation without changing the old
// cache key names.
relResolveCacheIdentifier = `${parent.path}\x00${request}`;
const filename = relativeResolveCache[relResolveCacheIdentifier];
if (filename !== undefined) {
const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
delete relativeResolveCache[relResolveCacheIdentifier];
}
}
// 直接從這邊去取檔案名稱
const filename = Module._resolveFilename(request, parent, isMain);
const cachedModule = Module._cache[filename];
// 如果是已經存在 Cache 中的 Module, 直接返回它的 exports 物件
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
// 如果是原生 Module, 直接返回它的 exports 物件
const mod = loadNativeModule(filename, request, experimentalModules);
if (mod && mod.canBeRequiredByUsers) return mod.exports;
// 剛剛上面有仔細看的時候都會發現, 每次取出的時候都需要呼叫 updateChildren
// 主要是確定載入的 Module 是否在它的 parent 底下, 不是的話將其更新到 parent 的 children 陣列底下
// 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;
}
let threw = true;
try {
// Intercept exceptions that occur during the first tick and rekey them
// on error instance rather than module instance (which will immediately be
// garbage collected).
if (enableSourceMaps) {
try {
module.load(filename);
} catch (err) {
rekeySourceMap(Module._cache[filename], err);
throw err; /* node-do-not-add-exception-line */
}
} else {
module.load(filename);
}
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
if (parent !== undefined) {
delete relativeResolveCache[relResolveCacheIdentifier];
}
}
}
return module.exports;
};
這樣就可以很清楚, require 的 執行過程了!
留言
張貼留言