nodejs中require的認識

關於Nodejs的優缺點以及它的使用場景這裡不會多說,因為既然是想要看Nodejs的文章,最主要的原因,應該是為瞭想要掌握這門技術吧?我這麼認為。在剛接觸Nodejs的最初的幾天裡,感覺自己一直就像個沒頭的蒼蠅,到處亂撞,看東西感覺心煩意亂的,後來慢慢的發現瞭一些規律,理解瞭一些基本的東西,才開始變得順理成章瞭,而在這個過程中,對require的認識,我覺得是至關重要的,所以就有瞭這篇文章。

Nodejs的核心方法–require

如果說Nodejs中最基礎最核心的一個方法,我認為就是模塊加載方法瞭,如何正確的理解這個方法的使用,關系到是否能更好更熟練的寫出nodejs的代碼,最近要使用nodejs,所以也花瞭挺多時間看這方面的資料,因為不喜歡記憶,喜歡去理解一個東西的我來說,快速的掌握這個,並且把常用模塊API都記下來簡直就是不太現實的,所以當中也有過暴躁抓狂的時候,不過也算是走瞭過來,現在雖然仍然是處於學習階段,但是對於require也有瞭一點自己的理解,所以就在這裡分享一下,如果有說的不對的地方,請多指教。

這裡,我就首先假設,您的電腦上已經配置瞭Nodejs,並且,您對Nodejs有一個最基礎的認識,比如知道如何執行一個nodejs的文件,就足夠瞭,如果這些您還不瞭解,那麼請先暫停閱讀,先安裝nodejs,並瞭解一個最簡單的nodejs程序,是怎麼寫的,比如使用nodejs輸出“Hello World”。如果這些您都瞭解,那麼請繼續向下看。

使用方法–類似於匿名的自執行函數

我是做前端的,那麼就以前端的JS代碼進行對比的說明,首先要說到的就是模塊化,前端的模塊化最常見的就是封裝到一個函數內部吧,而我覺得比較常用的就是使用匿名函數直接生成一個單獨的模塊,就像下面這樣:

(function(){
	//一段邏輯代碼。
})();

這樣,外部的所有的程序都不能訪問該模塊內部的屬性和方法,require也具有相同的性質,舉個最簡單的例子(變量的訪問):

app.js文件中,使用require加載一個test.js的模塊。可以在app.js中這麼寫:

