激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - node.js - require加載器實現原理的深入理解

require加載器實現原理的深入理解

2022-03-03 16:38隱冬 node.js

這篇文章主要給大家介紹了關于require加載器實現原理的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

我們常說node并不是一門新的編程語言,他只是javascript的運行時,運行時你可以簡單地理解為運行javascript的環境。在大多數情況下我們會在瀏覽器中去運行javascript,有了node的出現,我們可以在node中去運行javascript,這意味著哪里安裝了node或者瀏覽器,我們就可以在哪里運行javascript。

1.node模塊化的實現

node中是自帶模塊化機制的,每個文件就是一個單獨的模塊,并且它遵循的是CommonJS規范,也就是使用require的方式導入模塊,通過module.export的方式導出模塊。
node模塊的運行機制也很簡單,其實就是在每一個模塊外層包裹了一層函數,有了函數的包裹就可以實現代碼間的作用域隔離。

你可能會說,我在寫代碼的時候并沒有包裹函數呀,是的的確如此,這一層函數是node自動幫我們實現的,我們可以來測試一下。

我們新建一個js文件,在第一行打印一個并不存在的變量,比如我們這里打印window,在node中是沒有window的。

?
1
console.log(window);

通過node執行該文件,會發現報錯信息如下。(請使用系統默認cmd執行命令)。

