基於CommonJS規範,簡單實現NodeJs模塊化

目錄

CommonJS

image

CommonJS是一個模塊化的規範,Nodejs的模塊系統,就是參照CommonJS規範實現的。每一個文件就是一個模塊 ,每一個模塊都要本身的做用域。node

特色

  1. 全部代碼都有運行在模塊做用域,不會污染全局做用域
  2. 模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  3. 模塊加載的順序,按照其在代碼中出現的順序。

NodeJs 模塊化簡單實現

首先咱們先了解一下模塊化加載:git

  1. 模塊加載
  2. 模塊解析文件名,解析出絕對路徑來
  3. 咱們引入時能夠省略文件的後綴,有可能沒有寫後綴名, 會自動查找對應目錄下 .js.json的文件
  4. 屢次調用只走一次, 獲得一個真實存在的文件路徑,在緩存中看文件是否存在,不存在則建立模塊,而後放入到緩存中,方便別人讀取
  5. 讀取文件內容。若是是 .js 文件,就把內容加一個閉包
  6. 運行JS腳本,將結果返回

0. 建立module構造函數

每一個模塊內部,都有一個 module 對象,表明當前模塊。github

查看 console.log(module) module都有哪些屬性:json

  • module.id 模塊的識別符,一般是帶有絕對路徑的模塊文件名
  • module.filename 模塊的文件名,帶有絕對路徑
  • module.loaded 返回一個布爾值,表示模塊是否加載
  • module.parent 返回一個對象,表示調用該模塊的模塊
  • module.children 返回一個數組,表示該模塊要用到的其餘模塊
  • module.exports 表示模塊對外輸出的值,默認值爲空對象
function Module(id) {
    // 下面就簡單定義兩個經常使用的
    this.id = id
    this.exports = {}
}
複製代碼

1. 模塊加載

引入本身寫的模塊
// 咱們本身寫的文件模塊 要寫路徑 ./ 或者../
let hello = require('./1.NodeJS')
console.log(hello) // first NodeJS
複製代碼
引入內置模塊就直接寫名稱便可
//操做文件的模塊
let fs = require('fs')

// 處理路徑的模塊
let path = require('path')

// 虛擬機模塊,沙箱運行,防止變量污染
let vm = require('vm')
複製代碼

2. 解析文件,返回絕對路徑

Module._resolveFilename 方法實現的功能是判斷用戶引入的模塊是否包含後綴名,若是沒有後綴名會根據 Module._extensions 的鍵的順序查找文件,直到找到後綴名對應的文件的絕對路徑,優先查找 .js數組

// 將引入文件處理爲絕對路徑
Module._resolveFilename = function (p) {
  // 以js或者json結尾的
  if ((/\.js$|\.json$/).test(p)) {
    // __dirname當前文件所在的文件夾的絕對路徑
    // path.resolve方法就是幫咱們解析出一個絕對路徑出來
    return path.resolve(__dirname, p);
  } else {
    // 沒有後後綴  自動拼後綴 
    // Module._extensions 處理不一樣後綴的模塊
    let exts = Object.keys(Module._extensions);
    let realPath; // 存放真實存在文件的絕對路徑
    for (let i = 0; i < exts.length; i++) {
      // 依次匹配對應擴展名的絕對路徑
      let temp = path.resolve(__dirname, p + exts[i])
      try {
        // 經過fs的accessSync方法對路徑進行查找,找不到對應文件直接報錯 
        fs.accessSync(temp)
        realPath = temp
        break
      } catch (e) {
      }
    }
    if (!realPath) {
      throw new Error('module not exists');
    }
    // 將存在絕對路徑返回
    return realPath
  }
}
複製代碼

Module._extensions 處理對應模塊擴展名。這裏咱們只提 .js .json,對應模塊的處理功能咱們在後面來實現緩存

Module._extensions = {
    "js": function() {},
    "json": function() {}
}
複製代碼

3. 屢次調用只一次

當用戶重複加載一個已經加載過的模塊,咱們只有第一次是加載,而後放入緩存中,後面在加載時,直接返回緩存中便可bash

Module._cacheModule = { } 存放模塊緩存閉包

