nodejs入門之模塊

  • nodejs模塊語法與開閉原則
  • nodejs模塊的底層實現

 1、nodejs模塊語法與開閉原則

關於nodejs模塊我在以前的兩篇博客中都有涉及,但都沒有對nodejs模塊的底層作作任何探討,可是爲了使相關內容跟方便查看比對理解,這裏仍是先引入一下以前兩篇博客的鏈接:html

js模塊化入門與commonjs解析與應用node

ES6入門十二:Module(模塊化)模塊化

1.1 exports、module.exports、require()實現模塊導出導入:函數

 1 //示例一:導出原始值數據
 2 //a.js--用於導出數據
 3 let a = 123;
 4 module.exports.a=a;
 5 //inde.js--用於導入a模塊的數據
 6 let aModule = require('./a.js');
 7 console.log(aModule.a); //123
 8 
 9 //示例二:導出引用值數據
10 //a.js--同上
11 function foo(val){ 
12     console.log(val);
13 }
14 module.exports.foo = foo;
15 //index.js--同上
16 let aModule = require('./a.js');
17 let str = "this is 'index' module"
18 aModule.foo(str); //this is 'index' module
19 
20 //示例三:導出混合數據
21 a.js--同上
22 let a = 123;
23 function foo(val){ 
24     console.log(val);
25 }
26 module.exports = {
27     a:a,
28     foo:foo
29 }
30 //inde.js--同上
31 let aModule = require('./a.js');
32 let str = "this is 'index' module"
33 console.log(aModule.a);//123
34 aModule.foo(str); //this is 'index' module

在上面這些示例中,沒有演示exports的導出,暫時能夠把它看做與同等於module.exports,例如:測試

 1 //a.js -- 導出模塊
 2 let a = 123;
 3 function foo(val){ 
 4     console.log(val);
 5 }
 6 exports.a = a;
 7 exports.foo = foo;
 8 
 9 //inde.js -- 引用模塊a
10 let aModule = require('./a.js');
11 let str = "this is 'index' module"
12 console.log(aModule.a);//123
13 aModule.foo(str); //this is 'index' module

可是使用exports導出模塊不能這麼寫:ui

 1 //a.js
 2 let a = 123;
 3 function foo(val){ 
 4     console.log(val);
 5 }
 6 exports = {
 7     a:a,
 8     foo:foo
 9 }
10 
11 //index.js
12 let aModule = require('./a.js');
13 let str = "this is 'index' module"
14 console.log(aModule);// {} -- 一個空對象

至於爲何不能這麼寫,暫時不在這裏闡述,下一節關於nodejs模塊底層實現會具體的分析介紹,這裏先來介紹nodejs模塊的一個設計思想。this

1.2 nodejs模塊的開閉原則設計實現編碼

1 //a.js -- 導出模塊
2 let num = 123;
3 let str = "this is module 'a'";
4 exports.a = a;
5 
6 //index.js -- 引用模塊a
7 let aModule = require('./a.js');
8 console.log(aModule.num);//123
9 console.log(aModule.str);//undefined

這裏你會發現只有被exports執行了導出的num成員才能被正常導出,而str成員沒有被執行導出,在依賴a.js模塊的index.js中是不能引用到a.js模塊中的str成員。可能你會說這不是很正常嗎?都沒有導出怎麼引用呢?spa

不錯,這是一個很是正常狀況,由於語法就告訴了咱們,要想引用一個模塊的成員就必須先在被引用的模塊中導出該成員。然而這裏要討論的固然不會是導出與引用這個問題,而是模塊給我實現了一個很是友好的設計,假設我如今在a.js中有成員str,在index.js模塊中也有成員str,這回衝突嗎?顯然是不會的,即便在a.js中導出str而且在index.js中引用a.js模塊,由於index.js要使用a.js模塊的成員str,須要使用接收模塊變量aModule.str來使用。設計

 1 //a.js
 2 let num = 123;
 3 let str = "this is module 'a'";
 4 exports.num = num;
 5 exports.str = str;
 6 
 7 //index.js
 8 let aModule = require('./a.js');
 9 let str = "this is module 'index'"
10 console.log(aModule.num);//123
11 console.log(aModule.str);//this is module 'a'
12 console.log(str);//this is module 'index'

基於開閉原則的設計方式,封閉可讓模塊的內部實現隱藏起來,開放又能夠友好的實現模塊之間的相互依賴,這相對於以前咱們經常使用的回調函數解決方案,程序設計變得更清晰,代碼複用變得更靈活,更關鍵的是還解決了js中一個很是棘手的問題——命名衝突問題,上面的示例就是最好的證實。這裏須要拋出一個問題,看示例:

1 //下面這種寫法有什麼問題?
2 //a.js
3 let num = 123;
4 module.exports = num;
5 
6 //index.js
7 let aModule = require('./a.js');
8 let str = "this is module 'index'"
9 console.log(aModule);//123

