第二章. node中的模块和require
2019獨角獸企業重金招聘Python工程師標準>>>
一 什么是模塊.?????
????? JavaScript誕生初,它只不過是一個網頁的小腳本而已,沒有人會想到它會發展到現在能有大量的庫,工具,組件變得如此復雜,慢慢地javascript發展中,人們發現javascript有個先天的缺陷--缺少模塊.
????? 在其他語言中,java有類,python有import, php 有include和require甚至比它底層的C也有include.而javascript只能通過 <script>標簽引入,這種方式令代碼變得雜亂,依賴變得不清晰,安全性也不好(全局變量容易被污染).
為了解決問題,node引入了模塊這個概念(準確來說是CommonJS引入了模塊).nodejs中的模塊具有隔離作變量用域(包)防止變量污染和實習私有變量,開放接口和成員,提供外部引用,解決依賴不清晰的問題.
關于隔離和接口,在后面會再講
PS:nodejs模塊實現代碼主要在 lib/module.js.
二 node模塊分類
node模塊有下面幾種:
??? 1.系統模塊(核心模塊):由nodejs自帶提供,可以是用js或者C++編寫的,已經經過編譯的模塊.
??? 2.文件模塊(用戶模塊):由用戶編寫的.
??? 核心模塊在源代碼中已經編譯了,以二進制文件存在,模塊引入時可以直接加到內存,文件定位和編譯都省略掉,并在路徑分析中優先,所以速度是最快的.
??? 文件莫模塊運行的時候動態加載,需要經過路徑分析和文件定位,編譯,速度一般比核心模塊慢.
三 require 方法:
1.require作用:
??? require方法是nodejs提供的用于引用模塊的方法.例如我們需要引入文件模塊,則使用下面代碼
varmod = require('module_name')
??? 此句執行后,Node內部會載入內置模塊或通過NPM安裝的模塊。require函數會返回一個對象,該對象公開的API可能是函數,對象,或者屬性如函數,數組,甚至任意類型的JS對象。
2.后綴:
require 默認接受以下后綴:
1 .js 文件 node會通過fs模塊同步方式讀取文件內容,在文件頭尾加入內容,以實現作用域隔離和module.exports,編譯并且執行. 其他非node,json,js后綴的統一也當作.js處理.
2.json 文件 node會通過fs模塊同步方式讀取文件內容,并調用JSON.parse 返回執行JSON.parse后的json對象.
3.node 文件 這是C++編寫文件? node會調用dlopen()方法加載編譯后生出來的文件.
當文件不帶后綴,node會依照 目錄 .js .json .node 的順序進行查找.如果所有模塊路徑都找不到該文件,則拋出異常.
3. 模塊路徑(路徑分析):
require方法接受下面的引用方式
1.require("模塊名"),或者 require('目錄/模塊名') 不以./開頭的相對路徑
?如果模塊名不是路徑,也不是內置模塊,Node將試圖去當前目錄的node_modules文件夾里搜索。如果當前目錄的node_modules里沒有找到,Node會從父目錄的node_modules里搜索,這樣遞歸下去直到根目錄。如果找不到文件,則會拋出異常
2.require("./模塊名")或者 require('./目錄/模塊名') , 以 ./開頭的相對路徑
node會直接加載以node運行的工作目錄為基準的該模塊.如果模塊不存在則拋出異常
3 require("/目錄/模塊名") 或者 require('F:/目錄/模塊名') 絕對路徑
nodej會直接加載該模塊.如果模塊不存在則拋出異常.
*在這里值得注意的是,如果是想加載當前目錄的模塊 必須使用:require("./模塊名"),使用require("模塊名")是不行的
四.模塊緩存
對于已加載的模塊Node會緩存下來(核心模塊會放在NativeModele._cache上,文件模塊放在Module._cache上),而不必每次都重新搜索和編譯執行。多次require的同個模塊只會被執行一次,下面是一個示例
//modA.js
console.log('模塊modA開始加載...')
exports =function() {
console.log('Hi')
}
console.log('模塊modA加載完畢')
//init.js
varmod1 = require('./modA')
varmod2 = require('./modA')
console.log(mod1 === mod2)
命令行執行:
node init.js
輸出如下
$node init.js?????????????????????
模塊modA開始加載...??????????
模塊modA加載完畢???????????
true ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
可以看到雖然require了兩次,但modA.js仍然只執行了一次。mod1和mod2是相同的,即兩個引用都指向了同一個模塊對象。
模塊的緩存是基于文件的絕對路徑相關為key的數組,這樣保證了模塊緩存的唯一性,不會因為require中不同的寫法導致重復載入.
例如:
varmod1 = require('../demon/modA') ;
varmod2 = require('./modA')
console.log(mod1 === mod2)
我們依舊可以得到相同的結果.
五.模塊的作用域隔離:
1.什么是作用域.
? ? ?任何程序設計語言都有作用域的概念,簡單的說,作用域就是變量與函數的可訪問范圍,即作用域控制著變量與函數的可見性和生命周期。在JavaScript中,變量的作用域是通過"閉包"來實現,由于閉包作用范圍,我們又把其分為全局變量和局部變量.
1.1 閉包
? 閉包是javascript的作用域實現方式,簡單來說,一個閉包的范圍就是一個function的開始到結束之間.而整個程序我們可以認為是最大的閉包.而閉包具有"單向鏈性",即子級的閉包可以訪問父級閉包自身的和能訪問的對象.例如下面代碼:
var globalVar='I am golbal val';
function packetA(){
? ? ? ? ?var aVar='I am a var in A';
?????????function packetB(){
????????? ? var ?bVar ='I am a var in B';
????????????console.log(globalVal); // 輸出?I am golbal val
????????????console.log(aVar); // 輸出?I am a var in A
????????? ? console.log(bVar);//bVar ='I am a var in B';
? ? ? ? }
??????
????? ??console.log(globalVal); // 輸出?I am golbal val
????????console.log(aVar); // 輸出?I am a var in A
? ? ? ??console.log(bVar); // undefined
? ? ? ? packetB();
}
packetA();
//packetB() //在這里由于packetB在packetA內,不可被訪問,因此會拋出錯誤
2.node 作用域隔離的實現.
其實node隔離作用域實現非常簡單,就是在require的實現中,針對.js后綴的文件,node并非直接把讀入內容進行執行,而是在其開頭加入到一個封包類這個類就是一個隔離,再通return實現開放接口.
思路大概這樣
(function(){
return function(){
? //你的代碼 ?
}
})()
當然,實際沒有這么簡單.我明白其利用reurn 產生開放接口,利用function來實現隔離就夠了.
如果你寫比較多的面向對象js,你看了這段代碼,你會知道可以通過this來實現類似module.exports開放(public)的功能.
例如:
//文件 modA.js
console.log(test. publicVal);//輸出100
當然,官方并不希望我們這樣做.node更希望我們在文件的末端統一使用 module.exports=開放的內容 這種方式實現開放內容.
附帶:模塊實現實際精簡代碼:
function Module(id, parent) {?
? this.id = id;?
? this.exports = {};?
? this.parent = parent;?
? if (parent && parent.children) {?
? ? parent.children.push(this);?
? }?
?
? this.filename = null; ?
? this.loaded = false;?
? this.children = [];?
}?
//....
module.exports = Module;
///...
Module._load = function(request, parent, isMain){
//...
return?module.exports
}
六 ,module.exports
1.exports和module.exprts的疑惑
許多人都曾經糾結過為何存在exports情況下,還存在module.exports,理想情況只要給exports賦值即可.
首先,我們來理解一下js的對象引用機制.
var a=b={}, c={};
console.log(a);?
console.log(b);
console.log(a==b); //true javascript對象的比較是比較指針是否指向同一個地址
console.log(a==c);//false?javascript對象的比較是比較指針是否指向同一個地址而不是內容是否相同(基礎類型除外)
a.test=1;
console.log(a);
console.log(b);
console.log(a==b); //true
b.test=2;
console.log(b);
console.log(a);
console.log(a==b); //true
a=123;
console.log(a);
console.log(b);
console.log(a==b);//false
js的對象賦值并非跟php那樣復制一份,而是把對象的地址給了變量,直接給變量賦值時候javascript會創建一個新的內存,再把變量指針指向新的內存地址,從而改變值.(ps:形參也一樣,exports實際是一個形參)
exports是為了方便我們更改module.exports而設立的,真正的開放的接口是module.exports(在_load方法里面 返回的是module.exports) ;
exports和module.exports一開始指向同一個內存空間,如果你不直接對exports直接賦值而是對其子成員賦值,例如:
exports.son=1;
那么你就不會改變?module.exports ==?exports的關系
但如果你這樣:
exports={son:123};
那么就會改變module.exports ==?exports的關系,?module.exports ==?exports將會不成立,因為他們已經指向不同的內存塊.
當_load方法返回module.exports的時候(把它開放出去),你exports的內容實際不能被返回了,也就是它并沒有被開放出去.
但我們可以這樣:
module.exports={son:321};
雖然直接對module.exports進行賦值操作,一樣會破壞module.exports ==?exports的關系,不過使用module.exports即使module.exports ==?exports的關系不再成立,但不影響其把對象開放出去.
? ? ?在這種條件下,我建議使用module.exports作為一種習慣,另外最好不要像例子中這樣直接對module.exports或者exports進行操作,而是操作其子對象,因為無論直接操作module.exports還是exports,都會破壞兩者之間的正確關系(指向同一內存塊).這種操作非常不安全,直接操作module.exports可能會丟失之前開放的對象,而直接操作exports會導致你沒法開放對象.
2.使用this開放對象和使用module.exports開放對象分析.
官方提供了module.exports這個對象來開放對象,但通過代碼實現的分析,我們知道我們可以用this來實現開放對象.
例如:
例如:
//文件 modA.js
?console.log(test.a);//輸出100
但這樣做并不好,首先this的指向在javascript中就是一個非常容易犯錯的東西,this會因為調用對象不一樣而指向不一樣.而module.exports具有固定指向性.如果使用this,那么只能在模塊的根包范圍(全局)范圍內使用
看下面的例子:
/文件 modA.js
function test(){
? ? this.want2Public =200
}
test(); //文件 init.js
console.log(test. publicVal);//100
console.log(test.want2Public);//undifined
由于this的不穩定,你很可能會自己挖坑.
而module.exports就不存在這個問題.
上面的例子,把this改為module.exports,那want2Public 也可以開放出來了.
(由于工作及生活還不穩定,沒法提供一個準確的更新時間,博客不定期更新,如果覺得可以,希望大家多支持,點贊,如果有疑問請留言)
轉載于:https://my.oschina.net/gclinux/blog/492000
總結
以上是生活随笔為你收集整理的第二章. node中的模块和require的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: word文档中怎么插入打钩符号(怎样在w
- 下一篇: URAL 1225 Flags