[Node.js] 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 的 執行過程了!

留言

這個網誌中的熱門文章

[MySQL] schema 與資料類型優化

[翻譯] 介紹現代網路負載平衡與代理伺服器