nodejs實現(xiàn)一個自定義的require方法的詳細流程
1.前言
大家對nodejs中的require方法應該不會陌生,這個方法可以用來導入nodejs的內置模塊,自定義模塊,第三方模塊等,使用頻率非常高,那么這個方法內部是如何實現(xiàn)的呢?本篇文章就是從頭到尾拆分實現(xiàn)流程,最終實現(xiàn)一個自定義的require方法的
2.前置操作
導入所需的nodejs內置庫,分別為fs庫用來加載文件內容,path庫用于操作路徑,vm模塊執(zhí)行js代碼
const fs = require('fs')
const path = require('path')
const vm = require('vm')
3.操作步驟拆分
- 路徑分析,解析出絕對路徑
- 緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
- 文件定位,確定當前模塊的文件類型(
js,json等等文件,方便后續(xù)調用對應的編譯函數(shù)) - 編譯執(zhí)行,將加載模塊的內容變?yōu)榭梢栽诋斍澳K中直接使用的數(shù)據(jù)
4.開始創(chuàng)建自定義的require方法
1.創(chuàng)建customRequire方法
function customRequire(moduleName){}
2.創(chuàng)建一個Module類,用于存儲模塊信息,以及掛載一些輔助方法
class Module {
constructor(id) {
this.id = id
this.exports = {}
}
}
3.執(zhí)行第一步路徑分析,解析出絕對路徑操作
// 給module類添加一個靜態(tài)屬性,用于表示模塊的加載函數(shù)以及模塊后綴的加載順序
Module._extensions = {
'.js'(module) {},
'.json'(module) {},
// ...
}
// 創(chuàng)建輔助方法,用于解析導入模塊的絕對路徑
Module._resolveFilename = (moduleName) => {
// 獲取當前模塊的絕對路徑
let filePath = path.resolve(__dirname, moduleName)
// 獲取當前模塊的后綴名
const extName = path.extname(moduleName)
// 判斷文件是否存在
if (!fs.existsSync(filePath)) {
// 如果文件不存在,則按照順序查找后綴名
const keys = Object.keys(Module._extensions)
// 文件是否存在標識
let flag = false
for (let i = 0; i < keys.length; i++) {
// 獲取當前循環(huán)的后綴名
const currentExtName = keys[i]
// 拼接后綴名
const currentFilePath = filePath + currentExtName
// 判斷拼接后綴名之后的文件路徑是否存在
if (fs.existsSync(currentFilePath)) {
// 如果存在,則將當前文件路徑賦值給filePath
filePath = currentFilePath
// 將文件是否存在標識設置為true
flag = true
// 終止循環(huán)
break
}
}
// 如果不存在,則進行報錯
if (!flag) {
throw new Error(`${moduleName} is not exists`)
}
}
// 返回處理后得到的文件的絕對路徑
return filePath
}
// 創(chuàng)建自定義的require方法
function customRequire(moduleName) {
// 路徑分析,解析出模塊的絕對路徑
const absPath = Module._resolveFilename(moduleName)
}
4.執(zhí)行緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
// 創(chuàng)建自定義的require方法
function customRequire(moduleName) {
// 路徑分析,解析出模塊的絕對路徑
const absPath = Module._resolveFilename(moduleName)
// 緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
const cacheModel = Module._cache[absPath]
if (cacheModel) return cacheModel.exports
}
5.執(zhí)行創(chuàng)建空對象目標加載模塊操作(使用絕對路徑作為模塊的id)
// 定義空對象,用于存儲緩存的模塊
Module._cache = {}
// 創(chuàng)建自定義的require方法
function customRequire(moduleName) {
// 路徑分析,解析出模塊的絕對路徑
const absPath = Module._resolveFilename(moduleName)
// 緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
const cacheModel = Module._cache[absPath]
if (cacheModel) return cacheModel.exports
// 創(chuàng)建空對象目標加載模塊(使用絕對路徑作為模塊的id)
let module = new Module(absPath)
// 將已加載的模塊緩存起來
Module._cache[absPath] = module
}
6.執(zhí)行模塊的執(zhí)行加載(編譯執(zhí)行)
// 在module類的原型鏈上新增load方法,用于加載模塊
Module.prototype.load = function () {
// 獲取當前模塊的后綴名
const extName = path.extname(this.id)
// 根據(jù)后綴名調用對應的加載函數(shù)
Module._extensions[extName](this)
}
// 這里定義不同文件的不同加載與處理方法
Module._extensions = {
'.js'(module) {
// 讀取module.id對應的文件內容(這里的id對應的是需要讀取的文件的絕對路徑)
let content = fs.readFileSync(module.id, 'utf8')
// 包裝內容,使其成為一個函數(shù),主要作用就是為了讓加載的模塊中可以使用一些全局變量
content = Module.wrapper[0] + content + Module.wrapper[1]
// 使用VM模塊,將字符串內容變?yōu)橐粋€函數(shù)
const compileFn = vm.runInThisContext(content)
// 定義上方創(chuàng)建的函數(shù)的形參對應的實參
let exports = module.exports // 這里的module.exports就是接收到的形參,Module的實例化對象對exports靜態(tài)屬性
let filename = module.id
let dirname = path.dirname(module.id)
// 調用函數(shù),這里使用call方法,
// 1.將this指向module.exports,call方法的第一個值就是重新指向的this
// 2.此時被加載的模塊中就可以使用module.exports,所以被加載模塊中的變量就可以存儲到module.exports中
// 3.然后將其他的參數(shù)傳遞給函數(shù)
// 4.基于call的特性,會立即執(zhí)行此函數(shù)
compileFn.call(exports, exports, customRequire, module, filename, dirname)
// 返回被改動后的module.exports
return module.exports
},
'.json'(module) {
// 讀取module.id對應的文件內容(這里的id對應的是需要讀取的文件的絕對路徑)
let content = fs.readFileSync(module.id, 'utf8')
// 將讀取到的內容變?yōu)橐粋€對象
content = JSON.parse(content)
// 將讀取到的內容賦值給module.exports
module.exports = content
},
// ...
}
// 定義空對象,用于存儲緩存的模塊
Module._cache = {}
// 創(chuàng)建自定義的require方法
function customRequire(moduleName) {
// 路徑分析,解析出模塊的絕對路徑
const absPath = Module._resolveFilename(moduleName)
// 緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
const cacheModel = Module._cache[absPath]
if (cacheModel) return cacheModel.exports
// 創(chuàng)建空對象目標加載模塊(使用絕對路徑作為模塊的id)
let module = new Module(absPath)
// 將已加載的模塊緩存起來
Module._cache[absPath] = module
// 執(zhí)行加載(編譯執(zhí)行)
module.load()
// 返回模塊加載后的結果(注意,不能將id也返回出去)
return module.exports
}
7.測試自定義require方法的效果如何
// ...上方代碼
// 使用自定義模塊加載js文件
const obj1 = customRequire('./測試使用文件/測試使用js')
console.log('接收到使用customRequire模塊導入的js文件中導出的內容', obj1)
// 使用自定義模塊加載json文件
const obj2 = customRequire('./測試使用文件/測試使用json')
console.log('接收到使用customRequire模塊導入的json文件中導出的內容', obj2)
// 重復加載相同文件內容,觀察是否從緩存中讀取
const obj3 = customRequire('./測試使用文件/測試使用js')
console.log('接收到使用customRequire模塊導入的js文件中導出的內容', obj3)
5.整體代碼
// 導入所需的模塊
const fs = require('fs')
const path = require('path')
const vm = require('vm')
// 創(chuàng)建一個Module類,用于存儲模塊信息,以及掛載一些輔助方法
class Module {
constructor(id) {
this.id = id
this.exports = {}
}
}
// 在module類的原型鏈上新增load方法,用于加載模塊
Module.prototype.load = function () {
// 獲取當前模塊的后綴名
const extName = path.extname(this.id)
// 根據(jù)后綴名調用對應的加載函數(shù)
Module._extensions[extName](this)
}
// 給module類添加一個靜態(tài)屬性,用于表示模塊的加載函數(shù)以及模塊后綴的加載順序
Module._extensions = {
'.js'(module) {
// 讀取module.id對應的文件內容(這里的id對應的是需要讀取的文件的絕對路徑)
let content = fs.readFileSync(module.id, 'utf8')
// 包裝內容,使其成為一個函數(shù),主要作用就是為了讓加載的模塊中可以使用一些全局變量
content = Module.wrapper[0] + content + Module.wrapper[1]
// 使用VM模塊,將字符串內容變?yōu)橐粋€函數(shù)
const compileFn = vm.runInThisContext(content)
// 定義上方創(chuàng)建的函數(shù)的形參對應的實參
let exports = module.exports // 這里的module.exports就是接收到的形參,Module的實例化對象對exports靜態(tài)屬性
let filename = module.id
let dirname = path.dirname(module.id)
// 調用函數(shù),這里使用call方法,
// 1.將this指向module.exports,call方法的第一個值就是重新指向的this
// 2.此時被加載的模塊中就可以使用module.exports,所以被加載模塊中的變量就可以存儲到module.exports中
// 3.然后將其他的參數(shù)傳遞給函數(shù)
// 4.基于call的特性,會立即執(zhí)行此函數(shù)
compileFn.call(exports, exports, customRequire, module, filename, dirname)
// 返回被改動后的module.exports
return module.exports
},
'.json'(module) {
// 讀取module.id對應的文件內容(這里的id對應的是需要讀取的文件的絕對路徑)
let content = fs.readFileSync(module.id, 'utf8')
// 將讀取到的內容變?yōu)橐粋€對象
content = JSON.parse(content)
// 將讀取到的內容賦值給module.exports
module.exports = content
},
// ...
}
// 定義一個數(shù)組,用于存儲包裝內容
Module.wrapper = [
"(function(exports, require, module, __filename, __dirname) {\n",
"\n});"
]
// 創(chuàng)建輔助方法,用于解析導入模塊的絕對路徑
Module._resolveFilename = (moduleName) => {
// 獲取當前模塊的絕對路徑
let filePath = path.resolve(__dirname, moduleName)
// 獲取當前模塊的后綴名
const extName = path.extname(moduleName)
// 判斷文件是否存在
if (!fs.existsSync(filePath)) {
// 如果文件不存在,則按照順序查找后綴名
const keys = Object.keys(Module._extensions)
// 文件是否存在標識
let flag = false
for (let i = 0; i < keys.length; i++) {
// 獲取當前循環(huán)的后綴名
const currentExtName = keys[i]
// 拼接后綴名
const currentFilePath = filePath + currentExtName
// 判斷拼接后綴名之后的文件路徑是否存在
if (fs.existsSync(currentFilePath)) {
// 如果存在,則將當前文件路徑賦值給filePath
filePath = currentFilePath
// 將文件是否存在標識設置為true
flag = true
// 終止循環(huán)
break
}
}
// 如果不存在,則進行報錯
if (!flag) {
throw new Error(`${moduleName} is not exists`)
}
}
// 返回處理后得到的文件的絕對路徑
return filePath
}
// 定義空對象,用于存儲緩存的模塊
Module._cache = {}
// 創(chuàng)建自定義的require方法
function customRequire(moduleName) {
// 1.路徑分析,解析出模塊的絕對路徑
const absPath = Module._resolveFilename(moduleName)
// 2.緩存優(yōu)先原則,加載文件的時候優(yōu)先從緩存中加載
const cacheModel = Module._cache[absPath]
if (cacheModel) return cacheModel.exports
// 3.創(chuàng)建空對象目標加載模塊(使用絕對路徑作為模塊的id)
let module = new Module(absPath)
// 4.將已加載的模塊緩存起來
Module._cache[absPath] = module
// 5.執(zhí)行加載(編譯執(zhí)行)
module.load()
// 6.返回模塊加載后的結果(注意,不能將id也返回出去)
return module.exports
}
// 使用自定義模塊加載js文件
const obj1 = customRequire('./測試使用文件/測試使用js')
console.log('接收到使用customRequire模塊導入的js文件中導出的內容', obj1)
// 使用自定義模塊加載json文件
const obj2 = customRequire('./測試使用文件/測試使用json')
console.log('接收到使用customRequire模塊導入的json文件中導出的內容', obj2)
// 重復加載相同文件內容,觀察是否從緩存中讀取
const obj3 = customRequire('./測試使用文件/測試使用js')
console.log('接收到使用customRequire模塊導入的js文件中導出的內容', obj3)
6.總結
本篇文章解析了require的大致流程(原require肯定不止這么多代碼與功能的,復雜度要多的多,這里只是大致實現(xiàn)效果),主要用到了fs,path,vm模塊。
以上就是nodejs實現(xiàn)一個自定義的require方法的詳細流程的詳細內容,更多關于nodejs自定義require方法的資料請關注腳本之家其它相關文章!
相關文章
在nodejs中創(chuàng)建child process的方法
這篇文章主要介紹了在nodejs中創(chuàng)建child process的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01

