- nodejs模塊語法與開閉原則
- nodejs模塊的底層實現
一、nodejs模塊語法與開閉原則
關於nodejs模塊我在之前的兩篇博客中都有涉及,但都沒有對nodejs模塊的底層做做任何探討,但是為了使相關內容更方便查看比對理解,這裏還是先引入一下之前兩篇博客的連接:
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導出模塊不能這麼寫:
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模塊的一個設計思想。
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成員。可能你會說這不是很正常嗎?都沒有導出怎麼引用呢?
不錯,這是一個非常正常情況,因為語法就告訴了我們,要想引用一個模塊的成員就必須先在被引用的模塊中導出該成員。然而這裏要討論的當然不會是導出與引用這個問題,而是模塊給我實現了一個非常友好的設計,假設我現在在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來實現也是需要改的。所以維持模塊的開閉原則是良好的編碼風格。
二、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模塊加載相關內容也是在那篇博客。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計
※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務
※Google地圖已可更新顯示潭子電動車充電站設置地點!!