亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

CommonJS 模塊化簡(jiǎn)易實(shí)現(xiàn)

roadtogeek / 1120人閱讀

摘要:依賴模塊操作文件的模塊處理路徑的模塊虛擬機(jī),幫我們創(chuàng)建一個(gè)黑箱執(zhí)行代碼,防止變量污染創(chuàng)建構(gòu)造函數(shù)其實(shí)中引入的每一個(gè)模塊我們都需要通過(guò)構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例。


閱讀原文


CommonJS 概述

CommonJS 是一種模塊化的標(biāo)準(zhǔn),而 NodeJS 是這種標(biāo)準(zhǔn)的實(shí)現(xiàn),每個(gè)文件就是一個(gè)模塊,有自己的作用域。在一個(gè)文件里面定義的變量、函數(shù)、類,都是私有的,對(duì)其他文件不可見(jiàn)。


NodeJS 模塊化的簡(jiǎn)易實(shí)現(xiàn)

在實(shí)現(xiàn)模塊加載之前,我們需要清除模塊的加載過(guò)程:

假設(shè) A 文件夾下有一個(gè) a.js,我們要解析出一個(gè)絕對(duì)路徑來(lái);

我們寫(xiě)的路徑可能沒(méi)有后綴名 .js.json;

得到一個(gè)真實(shí)的加載路徑(模塊會(huì)被緩存)先去緩存中看一下這個(gè)文件是否存在,如果存在返回緩存 沒(méi)有則創(chuàng)建一個(gè)模塊;

得到對(duì)應(yīng)文件的內(nèi)容,加一個(gè)閉包,把內(nèi)容塞進(jìn)去,之后執(zhí)行即可。

1、提前加載需要用到的模塊

因?yàn)槲覀冎皇菍?shí)現(xiàn) CommonJS 的模塊加載方法,并不會(huì)去實(shí)現(xiàn)整個(gè) Node,在這里我們需要依賴一些 Node 的模塊,所以我們就 “不要臉” 的使用 Node 自帶的 require 方法把模塊加載進(jìn)來(lái)。

// 依賴模塊
// 操作文件的模塊
const fs = require("fs");

// 處理路徑的模塊
const path = require("path");

// 虛擬機(jī),幫我們創(chuàng)建一個(gè)黑箱執(zhí)行代碼,防止變量污染
const vm = require("vm");
2、創(chuàng)建 Module 構(gòu)造函數(shù)

其實(shí) CommonJS 中引入的每一個(gè)模塊我們都需要通過(guò) Module 構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例。

// 創(chuàng)建 Module 構(gòu)造函數(shù)
/*
* @param {String} p
*/
function Module(p) {
    this.id = p; // 當(dāng)前文件的表示(絕對(duì)路徑)
    this.exports = {}; // 每個(gè)模塊都有一個(gè) exports 屬性,用來(lái)存儲(chǔ)模塊的內(nèi)容
    this.loaded = false; // 標(biāo)記是否被加載過(guò)
}
3、定義靜態(tài)屬性存儲(chǔ)我們需要使用的一些值
// Module 靜態(tài)變量
// 函數(shù)后面需要使用的閉包的字符串
Module.wrapper = [
    "(function (exports, require, module, __dirname, __filename) {",
    "
})"
];

// 根據(jù)絕對(duì)路徑進(jìn)行緩存的模塊的對(duì)象
Module._cacheModule = {};

// 處理不同文件后綴名的方法
Module._extensions = {
    ".js": function() {},
    ".json": function() {}
};
4、創(chuàng)建引入模塊的 req 方法

為了防止和 Node 自帶的 require 方法重名,我們將模擬的方法重命名為 req。

// 引入模塊方法 req
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // 將 req 傳入的參數(shù)處理成絕對(duì)路徑
    let p = Module._resolveFileName(moduleId);

    // 生成一個(gè)新的模塊
    let module = new Module(p);
}

在上面代碼中,我們先把傳入的參數(shù)通過(guò) Module._resolveFileName 處理成了一個(gè)絕對(duì)路徑,并創(chuàng)建模塊實(shí)例把絕對(duì)路徑作為參數(shù)傳入,我們現(xiàn)在實(shí)現(xiàn)一下 Module._resolveFileName 方法。

5、返回文件絕對(duì)路徑 Module._resolveFileName 方法的實(shí)現(xiàn)

