在聊模塊以前,咱們先聊聊JS模塊化的不足javascript
在實應用中,JavaScript的表現能力取決於宿主環境中的API支持程程。在Web 1.0時,只有對DOM、BOM等基本的支持。隨着Web 2.0的推動 ,HTML5嶄露頭角,它將Web網頁帶進Web應用的時代,在瀏覽器中出現了更多、更強大的API供JavaScript調用。可是這些過程發生在前端,後端JavaScript 的規範卻遠遠落後。對於JavaScript自身而言,它的規範是薄弱的,還有如下缺陷。前端
所以,社區也爲JavaScript定了相應的規範,其中CommonJS的是最爲重要的里程程。java
CommonJS規範涵蓋了模塊、二進制、Buffer、字符集編碼、I/O流、單元測試、文件系統、進程環境、包管理等等。Node能以一中比較成熟的姿態出現,不開CommonJS規範的影響。 下圖是Node與瀏覽器以及w3c組織、CommonJS組織、ECMAScript之間的關係。 node
NodeCommonJS借鑑CommonJSd的的Modules規範實現了一套很是易用的模塊系統,NPM對Packages規範的無缺支持使得Node應用在開發過程當中事半功倍。在本文中,就以Node的模塊和包的實現展開說明。linux
CommonJS對模塊的定義十分簡單,主要分爲模塊引用、模塊定義、模塊標識3部分。json
在node.js 裏,模塊劃分全部的功能,每一個JS都是一個模塊 實現require方法,NPM實現了模塊的自動加載和安裝依賴後端
(function(exports,require,module,__filename,__dirname){
exports = module.exports={}
exports.name = 'zfpx';
exports = {name:'zfpx'};
return module.exports;
})
//往下會實現一個簡單的require方法
複製代碼
http
path
fs
util
events
編譯成二進制,加載速度最快,原來模塊經過名稱來加載通常本身寫的經過路徑來加載,別人寫的經過名稱去當前目錄或全局的node_modules下面去找數組
全局目錄 window
若是在環境變量中設置了NODE_PATH
變量,並將變量設置爲一個有效的磁盤目錄,require
在本地找不到此模塊時向在此目錄下找這個模塊。 UNIX操做系統中會從 $HOME/.node_modules
$HOME/.node_libraries
目錄下尋找瀏覽器
Node.js模塊分爲兩類(第三方模塊這裏暫時不提),一類是核心模塊(即原生模塊),一類是文件模塊(咱們本身寫的)。原生模塊加載速度最快,而文件模塊是動態加載的,加載速度比原生模塊慢。但Node對兩類模塊都會進行緩存,因此在第二次調用require的時候,是不會重複調用的。 在文件模塊中,又分3類:.js .json .node 緩存
從文件模塊緩存中加載 儘快原生模塊與文件模塊的優先級不一樣,可是都不會優先於從文件模塊的緩存中加載已經存在的模塊。
從原生模塊加載 原生模塊的優先級僅次於文件模塊緩存的優先級。require方法在解析文件名以後,會先檢查模塊是否在原生模塊列表中。舉個例子,怡http爲例,即便在目錄下存在http.js http.json http.node文件,但require("http")不會先從這些文件中加載,而是優先從原生模塊中加載。 從文件加載 當文件模塊緩存中不存在,而且不是原生模塊的時候,Node.js會解析require傳入的參數,病加載實際的文件。
整個查找過程相似原型鏈的查找和做用域的查找,但node對路徑查找實現了緩存機制,因此不會很耗性能。
接下來,將會實現一個簡單的require的方法,在此以前,先簡單瞭解一下須要用到的方法:
//引入fs模塊
let fs = require('fs');
// fs裏面有一個新增 判斷文件是否存在
fs.accessSync('./5.module/1.txt'); // 文件找到了就不會發生任何異常
let path = require('path');// 解決路徑問題
console.log(path.resolve(__dirname,'a')); // 解析絕對路徑
// resolve方法你能夠給他一個文件名,他會按照當前運行的路徑 給你拼出一個絕對路徑
// __dirname 當前文件所在的文件的路徑 他和cwd有區別
console.log(path.join(__dirname,'a')); // join就是拼路徑用的 能夠傳遞多個參數
// 獲取基本路徑
console.log(path.basename('a.js','.js')); // 常常用來 獲取除了後綴的名字
console.log(path.extname('a.min.js')); // 獲取文件的後綴名(最後一個.的內容)
console.log(path.posix.delimiter); // window下是分號 maclinux 是:
console.log(path.sep); // window \ linux /
// vm 虛擬機模塊 runinThisContext
let vm = require('vm');//很是像eval,eval能夠把字符串當成js文件執行,但它是依賴於環境的
var a = 1;
eval('console.log(a)');//運行這行代碼的時候,是依賴於var a = 1這個環境的,這個方案會污染eval的執行結果
var b = 2;
vm.runInThisContext('console.log(b)');//runInThisContext會製造一個乾淨的環境。讓代碼跑在乾淨的環境裏,乾淨的環境會隔離上面寫的代碼,輸出:b is not defined
複製代碼
輸出的結果:
c:\Users\19624\Desktop\201802\5.module\a
c:\Users\19624\Desktop\201802\5.module\a
a
.js
:
\
1
/* 能夠在node環境中運行試試看 */
複製代碼
require的簡單實現:
let fs = require('fs');
let path = require('path');
let vm = require('vm');
// 本身寫一個模塊加載require
// require 出來的是一個模塊
function Module(filename){//構造函數,每一個模塊都應該有個絕對路徑
this.filename = filename;
this.exports = {};
}
// 若是沒有後綴的話,但願加js,json還有node的擴展名,擴展名存到構造函數上
Module._extentions = ['.js','.json','.node'];//若是沒有後綴,但願添加上查找
// 緩存的地址
Module._cathe = {};//讀到一個文件,就往裏放一個, key是它的絕對路徑,值是它的內容
// 解析絕對路徑
Module._resolvePathname = function(filename){
let p = path.resolve(__dirname,filename);//以當前的文件夾路徑和filename解析
console.log(path.extname(p))
if(!path.extname(p)){//判斷文件是否有後綴名,若是有,則直接返回p,沒有則作進一步處理
//沒有的話,循環Module._extentions,一個個加後綴
for(var i = 0;i<Module._extentions.length;i++){
let newPath = p + Module._extentions[i];
//console.log(newPath);
// 判斷路徑存不存,若是不存在,會報異常,爲了避免報錯,使用try catch
try {//若是訪問的文件不存在,就會發生異常
fs.accessSync(newPath);
// 沒報錯,就返回newPath
return newPath
} catch (e) {}
}
}
return p;//解析出來的就是一個絕對路徑
}
// 加載模塊
Module.wrapper = [
"(function(exports,require,module,__dirname,__filename){","\n})"
];
Module.wrap = function(script){
return Module.wrapper[0] + script + Module.wrapper[1];//拼成一個函數
}
Module._extentions['js'] = function(module){//Module._extentions加了一個屬性,屬性等於函數
// 若是是js,就同步的讀取出來,而後去exports的值
// module是個對象,對象裏有 filename exports
let script = fs.readFileSync(module.filename);
// 執行的時候,會套個閉包環境
// (function(exports,require,module,__dirname,filename){})
let fnStr = Module.wrap(script);//拼成一個函數
vm.runInThisContext(fnStr).call(module.exports,module,exports,req,module)
};
Module._extentions['json'] = function(module){
let script = fs.readFileSync(module.filename);
// 若是是json直接拿到內容 json.parse便可
module.exports = JSON.parse(script);
}
Module.prototype.load = function(filename){//加了prototype是實例調用的,不加是構造函數調用的
// 模塊多是json,也多是js
let ext = path.extname(filename).slice(1);//.js .json,slice方法刪除.
// js用js的方式加載,json用json的方式加載
Module._extentions[ext](this);//原型中的this,指的是當前Module的實例,看上兩行代碼Module._extentions['js'],這裏的this傳給module,module表明當前加載的模塊
}
function req(filename){//filename是文件名 文件名可能沒有後綴
// 咱們須要弄出一個絕對路徑來,緩存是根據絕對路徑來的
// 先獲取到絕對路徑
filename = Module._resolvePathname(filename);
console.log(filename);
// 先看這個路徑在緩存中有沒有,若是有則直接返回
let catchModule = Module._cathe[filename];
if(catchModule){
// 若是是true,表示緩存過,就會直接返回緩存的exports屬性
return catchModule.exports
}
// 沒緩存,加載模塊--建立實力
let module = new Module(filename);//建立模塊
// 將模塊的內容加載出來
module.load(filename);//load是原型鏈上的方法,往上看~
Module._cache[filename] = module;
module.loaded = true; // 表示當前模塊是否加載完
return module.exports;
}
let result = require('./school');
result = req('./school');
console.log(result);
複製代碼
此時school的文件是:
// 導出文件 才能給其餘文件使用
console.log('加載');
module.exports = 'leo'
複製代碼
輸出的結果爲:
加載
加載
zfpx
複製代碼