手寫commonJs

介紹

commonJs是前端模塊化規範的其中一種,主要使用在node.js。javascript

每一個文件都是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。html

特色

  1. 全部模塊運行在模塊本身的做用域中,不影響全局做用域;
  2. 模塊能夠被屢次加載,但只有第一次會運行一次,後面再加載使用緩存。
  3. 讀取模塊的路徑能夠不加後綴,自動找到對應後綴模塊。

基本使用

定義一個a.js的文件前端

const fn = function () {
    console.log('Hello');
}
module.exports = fn;
複製代碼

再定義b.js文件,並在其中引入a.jsjava

const fn = require('./b');
fn(); // Hello
複製代碼

b中調用a中的fn方法,打印出‘Hello’。node

手寫commonJs

所需調用的node內置模塊或第三方模塊

  • fs文件系統模塊
  • path路徑解析模塊
  • vm虛擬機

定義一個require方法

const req = function(id) {...} // 取名req與系統默認的require做區分
const fn = req('./a');
複製代碼

定義Module對象

function Module(id) {
    this.id = id;
    this.exports = {};
}
複製代碼

定義不一樣後綴的對應處理方法

commomJs能夠引入.js、.json、.node、.mjs的文件,因爲篇幅有限,這裏只定義了.js和.json的處理方法。json

Module.extensions = {};
Module.extensions['.js'] = function (module) {...};
Module.extensions['.json'] = function (module) {...};
複製代碼

定義方法,給路徑自動加上對應後綴

Module.getPath = function(id) {
    const absPath = path.resolve(id); // 得到絕對路徑
    if (fs.existsSync(absPath)) { // 若輸入的路徑已包含後綴,能夠直接找到
        return absPath;
    }
    const extensions = Object.keys(Module.extensions);
    for (let i = 0;i < extensions.length; i++) {
        const ext = `${absPath}${extensions[i]}`;
        if (fs.existsSync(ext)) {
            return ext;
        }
    }
    throw new Error('The file do not exist'); // 加上後綴還沒找到,拋出錯誤
}
複製代碼

完善req方法

const req = function(id) {
    const ext = Module.getPath(id); // 得到完整路徑
    const myModule = new Module(ext); // 實例化一個模塊
    const extName = path.extname(ext); // 得到文件的後綴
    const result = Module.extensions[extName](myModule); // 執行對應後綴方法
    return result;
}
複製代碼

完善js後綴文件處理方法

核心邏輯在於用fs文件系統讀取到js文件中的內容,而後再將其封裝成方法(爲了保證私有做用域,以及將exports的值賦到咱們本身定義的module中)。api

let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
複製代碼

但此時得到的wrapper依舊是字符串,咱們須要將字符串轉換成能執行的函數方法,此時通常會想到eval。咱們這裏使用更高級的vm,具體使用方法參考vm官方文檔緩存

let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
const fn = vm.runInThisContext(wrapper); // vm.runInThisContext返回封裝的那個方法
複製代碼

完整js處理實現bash

Module.extensions['.js'] = function (module) {
    let script = fs.readFileSync(module.id, 'utf8');
    const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
    const fn = vm.runInThisContext(wrapper);
    fn(module.exports, req, module, __dirname, __filename);
    return module.exports;
};
複製代碼

完善json後綴文件處理方法

json的處理會簡單不少,把文件讀取到後,直接JSON.parse返回對象便可。app

Module.extensions['.json'] = function (module) {
    let jsonContent = fs.readFileSync(module.id, 'utf8');
    return JSON.parse(jsonContent);
};
複製代碼

緩存

每次讀取文件以後,下一次讀取統一文件直接使用緩存便可。定義cache對象:

Module.cache = {};
複製代碼

在req方法內,若是緩存內有數據直接返回緩存數據,而且在拿到新的模塊數據後要將數據計入緩存中。完善後的req方法:

const req = (id) => {
    const ext = Module.getPath(id);
    if (Module.cache[ext]) { // 查詢緩存
        return Module.cache[ext]; // 查詢到緩存,使用緩存
    }
    const myModule = new Module(ext);
    // 對應後綴方法執行
    const result = Module.extensions[path.extname(ext)](myModule);
    Module.cache[ext] = myModule; // 計入緩存
    return result;
};
複製代碼

完整代碼

const path = require('path');
const fs = require('fs');
const vm = require('vm');

// Module處理
function Module(id) {
    this.id = id;
    this.exports = {};
};
// 緩存
Module.cache = {};
// 不一樣後綴類型處理
Module.extensions = {};
Module.extensions['.js'] = function (module) {
    let script = fs.readFileSync(module.id, 'utf8');
    const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
    const fn = vm.runInThisContext(wrapper);
    fn(module.exports, req, module, __dirname, __filename);
    return module.exports;
};
Module.extensions['.json'] = function (module) {
    let jsonContent = fs.readFileSync(module.id, 'utf8');
    return JSON.parse(jsonContent);
};
Module.getPath = function (id) {
    const absPath = path.resolve(id);
    if (fs.existsSync(absPath)) {
        return absPath;
    }
    const extensions = Object.keys(Module.extensions);
    for (let i = 0; i < extensions.length; i++) {
        const extPath = `${absPath}${extensions[i]}`;
        if (fs.existsSync(extPath)) {
            return extPath;
        }
    }
    throw new Error('The file do not exist');
}

const req = (id) => {
    const ext = Module.getPath(id);
    if (Module.cache[ext]) {
        return Module.cache[ext];
    }
    const myModule = new Module(ext);
    // 對應後綴方法執行
    const result = Module.extensions[path.extname(ext)](myModule);
    Module.cache[ext] = myModule;
    return result;
};

//如下是使用req的代碼塊
const func = req('./b');
func(); // Hello
複製代碼
相關文章
相關標籤/搜索