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