這種寫法不會報錯,也能正常達到目前的需求,若是從能解決目前的功能需求角度來講,它沒錯。可是開閉原則的重要思想就是讓模塊保持相對封閉,又有更好的拓展性,這樣寫顯然不合適,好比就上面的代碼寫完上線之後,業務又出現了一個新的需求須要a.js模塊導出一個成員str,這時候顯然須要同時更改a.js模塊和index.js模塊,即便新需求不須要index.js來實現也是須要改的。因此維持模塊的開閉原則是良好的編碼風格。

 2、nodejs模塊的底層實現原理

2.1 module.exports與exports的區別:

//a.js
console.log(module.exports == exports);//true

//而後在控制檯直接執行a.js模塊
node a.js

實際上它們是沒有區別的,那爲何在以前的exports不能直接等於一個對象,而module.exports能夠呢?這關乎於js的引用值指向問題:

 

 當export被賦值一個對象時,就發生了一下變化:

這時候咱們能夠肯定node不會導出exports,由於前面的示例已經說明了這一點,可是值得咱們繼續思考的是,node模塊是依據module.exports、exports、仍是它們指向的初始對象呢?這裏你確定會說是module.exports,由於前面已經有示例是module.exports指向一個新的對象被成功導出,可是我並不以爲前面那些示例能說服我,好比下面這種狀況:

 1 //a.js模塊
 2 let num = 123;
 3 function foo(val){
 4     console.log(val);
 5 }
 6 module.exports = {
 7     num:num
 8 }
 9 exports = {
10     foo:foo
11 }
12 //index.js模塊
13 let aModule = require('./a.js');
14 console.log(aModule);//這裏會打印出什麼?

咱們現不測試也不猜想,先經過下面的示圖來看下如今的a.js模塊中module.exports、exports、以及它們兩初始指向的空對象的關係圖:

 

 這時候咱們來看一下index.js執行會輸出什麼?

{ num: 123 }

因此從這個結果能夠看出,最後require()最後導入的是被引用模塊的module.exports。探討到這裏的時候並無到達node模塊的終點,咱們這裏module.exports、exports、require()是從哪裏來的?node系統內置變量?仍是別的?

2.2 node模塊的底層實現原理

這部分的內容其實也沒有太多能夠說的,就前面提出來的問題其實有一個方式就可讓你一目瞭然,只須要在一個js文件中編寫一下代碼,而後使用node執行這個js文件就能夠了:

1 console.log(require);      // 一個方法
2 console.log(module);       //  一個對象
3 console.log(exports);      //  一個空對象
4 console.log(__dirname);    //  當前模塊所在路徑
5 console.log(__filename);   //  當前文件的路徑

 這時由於node模塊實際上底層是被放到一個當即執行函數內(不要在意xyz這個名稱,由於我也不知道node底層到底用的什麼名稱),這些變量其實就是這個函數的參數,這個函數大概是一下形式:

1 function xyz(module.exports,require,module,__filename,__dirname){
2     //...
3     //  這裏就是咱們在模塊中寫入的代碼
4     //...
5     return module.exports;
6 }

經過上面的推斷就能夠獲得下面這樣的結果:

1 console.log(module.exports == arguments[0]);//true
2 console.log(require == arguments[1]);//true
3 console.log(module == arguments[2]);//true
4 console.log(__filename == arguments[3]);//true
5 console.log(__dirname == arguments[4]);//true

經過執行這段打印代碼也確實能夠獲得這樣的結果,到這裏又有一個值得咱們關注的內容,就是每一個模塊的module參數:

 1 console.log(module);
 2 Module {
 3     id: '.',//當前模塊的id都是'.',在後面的parent和children裏面的模塊對象上的id就是的對應模塊的filename
 4     exports: {},//這裏是模塊導出對象
 5     parent: null,//這裏是當前模塊被那些模塊引用的模塊對象列表,意思是當前模塊做爲那些模塊的父級模塊
 6     filename:'',//這裏是當前文件路徑的絕對路徑
 7     loaded: false,//模塊加載狀態,若是在模塊內部輸出module對象它永遠都會是false,由於只有這個模塊加載完成以後纔會被修改爲true
 8     children: [
 9         // 這裏是引用模塊module對象列表,意思是當前模塊做爲了那些模塊的子模塊
10     ],
11     paths:[ 
12         // 這裏是外部模塊包的路徑列表,從最近的路徑(模塊所在同級路徑)到系統盤路徑全部的node_modules文件夾路徑
13      ] 
14     }

到這裏有可能你還會問爲何底層實現裏面只有module.exports,沒有export,這個解釋起來真的費勁,下面這一行代碼幫你搞定:

let exports = module.exports;

這篇博客主要介紹了node模塊的內部內容,並未就node模塊基於commonjs規範作任何介紹,是由於在以前的博客中已經有了很是全面的解析,詳細參考博客開始時的鏈接,關於node模塊加載相關內容也是在那篇博客。

相關文章
相關標籤/搜索