這個(gè)方法的功能就是將 req 方法的參數(shù)根據(jù)是否有后綴名兩種方式處理成帶后綴名的文件絕對(duì)路徑,如果 req 的參數(shù)沒(méi)有后綴名,會(huì)去按照 Module._extensions 的鍵的后綴名順序進(jìn)行查找文件,直到找到后綴名對(duì)應(yīng)文件的絕對(duì)路徑,優(yōu)先 .js,然后是 .json,這里我們只實(shí)現(xiàn)這兩種文件類型的處理。

// 處理絕對(duì)路徑 _resolveFileName 方法
/*
* @param {String} moduleId
*/
Module._resolveFileName = function(moduleId) {
    // 將參數(shù)拼接成絕對(duì)路徑
    let p = path.resolve(moduleId);

    // 判斷是否含有后綴名
    if (!/.w+$/.test(p)) {
        // 創(chuàng)建規(guī)范規(guī)定查找文件后綴名順序的數(shù)組 .js .json
        let arr = Object.keys(Module._extensions);

        // 循環(huán)查找
        for (let i = 0; i < arr.length; i++) {
            // 將絕對(duì)路徑與后綴名進(jìn)行拼接
            let file = p + arr[i];
            // 查找不到文件時(shí)捕獲異常
            try {
                // 并通過(guò) fs 模塊同步查找文件的方法對(duì)改路徑進(jìn)行查找,文件未找到會(huì)直接進(jìn)入 catch 語(yǔ)句
                fs.accessSync(file);

                // 如果找到文件將該文件絕對(duì)路徑返回
                return file;
            } catch (e) {
                // 當(dāng)后綴名循環(huán)完畢都沒(méi)有找到對(duì)應(yīng)文件時(shí),拋出異常
                if (i >= arr.length) throw new Error("not found module");
            }
        }
    } else {
        // 有后綴名直接返回該絕對(duì)路徑
        return p;
    }
};
6、加載模塊的 load 方法
// 完善 req 方法
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // 將 req 傳入的參數(shù)處理成絕對(duì)路徑
    let p = Module._resolveFileName(moduleId);

    // 生成一個(gè)新的模塊
    let module = new Module(p);

    // ********** 下面為新增代碼 **********
    // 加載模塊
    let content = module.load(p);

    // 將加載后返回的內(nèi)容賦值給模塊實(shí)例的 exports 屬性上
    module.exports = content;

    // 最后返回 模塊實(shí)例的 exports 屬性,即加載模塊的內(nèi)容
    return module.exports;
    // ********** 上面為新增代碼 **********
}

上面代碼實(shí)現(xiàn)了一個(gè)實(shí)例方法 load,傳入文件的絕對(duì)路徑,為模塊加載文件的內(nèi)容,在加載后將值存入模塊實(shí)例的 exports 屬性上最后返回,其實(shí) req 函數(shù)返回的就是模塊加載回來(lái)的內(nèi)容。

// load 方法
// 模塊加載的方法
Module.prototype.load = function(filepath) {
    // 判斷加載的文件是什么后綴名
    let ext = path.extname(filepath);

    // 根據(jù)不同的后綴名處理文件內(nèi)容,參數(shù)是當(dāng)前實(shí)例
    let content = Moudule._extensions[ext](this);

    // 將處理后的結(jié)果返回
    return content;
};
7、實(shí)現(xiàn)加載 .js 文件和 .json 文件的方法

還記得前面準(zhǔn)備的靜態(tài)屬性中有 Module._extensions 就是用來(lái)存儲(chǔ)這兩個(gè)方法的,下面我們來(lái)完善這兩個(gè)方法。

// 處理后綴名方法的 _extensions 對(duì)象
Module._extensions = {
    ".js": function(module) {
        // 讀取 js 文件,返回文件的內(nèi)容
        let script = fs.readFileSync(module.id, "utf8");

        // 給 js 文件的內(nèi)容增加一個(gè)閉包環(huán)境
        let fn = Module.wrap(script);

        // 創(chuàng)建虛擬機(jī),將我們創(chuàng)建的 js 函數(shù)執(zhí)行,將 this 指向模塊實(shí)例的 exports 屬性
        vm.runInThisContext(fn).call(
            module.exports,
            module.exports,
            req,
            module
        );

        // 返回模塊實(shí)例上的 exports 屬性(即模塊的內(nèi)容)
        return module.exports;
    },
    ".json": function(module) {
        // .json 文件的處理相對(duì)簡(jiǎn)單,將讀出的字符串轉(zhuǎn)換成對(duì)象即可
        return JSON.parse(fs.readFileSync(module.id, "utf8"));
    }
};

