## 1.1 前言
### 1.1.1 什么是NodeJS
JS是腳本語言,腳本語言都需要一個解析器才能運行。對于寫在HTML頁面里的JS,瀏覽器充當了解析器的角色。而對于需要獨立運行的JS,NodeJS就是一個解析器。
每一種解析器都是一個運行環境,不但允許JS定義各種數據結構,進行各種計算,還允許JS使用運行環境提供的內置對象和方法做一些事情。例如運行在瀏覽器中的JS的用途是操作DOM,瀏覽器就提供了document之類的內置對象。而運行在NodeJS中的JS的用途是操作磁盤文件或搭建HTTP服務器,NodeJS就相應提供了fs、http等內置對象。
**簡單的說, Node.js 就是運行在服務端的 JavaScript。**
### 1.1.2 作用
NodeJS的作者說,他創造NodeJS的目的是為了實現高性能Web服務器,他首先看重的是**事件機制和異步IO模型**的優越性,而不是JS。但是他需要選擇一種編程語言實現他的想法,這種編程語言不能自帶IO功能,并且需要能良好支持事件機制。JS沒有自帶IO功能,天生就用于處理瀏覽器中的DOM事件,并且擁有一大群程序員,因此就成為了天然的選擇。
如他所愿,NodeJS在服務端活躍起來,出現了大批基于NodeJS的Web服務。而另一方面,NodeJS讓前端眾如獲神器,終于可以讓自己的能力覆蓋范圍跳出瀏覽器窗口,更大批的前端工具如雨后春筍。
因此,對于前端而言,雖然不是人人都要拿NodeJS寫一個服務器程序,但簡單可至使用命令交互模式調試JS代碼片段,復雜可至編寫工具提升工作效率。
## 1.2 模塊
### 1.2.1 NodeJS模塊
**js的天生缺陷——缺少模塊化管理機制**
表現: JS中容易出現變量被覆蓋,方法被替代的情況(即被污染)。特別是存在依賴關系時,容易出現錯誤。這是因為JS缺少模塊管理機制,來隔離實現各種不同功能的JS判斷,避免它們相互污染。
解決:經常采用命名空間的方式,把變量和函數限制在某個特定的作用域內,人肉約定一套命名規范來限制代碼,保證代碼安全運行。jQuery中有許多變量和方法,但是無法直接訪問,必須通過jQuery,$調用各個方法。
**Commonjs規范**
不同于jQuery,Commonjs是一套規范,約定了js如何組織,如何編寫,包括包,二進制,套接字,單元測試等等。大部分標準在擬定和討論之中,首先把執行不同任務的代碼塊和代碼文件看為獨立的模塊,每一個模塊都是一個單獨的作用域,但不是孤立的,可能存在依賴關系。每個模塊分為三個部分,定義、標識和引用。這套規范與現實產品如node.js相互影響,良性循環。
**NodeJs的模塊管理機制**
基于commonjs實現了模塊管理系統。node中每一個js文件都是一個獨立的模塊,在其內部不需要有命名空間,不需要擔心變量的污染和方法定義時的隔離。同時模塊之間可以組合形成更強大的模塊或功能包。npm即是用來管理各種功能包的。
**模塊的類型**
①核心模塊:如http、fs、path...
②本地模塊:var util = require('./require.js');
③通過npm安裝的第三方模塊
在nodejs中可以通過文件路徑或文件名來引入模塊,如果用名稱引用非核心模塊的話,node最終會把模塊映射到對應的模塊文件的路徑,那些包含了核心函數的核心模塊會在node啟動時被預先加載。
### 1.2.2 CommonJS規范
在CommonJS規范下,每個.js文件都是一個模塊,它們內部各自使用的變量名和函數名都互不沖突,例如,a.js和b.js都申明了全局變量var s = 'xxx',但互不影響。
一個模塊想要對外暴露變量(函數也是變量),可以用`module.exports = variable;`,一個模塊要引用其他模塊暴露的變量,用`var ref = require('module_name');`就拿到了引用模塊的變量。
在編寫每個模塊時,都有`require、exports、module`三個預先定義好的變量可供使用。
**(1)require**
require函數用于在當前模塊中加載和使用別的模塊,傳入一個模塊名,返回一個模塊導出對象。模塊名可使用相對路徑(以./開頭)或者是絕對路徑(以/或C:之類的盤符開頭)。另外,模塊名中的.js擴展名可以省略。
~~~javascript
//例子
var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');
// foo1至foo4中保存的是同一個模塊的導出對象。
~~~
還可以使用以下方式加載和使用一個JSON文件。
~~~javascript
var data = require('./data.json');
~~~
**(2)exports**
exports對象是當前模塊的導出對象,用于導出模塊公有方法和屬性。別的模塊通過require函數使用當前模塊時得到的就是當前模塊的exports對象。下例中導出了一個公有方法。
~~~javascript
exports.hello = function () {
console.log('Hello World!');
};
~~~
**(3)module**
通過module對象可以訪問到當前模塊的一些相關信息,但最多的用途是替換當前模塊的導出對象。例如模塊導出對象默認是一個普通對象,如果想改成一個函數的話,可以使用以下方式。
~~~javascript
module.exports = function () {
console.log('Hello World!');
};
~~~
詳細的導入導出原理,可以查看這篇[教程](https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434502419592fd80bbb0613a42118ccab9435af408fd000)
## 1.3 模塊初始化
一個模塊中的 JS 代碼僅在模塊第一次被使用時執行一次,并在執行過程中初始化模塊的導出對象。之后,緩存起來的導出對象被重復利用。
**主模塊**
通過命令行參數傳遞給 NodeJS 以啟動程序的模塊被稱為**主模塊**。主模塊負責調度組成整個程序的其它模塊完成工作。例如通過以下命令啟動程序時,main.js 就是主模塊。
~~~
node main.js
~~~
例如有以下目錄。
~~~
- /NodeJS/hello/
- util/
counter.js
main.js
~~~
~~~javascript
//counter.js
var i = 0;
function count() {
return ++i;
}
exports.count = count;
~~~
該模塊內部定義了一個私有變量 i,并在 exports 對象導出了一個公有方法 count。
主模塊 main.js :
~~~javascript
var counter1 = require('./util/counter');
var counter2 = require('./util/counter');
console.log(counter1.count());
console.log(counter2.count());
console.log(counter2.count());
~~~
運行該程序的結果如下:
~~~javascript
$ node main.js
1
2
3
~~~
可以看到,counter.js 并沒有因為被 require 了兩次而初始化兩次。