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.開始創(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中創(chuàng)建child process的方法
這篇文章主要介紹了在nodejs中創(chuàng)建child process的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01sublime text配置node.js調(diào)試(圖文教程)
下面小編就為大家分享一篇sublime text配置node.js調(diào)試(圖文教程),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11深入理解Node.js中CORS的三個(gè)重要響應(yīng)頭
CORS是一種安全機(jī)制,通過(guò)配置適當(dāng)?shù)捻憫?yīng)頭,服務(wù)器可以允許或限制外部域?qū)Y源的訪問(wèn),本文主要介紹了Node.js中CORS的三個(gè)重要響應(yīng)頭,感興趣的可以了解一下2024-12-12