我們這里使用了 Module.wrap 方法,代碼如下,其實(shí)幫助我們加了一個(gè)閉包環(huán)境(即套了一層函數(shù)并傳入了我們需要的參數(shù)),里面所有的變量都是私有的。

// 創(chuàng)建閉包 wrap 方法
Module.wrap = function(content) {
    return Module.wrapper[0] + content + Module.wrapper[1];
};

Module.wrapper 的兩個(gè)值其實(shí)就是我們需要在外層包了一個(gè)函數(shù)的前半段和后半段。

這里我們要?jiǎng)澲攸c(diǎn)了,非常重要:
1、我們?cè)谔摂M機(jī)中執(zhí)行構(gòu)建的閉包函數(shù)時(shí)利用執(zhí)行上/下文 callthis 指向了模塊實(shí)例的 exports 屬性上,所以這也是為什么我們用 Node 啟動(dòng)一個(gè) js 文件,打印 this 時(shí),不是全局對(duì)象 global,而是一個(gè)空對(duì)象,這個(gè)空對(duì)象就是我們的 module.exports,即當(dāng)前模塊實(shí)例的 exports 屬性。
2、還是第一條的函數(shù)執(zhí)行,我們傳入的第一個(gè)參數(shù)是改變 this 指向,那第二個(gè)參數(shù)是 module.exports,所以在每個(gè)模塊導(dǎo)出的時(shí)候,使用 module.exports = xxx,其實(shí)直接替換了模塊實(shí)例的值,即直接把模塊的內(nèi)容存放在了模塊實(shí)例的 exports 屬性上,而 req 最后返回的就是我們模塊導(dǎo)出的內(nèi)容。
3、第三個(gè)參數(shù)之所以傳入 req 是因?yàn)槲覀冞€可能在一個(gè)模塊中導(dǎo)入其他模塊,而 req 會(huì)返回其他模塊的導(dǎo)出在當(dāng)前模塊使用,這樣整個(gè) CommonJS 的規(guī)則就這樣建立起來(lái)了。

8、對(duì)加載過(guò)的模塊進(jìn)行緩存

我們現(xiàn)在的程序是有問(wèn)題的,當(dāng)重復(fù)加載了一個(gè)已經(jīng)加載過(guò)得模塊,當(dāng)執(zhí)行 req 方法的時(shí)候會(huì)發(fā)現(xiàn),又創(chuàng)建了一個(gè)新的模塊實(shí)例,這是不合理的,所以我們下面來(lái)實(shí)現(xiàn)一下緩存機(jī)制。

還記得之前的一個(gè)靜態(tài)屬性 Module._cacheModule,它的值是一個(gè)空對(duì)象,我們會(huì)把所有加載過(guò)的模塊的實(shí)例存儲(chǔ)到這個(gè)對(duì)象上。

// 完善 req 方法(處理緩存)
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // 將 req 傳入的參數(shù)處理成絕對(duì)路徑
    let p = Module._resolveFileName(moduleId);

    // ********** 下面為新增代碼 **********
    // 判斷是否已經(jīng)加載過(guò)
    if (Module._cacheModule[p]) {
        // 模塊存在,如果有直接把 exports 對(duì)象返回即可
        return Module._cacheModule[p].exprots;
    }
    // ********** 上面為新增代碼 **********

    // 生成一個(gè)新的模塊
    let module = new Module(p);

    // 加載模塊
    let content = module.load(p);

    // ********** 下面為新增代碼 **********
    // 存儲(chǔ)時(shí)是拿模塊的絕對(duì)路徑作為鍵與模塊內(nèi)容相對(duì)應(yīng)的
    Module._cacheModule[p] = module;

    // 是否緩存表示改為 true
    module.loaded = true;
    // ********** 上面為新增代碼 **********

    // 將加載后返回的內(nèi)容賦值給模塊實(shí)例的 exports 屬性上
    module.exports = content;

    // 最后返回 模塊實(shí)例的 exports 屬性,即加載模塊的內(nèi)容
    return module.exports;
}
9、試用 req 加載模塊

在同級(jí)目錄下新建一個(gè)文件 a.js,使用 module.exports 隨便導(dǎo)出一些內(nèi)容,在我們實(shí)現(xiàn)模塊加載的最下方嘗試引入并打印內(nèi)容。

// 導(dǎo)出自定義模塊
// a.js
module.exports = "Hello world";
// 檢測(cè) req 方法
const a = req("./a");
console.log(a); // Hello world


CommonJS 模塊查找規(guī)范