var strapp = "this is app.js";
require("./test");
console.log("next two log is from app.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}

在test.js中這麼寫:

var strtest = "this is test.js";

console.log("next two log is from test.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}

那麼在執行app.js之後,就會在Nodejscommand面板中,顯示以下信息:

next two log is from test.js
strapp is undefined!
strtest=this is test.js
next two log is from app.js
strapp=this is app.js
strtest is undefined!

對比使用JS中匿名自執行函數來寫一個相同的例子:

var strapp = "this is app.js";
(function(){
    //代替nodejs中的require("./test");
    var strtest = "this is test.js";

    console.log("next two log is from test.js");
    if(typeof strapp == "undefined"){
	console.log("strapp is undefined!");
    }else{
	console.log("strapp="+strapp);
    }
    if(typeof strtest == "undefined"){
	console.log("strtest is undefined!");
    }else{
	console.log("strtest="+strtest);
    }
})();
console.log("next two log is from app.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}

這個時候的執行結果,對於JS作用域有所瞭解的,都能想象到,最後的輸出結果吧?結果就是:

next two log is from test.js
strapp=this is app.js
strtest=this is test.js
next two log is from app.js
strapp=this is app.js
strtest is undefined!

如果您對這段JS的執行結果,有什麼疑問,說明您對作用域的理解還不足,請先查看另外一篇文章,也許可以給你一些幫助:淺析作用域鏈–JS基礎核心之一。

類似於對象

這種方法算是最常見的瞭,因為所有的核心模塊,都算是基於該類的。

那就是在執行require加載模塊之後,會返回一個對象,一個稱之為module.exports(關於module以後再進行說明,本篇不涉及)的對象,既然返回的是一個對象,那麼這個對象就有可能是一個空對象,包含一個或多個屬性或者方法的對象。

首先,我們來看下,什麼時候會返回一個空對象?

就像是上面的代碼,上面的那個實例的代碼,其實就是返回瞭一個空對象。下面我們把app.js的內容進行簡化為如下:

var strapp = "this is app.js";
var test = require("./test");
console.log(test);

執行node app.js,看看在command的控制臺上,會出現什麼樣的信息呢?

next two log is from test.js
strapp is undefined!
strtest=this is test.js
{}     //註意這裡,這就是console.log打印出的結果

OK,到瞭這裡,我們就對這個require有瞭更多一點的認識。

接下來,再修改test.js的內容為如下內容:

var strtest = "this is test.js";
module.exports = strtest;

這個時候,再次執行node app.js,會有什麼效果呢?此時就隻有一條輸出,其內容為:

this is test.js

由此可以看出,在通過require("./test")加載模塊之後,這裡返回的卻直接是一個字符串瞭,為瞭能確認這一點,可以自己查看一下test的類型就能確定瞭。這裡不再多說。

說到這裡,想必也能發現一個問題瞭,就是前文有提到過,require加載模塊之後,會返回module.exports對象,這裡說是對象就有些不嚴謹瞭,因為像上面這樣,返回的並不是對象,而是一個字符串,所以,隻能說是,require加載模塊之後,返回的是module.exports的屬性值。

並且,通過上面的例子,我們還能發現一個問題就是,Nodejs在變量類型上,是完全和js相同的,比如這裡,可以給一個空對象賦值,把這個對象變成字符串。其實吧,也算是正常,Nodejs本就是使用的js的語法,隻是個人總覺得,有些東西,隻有試過,嘗試瞭多種用法之後,才能放心的使用。

既然這裡可以返回一個字符串,那肯定也可以返回其他類型的數據瞭,比如一個函數,一個對象?所以,我們就可以利用這一特性,把一些單獨的模塊直接拉出來,然後進行處理,並且,這些單獨的模塊,我們還不需要擔心,定義的變量什麼的會影響到其他的模塊。因為就算是在加載模塊的文件中(例如上面的app.js),也不能調用被加載的模塊(例如上面的test.js)中的私有變量,隻能通過require的返回值,調用module.exports的屬性值中的信息。

說到這裡,依然拿出JS中類似的寫法,這裡的require依然類似於JS中的自執行函數,比如下面的JS例子,就可以完成和上面的nodejs中兩個文件相同的效果。說的更相關一點,就是require就是在使用閉包。

var test = (function(){
    var strtest = "this is test.js";
    return strtest;
})();

var app = (function(){
    var apptest = "this is app.js",
	gettest = test;
	
    console.log(gettest);
})();

使用js執行,就可以有相同的效果,並且,test中的私有變量不能訪問app中的私有變量,而app中的私有變量,也不能訪問test中的私有變量,這樣,就達到瞭和nodejs相同的效果瞭。當然啦,同樣可以通過一些對外的公開的接口訪問到內部的一些信息,比如這裡的test方法,就返回一個strtest的屬性值。

雖然,這裡都是寫的最簡單的,但是轉念想一想,如果test方法返回的是一個對象呢?是一個函數呢?這個熟悉JS的都應該能明白的,不多說。

總結其實看到這裡,應該也對require有瞭一點基本的認識瞭吧,我就覺得,require其實就是會返回一個數據的自執行函數,就像前面的這個代碼一樣,隻是寫法不同而已。

再對比到nodejs這邊,如果使用require加載瞭一個模塊中,module.exports返回的是一個函數呢,返回的是一個對象呢?嘿嘿…相同的用法的。

所以,想象一下,我們常使用的一些核心模塊的方法,比如:

var exp = require('express'),
    app = exp.createServer();

現在看來,也隻是在require中,返回瞭express模塊中的module.exports的屬性值,並把該值保持到瞭exp變量中,那麼該變量中,就有包含瞭所有的module.exports中的屬性值,所以通過不同的方法名,就可以調用到相應的方法。

所以也就通過exp.createServer方法,創建瞭一個服務。有興趣的話,可以看看express中,存在多少的屬性和方法,直接在上述的基礎上,使用console.log(exp)即可(參數隻要exp,才能看到的哦,還有就是,你當前的工程,是添加瞭express模塊的,可以檢查當前工程的package.json文件中的dependencies屬性中,是否有express的相關信息,也可以直接查看:node_modules文件夾中查看,是不是有express的模塊,這裡不對這個進行細說)。

說到這裡,我覺得如果能理解到require的工作原理,也算是nodejs的基本入門瞭吧,因為再其他的,就是瞭解模塊的方法的功能瞭,瞭解怎麼用瞭。這些都是需要一些記憶和練習的,我個人覺得,這都是次要的瞭。

重復加載模塊

當有時候,我們重復加載瞭同一個模塊,會不是有問題呢?比如下面這樣的代碼:app.js中:

var test1 = require('./test.js');
console.log("test1="+test1);

var test2 = require('./test.js');
console.log("test2="+test2);

test.js中:

var num = Math.random();
console.log(num);
module.exports = num;

這個時候,會不會加載兩次呢?試試就知道瞭,不會的,這個時候的控制臺的信息顯示結果順序為(是一個隨機數):

console.log(num);
console.log("test1="+test1);
console.log("test2="+test2);

所以肯定是require中進行瞭處理,保證在require時,如果已經加載過該模塊,那麼就把之前加載的進行賦值,所以,在這裡,app.js中的test2的賦值,其實質也就是:test2 = test1;

這些都清楚瞭,我覺得對於nodejs就算是有一點認識瞭吧,反正在我剛學習nodejs的前幾天,沒有想通一些事情時,就會感覺有勁無處使,沒有方向的亂撞的感覺。明白瞭本文的這些東西之後,就感覺一切都通順瞭。

在nodejs這塊,我還隻是個新手,所以對於require的理解,也有很多不足,如果文中有什麼錯誤,或者描述不當,請指教。

發佈留言