?
1
2
3
4
5
6
7
8
9
10
11
(function (exports, require, module, __filename, __dirname) { console.log(window);
ReferenceError: window is not defined
    at Object.<anonymous> (/Users/choice/Desktop/node/main.js:1:75)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)

可以看到報錯的頂層有一個自執行的函數,, 函數中包含exports, require, module, __filename, __dirname這些我們常用的全局變量。

我在之前的《前端模塊化發展歷程》一文中介紹過。自執行函數也是前端模塊化的實現方案之一,在早期前端沒有模塊化系統的時代,自執行函數可以很好的解決命名空間的問題,并且模塊依賴的其他模塊都可以通過參數傳遞進來。cmd和amd規范也都是依賴自執行函數實現的。

在模塊系統中,每個文件就是一個模塊,每個模塊外面會自動套一個函數,并且定義了導出方式 module.exports或者exports,同時也定義了導入方式require。

?
1
2
3
4
let moduleA = (function() {
    module.exports = Promise;
    return module.exports;
})();

2.require加載模塊

require依賴node中的fs模塊來加載模塊文件,fs.readFile讀取到的是一個字符串。

在javascrpt中我們可以通過eval或者new Function的方式來將一個字符串轉換成js代碼來運行。

eval

?
1
2
3
const name = 'yd';
const str = 'const a = 123; console.log(name)';
eval(str); // yd;

new Function

new Function接收的是一個要執行的字符串,返回的是一個新的函數,調用這個新的函數字符串就會執行了。如果這個函數需要傳遞參數,可以在new Function的時候依次傳入參數,最后傳入的是要執行的字符串。比如這里傳入參數b,要執行的字符串str。

?
1
2
3
4
const b = 3;
const str = 'let a = 1; return a + b';
const fun = new Function('b', str);
console.log(fun(b, str)); // 4

可以看到eval和Function實例化都可以用來執行javascript字符串,似乎他們都可以來實現require模塊加載。不過在node中并沒有選用他們來實現模塊化,原因也很簡單因為他們都有一個致命的問題,就是都容易被不屬于他們的變量所影響。

如下str字符串中并沒有定義a,但是確可以使用上面定義的a變量,這顯然是不對的,在模塊化機制中,str字符串應該具有自身獨立的運行空間,自身不存在的變量是不可以直接使用的。

?
1
2
3
4
5
6
7
8
const a = 1;
 
const str = 'console.log(a)';
 
eval(str);
 
const func = new Function(str);
func();

node存在一個vm虛擬環境的概念,用來運行額外的js文件,他可以保證javascript執行的獨立性,不會被外部所影響。

vm 內置模塊

雖然我們在外部定義了hello,但是str是一個獨立的模塊,并不在村hello變量,所以會直接報錯。

?
1
2
3
4
5
// 引入vm模塊, 不需要安裝,node 自建模塊
const vm = require('vm');
const hello = 'yd';
const str = 'console.log(hello)';
wm.runInThisContext(str); // 報錯

所以node執行javascript模塊時可以采用vm來實現。就可以保證模塊的獨立性了。

3.require代碼實現

介紹require代碼實現之前先來回顧兩個node模塊的用法,因為下面會用得到。

path模塊

用于處理文件路徑。

basename: 基礎路徑, 有文件路徑就不是基礎路徑,基礎路勁是1.js

extname: 獲取擴展名

dirname: 父級路勁

join: 拼接路徑

resolve: 當前文件夾的絕對路徑,注意使用的時候不要在結尾添加/

__dirname: 當前文件所在文件夾的路徑

__filename: 當前文件的絕對路徑

?
1
2
3
4
5
6
const path = require('path', 's');
console.log(path.basename('1.js'));
console.log(path.extname('2.txt'));
console.log(path.dirname('2.txt'));
console.log(path.join('a/b/c', 'd/e/f')); // a/b/c/d/e/
console.log(path.resolve('2.txt'));

fs模塊

用于操作文件或者文件夾,比如文件的讀寫,新增,刪除等。常用方法有readFile和readFileSync,分別是異步讀取文件和同步讀取文件。

?
1
2
3
const fs = require('fs');
const buffer = fs.readFileSync('./name.txt', 'utf8'); // 如果不傳入編碼,出來的是二進制
console.log(buffer);

fs.access: 判斷是否存在,node10提供的,exists方法已經被廢棄, 原因是不符合node規范,所以我們采用access來判斷文件是否存在。

?
1
2
3
4
5
try {
    fs.accessSync('./name.txt');
} catch(e) {
    // 文件不存在
}

4.手動實現require模塊加載器

首先導入依賴的模塊path,fs, vm, 并且創建一個Require函數,這個函數接收一個modulePath參數,表示要導入的文件路徑。

?
1
2
3
4
5
6
7
8
9
// 導入依賴
const path = require('path'); // 路徑操作
const fs = require('fs'); // 文件讀取
const vm = require('vm'); // 文件執行
 
// 定義導入類,參數為模塊路徑
function Require(modulePath) {
    ...
}

在Require中獲取到模塊的絕對路徑,方便使用fs加載模塊,這里讀取模塊內容我們使用new Module來抽象,使用tryModuleLoad來加載模塊內容,Module和tryModuleLoad我們稍后實現,Require的返回值應該是模塊的內容,也就是module.exports。

?
1
2
3
4
5
6
7
8
9
10
11
// 定義導入類,參數為模塊路徑
function Require(modulePath) {
    // 獲取當前要加載的絕對路徑
    let absPathname = path.resolve(__dirname, modulePath);
    // 創建模塊,新建Module實例
    const module = new Module(absPathname);
    // 加載當前模塊
    tryModuleLoad(module);
    // 返回exports對象
    return module.exports;
}

Module的實現很簡單,就是給模塊創建一個exports對象,tryModuleLoad執行的時候將內容加入到exports中,id就是模塊的絕對路徑。

?
1
2
3
4
5
6
// 定義模塊, 添加文件id標識和exports屬性
function Module(id) {
    this.id = id;
    // 讀取到的文件內容會放在exports中
    this.exports = {};
}

之前我們說過node模塊是運行在一個函數中,這里我們給Module掛載靜態屬性wrapper,里面定義一下這個函數的字符串,wrapper是一個數組,數組的第一個元素就是函數的參數部分,其中有exports,module. Require,__dirname, __filename, 都是我們模塊中常用的全局變量。注意這里傳入的Require參數是我們自己定義的Require。

第二個參數就是函數的結束部分。兩部分都是字符串,使用的時候我們將他們包裹在模塊的字符串外部就可以了。

?
1
2
3
4
Module.wrapper = [
    "(function(exports, module, Require, __dirname, __filename) {",
    "})"
]

_extensions用于針對不同的模塊擴展名使用不同的加載方式,比如JSON和javascript加載方式肯定是不同的。JSON使用JSON.parse來運行。

javascript使用vm.runInThisContext來運行,可以看到fs.readFileSync傳入的是module.id也就是我們Module定義時候id存儲的是模塊的絕對路徑,讀取到的content是一個字符串,我們使用Module.wrapper來包裹一下就相當于在這個模塊外部又包裹了一個函數,也就實現了私有作用域。

使用call來執行fn函數,第一個參數改變運行的this我們傳入module.exports,后面的參數就是函數外面包裹參數exports, module, Require, __dirname, __filename

?
1
2
3
4
5
6
7
8
9
10
11
12
Module._extensions = {
    '.js'(module) {
        const content = fs.readFileSync(module.id, 'utf8');
        const fnStr = Module.wrapper[0] + content + Module.wrapper[1];
        const fn = vm.runInThisContext(fnStr);
        fn.call(module.exports, module.exports, module, Require,_filename,_dirname);
    },
    '.json'(module) {
        const json = fs.readFileSync(module.id, 'utf8');
        module.exports = JSON.parse(json); // 把文件的結果放在exports屬性上
    }
}

tryModuleLoad函數接收的是模塊對象,通過path.extname來獲取模塊的后綴名,然后使用Module._extensions來加載模塊。

?
1
2
3
4
5
6
7
// 定義模塊加載方法
function tryModuleLoad(module) {
    // 獲取擴展名
    const extension = path.extname(module.id);
    // 通過后綴加載當前模塊
    Module._extensions[extension](module);
}

至此Require加載機制我們基本就寫完了,我們來重新看一下。Require加載模塊的時候傳入模塊名稱,在Require方法中使用path.resolve(__dirname, modulePath)獲取到文件的絕對路徑。然后通過new Module實例化的方式創建module對象,將模塊的絕對路徑存儲在module的id屬性中,在module中創建exports屬性為一個json對象。

使用tryModuleLoad方法去加載模塊,tryModuleLoad中使用path.extname獲取到文件的擴展名,然后根據擴展名來執行對應的模塊加載機制。

最終將加載到的模塊掛載module.exports中。tryModuleLoad執行完畢之后module.exports已經存在了,直接返回就可以了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 導入依賴
const path = require('path'); // 路徑操作
const fs = require('fs'); // 文件讀取
const vm = require('vm'); // 文件執行
 
// 定義導入類,參數為模塊路徑
function Require(modulePath) {
    // 獲取當前要加載的絕對路徑
    let absPathname = path.resolve(__dirname, modulePath);
    // 創建模塊,新建Module實例
    const module = new Module(absPathname);
    // 加載當前模塊
    tryModuleLoad(module);
    // 返回exports對象
    return module.exports;
}
// 定義模塊, 添加文件id標識和exports屬性
function Module(id) {
    this.id = id;
    // 讀取到的文件內容會放在exports中
    this.exports = {};
}
// 定義包裹模塊內容的函數
Module.wrapper = [
    "(function(exports, module, Require, __dirname, __filename) {",
    "})"
]
// 定義擴展名,不同的擴展名,加載方式不同,實現js和json
Module._extensions = {
    '.js'(module) {
        const content = fs.readFileSync(module.id, 'utf8');
        const fnStr = Module.wrapper[0] + content + Module.wrapper[1];
        const fn = vm.runInThisContext(fnStr);
        fn.call(module.exports, module.exports, module, Require,_filename,_dirname);
    },
    '.json'(module) {
        const json = fs.readFileSync(module.id, 'utf8');
        module.exports = JSON.parse(json); // 把文件的結果放在exports屬性上
    }
}
// 定義模塊加載方法
function tryModuleLoad(module) {
    // 獲取擴展名
    const extension = path.extname(module.id);
    // 通過后綴加載當前模塊
    Module._extensions[extension](module);
}

5.給模塊添加緩存

添加緩存也比較簡單,就是文件加載的時候將文件放入緩存在,再去加載模塊時先看緩存中是否存在,如果存在直接使用,如果不存在再去重新嘉愛,加載之后再放入緩存。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定義導入類,參數為模塊路徑
function Require(modulePath) {
    // 獲取當前要加載的絕對路徑
    let absPathname = path.resolve(__dirname, modulePath);
    // 從緩存中讀取,如果存在,直接返回結果
    if (Module._cache[absPathname]) {
        return Module._cache[absPathname].exports;
    }
    // 嘗試加載當前模塊
    tryModuleLoad(module);
    // 創建模塊,新建Module實例
    const module = new Module(absPathname);
    // 添加緩存
    Module._cache[absPathname] = module;
    // 加載當前模塊
    tryModuleLoad(module);
    // 返回exports對象
    return module.exports;
}

6.自動補全路徑

自動給模塊添加后綴名,實現省略后綴名加載模塊,其實也就是如果文件沒有后綴名的時候遍歷一下所有的后綴名看一下文件是否存在。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定義導入類,參數為模塊路徑
function Require(modulePath) {
    // 獲取當前要加載的絕對路徑
    let absPathname = path.resolve(__dirname, modulePath);
    // 獲取所有后綴名
    const extNames = Object.keys(Module._extensions);
    let index = 0;
    // 存儲原始文件路徑
    const oldPath = absPathname;
    function findExt(absPathname) {
        if (index === extNames.length) {
           return throw new Error('文件不存在');
        }
        try {
            fs.accessSync(absPathname);
            return absPathname;
        } catch(e) {
            const ext = extNames[index++];
            findExt(oldPath + ext);
        }
    }
    // 遞歸追加后綴名,判斷文件是否存在
    absPathname = findExt(absPathname);
    // 從緩存中讀取,如果存在,直接返回結果
    if (Module._cache[absPathname]) {
        return Module._cache[absPathname].exports;
    }
    // 嘗試加載當前模塊
    tryModuleLoad(module);
    // 創建模塊,新建Module實例
    const module = new Module(absPathname);
    // 添加緩存
    Module._cache[absPathname] = module;
    // 加載當前模塊
    tryModuleLoad(module);
    // 返回exports對象
    return module.exports;
}

7.分析實現步驟

1.導入相關模塊,創建一個Require方法。

2.抽離通過Module._load方法,用于加載模塊。

3.Module.resolveFilename 根據相對路徑,轉換成絕對路徑。

4.緩存模塊 Module._cache,同一個模塊不要重復加載,提升性能。

5.創建模塊 id: 保存的內容是 exports = {}相當于this。

6.利用tryModuleLoad(module, filename) 嘗試加載模塊。

7.Module._extensions使用讀取文件。

8.Module.wrap: 把讀取到的js包裹一個函數。

9.將拿到的字符串使用runInThisContext運行字符串。

10.讓字符串執行并將this改編成exports。

總結

到此這篇關于require加載器實現原理的文章就介紹到這了,更多相關require加載器原理內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://juejin.cn/post/6949385808755294245

延伸 · 閱讀

精彩推薦
  • node.jsNode.js 中如何收集和解析命令行參數

    Node.js 中如何收集和解析命令行參數

    這篇文章主要介紹了Node.js 中如何收集和解析命令行參數,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋...

    descire8802021-12-28
  • node.js在瀏覽器中,把 Vite 跑起來了!

    在瀏覽器中,把 Vite 跑起來了!

    大家好,我是 ssh,前幾天在推上沖浪的時候,看到 Francois Valdy 宣布他制作了 browser-vite[1],成功把 Vite 成功在瀏覽器中運行起來了。這引起了我的興趣,如...

    前端從進階到入院9282022-01-11
  • node.jsNode.js ObjectWrap 的弱引用問題

    Node.js ObjectWrap 的弱引用問題

    最近在寫 Node.js Addon 的過程中,遇到了一個問題,然后發現是 ObjectWrap 弱引用導致的,本文介紹一下具體的問題和排查過程,以及 ObjectWrap 的使用問題。...

    編程雜技9852022-01-04
  • node.jsnodejs中使用worker_threads來創建新的線程的方法

    nodejs中使用worker_threads來創建新的線程的方法

    這篇文章主要介紹了nodejs中使用worker_threads來創建新的線程的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友...

    flydean程序那些事8982022-01-06
  • node.js詳解node.js創建一個web服務器(Server)的詳細步驟

    詳解node.js創建一個web服務器(Server)的詳細步驟

    這篇文章主要介紹了詳解node.js創建一個web服務器(Server)的詳細步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    王佳斌8952021-12-31
  • node.jsrequire加載器實現原理的深入理解

    require加載器實現原理的深入理解

    這篇文章主要給大家介紹了關于require加載器實現原理的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需...

    隱冬8462022-03-03
  • node.jslinux服務器快速卸載安裝node環境(簡單上手)

    linux服務器快速卸載安裝node環境(簡單上手)

    這篇文章主要介紹了linux服務器快速卸載安裝node環境(簡單上手),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需...

    mose-x8462022-01-22
  • node.jsk8s node節點重新加入master集群的實現

    k8s node節點重新加入master集群的實現

    這篇文章主要介紹了k8s node節點重新加入master集群的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋...

    Scarborought13922022-01-22
主站蜘蛛池模板: 大学生一级毛片在线视频 | 亚洲影视中文字幕 | 成人免费一区二区 | 毛片小网站 | 国产系列 视频二区 | 99精品视频在线免费观看 | 好吊一区二区三区 | 国产一区二区视频观看 | 成人在线观看免费高清 | 国产成视频在线观看 | 久久伊人国产精品 | 色播视频网站 | 成人毛毛片 | 久久免费视频3 | 久久精品日产高清版的功能介绍 | 一级大片一级一大片 | 黑人一区 | 亚洲一级片免费观看 | 欧美三级日本三级少妇99 | 免费国产一级淫片 | 久久狠狠高潮亚洲精品 | 蜜桃视频观看麻豆 | 成人福利在线看 | 亚洲网站在线播放 | 一级黄色在线观看 | 美女av在线免费观看 | 久草在线网址 | av大全在线免费观看 | 国产精品一品二区三区四区18 | 久国久产久精永久网页 | 国产精品视频一区二区三区四 | 国产亚洲精品久久久久久久久久 | 龙床上的呻吟高h | 成人做爰www免费看 成人午夜视频免费看 | 国产精品一区二区三区在线播放 | 免费黄色大片网站 | 欧美成人一区二区三区电影 | 色污视频在线观看 | 极品国产91在线网站 | 99久久久国产精品 | 毛片在线免费视频 |