其實(shí)我們只實(shí)現(xiàn)了 CommonJS 規(guī)范的一部分,即自定義模塊的加載,其實(shí)在 CommonJS 的規(guī)范當(dāng)中關(guān)于模塊查找的規(guī)則還有很多,具體的我們就用下面的流程圖來(lái)表示。

這篇文章讓我們了解了 CommonJS 是什么,主要目的在于理解 Node 模塊化的實(shí)現(xiàn)思路,想要更深入的了解 CommonJS 的實(shí)現(xiàn)細(xì)節(jié),建議看一看 NodeJS 源碼對(duì)應(yīng)的部分,如果覺(jué)得源碼比較多,不容易找到模塊化實(shí)現(xiàn)的代碼,也可以在 VSCode 中通過(guò)調(diào)用 require 方法引入模塊時(shí),打斷點(diǎn)調(diào)試,一步一步的跟進(jìn)到 Node 源碼中查看。


文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98306.html

相關(guān)文章

  • 關(guān)于JavaScript模塊規(guī)范之CommonJSAMDCMD

    摘要:所有依賴這個(gè)模塊的語(yǔ)句,都定義在一個(gè)回調(diào)函數(shù)中,等到加載完成之后,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行。也采用語(yǔ)句加載模塊,但是不同于,它要求兩個(gè)參數(shù)第一個(gè)參數(shù),是一個(gè)數(shù)組,里面的成員就是要加載的模塊第二個(gè)參數(shù),則是加載成功之后的回調(diào)函數(shù)。 本篇文章來(lái)自對(duì)文章《js模塊化編程之徹底弄懂CommonJS和AMD/CMD!》的總結(jié),大部分摘自文章原話,本人只是為了學(xué)習(xí)方便做的筆記,之后有新的體會(huì)會(huì)及時(shí)補(bǔ)充...

    binaryTree 評(píng)論0 收藏0
  • js 簡(jiǎn)易模塊加載器 示例分析

    摘要:簡(jiǎn)易模塊加載器示例點(diǎn)來(lái)了接下來(lái)我們先來(lái)看一段建議模塊加載器的示例代碼以上是加載器的實(shí)現(xiàn),再來(lái)看看如何使用吧你好啊要去杭州玩了章煒今天天氣不錯(cuò)噢在以上代碼中,我們定義了三個(gè)模塊,分別名為。 前端模塊化 關(guān)注前端技術(shù)發(fā)展的各位親們,肯定對(duì)模塊化開(kāi)發(fā)這個(gè)名詞不陌生。隨著前端工程越來(lái)越復(fù)雜,代碼越來(lái)越多,模塊化成了必不可免的趨勢(shì)。 各種標(biāo)準(zhǔn) 由于javascript本身并沒(méi)有制定相關(guān)標(biāo)準(zhǔn)(當(dāng)然...

    miqt 評(píng)論0 收藏0
  • 通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)易打包工具,分析打包的核心原理

    摘要:而在編譯過(guò)程中通過(guò)語(yǔ)法和詞法的分析得出一顆語(yǔ)法樹(shù),我們可以將它稱為抽象語(yǔ)法樹(shù)也稱為語(yǔ)法樹(shù),指的是源代碼語(yǔ)法所對(duì)應(yīng)的樹(shù)狀結(jié)構(gòu)。而這個(gè)卻恰恰使我們分析打包工具的重點(diǎn)核心。 概述 眼下wepack似乎已經(jīng)成了前端開(kāi)發(fā)中不可缺少的工具之一,而他的一切皆模塊的思想隨著webpack版本不斷的迭代(webpack 4)使其打包速度更快,效率更高的為我們的前端工程化服務(wù)showImg(https:/...

    red_bricks 評(píng)論0 收藏0
  • 實(shí)現(xiàn)一個(gè)簡(jiǎn)易的webpack

    摘要:首先一段代碼轉(zhuǎn)化成的抽象語(yǔ)法樹(shù)是一個(gè)對(duì)象,該對(duì)象會(huì)有一個(gè)頂級(jí)的屬性第二個(gè)屬性是是一個(gè)數(shù)組。最終完成整個(gè)文件依賴的處理。參考文章抽象語(yǔ)法樹(shù)一看就懂的抽象語(yǔ)法樹(shù)源碼所有的源碼已經(jīng)上傳 背景 隨著前端復(fù)雜度的不斷提升,誕生出很多打包工具,比如最先的grunt,gulp。到后來(lái)的webpack和 Parcel。但是目前很多腳手架工具,比如vue-cli已經(jīng)幫我們集成了一些構(gòu)建工具的使用。有的時(shí)...

    darcrand 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<