// 獲取內存中的結果
let cache = Module_cacheModule[filename]
// 判斷是否存在
if (cache) {
    // 若是存在直接將 exports對象返回
    return cache.exports
}
// 若是不存在內存中,就建立模塊,而後加入到內存中
let module = new Module(filename)
Module._cacheModule[filename] = module
複製代碼

4. 加載模塊,對於不一樣類型的文件作不一樣的處理

根據前面咱們說到的模塊化,對於已經存放在內存中的咱們直接返回就能夠了,對於新添加的模塊,咱們該讀取文件了, 根據傳入的模塊,嘗試加載模塊方法app

// 根據傳入的模塊,嘗試加載模塊方法
function tryModuleLoad(module) {
  // 前面咱們已經提到 module.id 爲模塊的識別符,一般是帶有絕對路徑的模塊文件名
  // path.extname 獲取文件的擴展名
  /* let ext = path.extname(module.id);
  // 若是擴展名是js 調用js處理器 若是是json 調用json處理器
  Module._extensions[ext](module); // exports 上就有了數組 */
  let ext = path.extname(module.id);//擴展名
  // 若是擴展名是js 調用js處理器 若是是json 調用json處理器
  Module._extensions[ext](module); // exports 上就有了數組
}
複製代碼

Module._extensions 處理對應後綴名模塊。這裏咱們只提 .js .json模塊化

// 處理對應後綴名模塊
Module._extensions = {
  ".js": function (module) {
    // 對於js文件,讀取內容
    let content = fs.readFileSync(module.id, 'utf8')
    // 給內容添加閉包, 後面實現
    let funcStr = Module.wrap(content)
    // vm沙箱運行, node內置模塊,前面咱們已經引入, 將咱們js函數執行,將this指向 module.exports
    vm.runInThisContext(funcStr).call(module.exports, module.exports, req, module)
  },
  ".json": function (module) {
    // 對於json文件的處理就相對簡單了,將讀取出來的字符串轉換未JSON對象就能夠了
    module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8'))
  }
}
複製代碼

上面咱們使用了 Module.wrap 方法,是幫助咱們添加一個閉包,簡單說就是咱們在外面包了一個函數的前半段和後半段

// 存放閉包字符串
Module.wrapper = [
  "(function (exports, require, module, __filename, __dirname) {",
  "})"
]
複製代碼
// 將咱們讀到js的內容傳入,組合成閉包字符串
Module.wrap = function (script) {
  return Module.wrapper[0] + script + Module.wrapper[1];
}
複製代碼

5. 運行並返回結果

要運行,咱們來看一下完整的模塊加載代碼

// 模塊加載
Module._load = function (f) {
  // 相對路徑,可能這個文件沒有後綴,嘗試加後綴
  let fileName = Module._resolveFilename(f); // 獲取到絕對路徑
  // 判斷緩存中是否有該模塊
  if (Module._cacheModule[fileName]) {
    return Module._cacheModule[fileName].exports
  }
  let module = new Module(fileName); // 沒有就建立模塊
  Module._cacheModule[fileName] = module // 並將建立的模塊添加到緩存

  // 加載模塊
  tryModuleLoad(module)
  return module.exports
}
複製代碼

到這裏,一個簡單的module就實現了,讓咱們測試一下吧

// 測試代碼
function req(p) {
  return Module._load(p); // 加載模塊
}

// 第一次沒有緩存,建立module,並添加到緩存中
let str = req('./1.NodeJS');
// 第二次就是返回的緩存中的
let str1 = req('./1.NodeJS.js');
console.log(str) // first NodeJS
console.log(str1) // first NodeJS
複製代碼

附源碼

重要爲了方便你們瞭解、查看、調試代碼,完整的源碼參見gitHub

總結

本篇文章是基於CommonJS規範,實現了一個簡單的NodeJS模塊化,主要目的在於理解 NodeJS 模塊化的實現思路,但願對你們瞭解模塊化起到必定的做用。

做者:香香

未來的你,必定會感謝如今拼命努力的本身!

Node基礎系列文章

相關文章
相關標籤/搜索