Node.js 是什麼?
以下引自 Node.js 官網:
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
- 不是編程語言
- 也不是框架和庫
- 是一個 JavaScript 運行時(環境)
- 能解析和執行 JavaScript 代碼(嚴格來說應該是 ECMAScript 代碼)
- 構建於 Chrome V8 JavaScript 引擎之上
- 爲JavaScript 提供了服務端編程的能力
- 文件IO
- 網絡IO
- 從技術角度它的能力和 Java、PHP、Python、Perl、Ruby 等服務端技術類似
Node 的特點
- 事件驅動
- 非阻塞 IO(異步) 模型
- 單線程
- 跨平臺
執行一個JS文件
1. 新建一個 hello.js 並寫入以下示例代碼
const message = 'Hello Node.js!'
console.log(message)
2. 打開命令行並定位到 hello.js
文件所屬目錄
3. 在命令行中輸入 node hello.js
回車執行
注意:
- 文件名不要起名爲
node.js
- 文件名或者文件路徑最好不要有中文
- 文件路徑或者文件名不要出現空格
文件讀寫
文件讀取:
const fs = require('fs')
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err
console.log(data)
})
文件寫入:
const fs = require('fs')
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err
console.log('The file has been saved!')
})
HTTP 服務
// 接下來,我們要幹一件使用 Node 很有成就感的一件事兒
// 你可以使用 Node 非常輕鬆的構建一個 Web 服務器
// 在 Node 中專門提供了一個核心模塊:http
// http 這個模塊的職責就是幫你創建編寫服務器的
// 1. 加載 http 核心模塊
var http = require('http')
// 2. 使用 http.createServer() 方法創建一個 Web 服務器
// 返回一個 Server 實例
var server = http.createServer()
// 3. 服務器要幹嘛?
// 提供服務:對 數據的服務
// 發請求
// 接收請求
// 處理請求
// 給個反饋(發送響應)
// 註冊 request 請求事件
// 當客戶端請求過來,就會自動觸發服務器的 request 請求事件,然後執行第二個參數:回調處理函數
server.on('request', function () {
res.end('Hello Node.js!')
})
// 4. 綁定端口號,啓動服務器
server.listen(3000, function () {
console.log('服務器啓動成功,請求訪問 http://127.0.0.1:3000/')
})
什麼是模塊化
當你的網站開發越來越複雜代碼越來越多的時候會經常遇到什麼問題?
- 惱人的命名衝突
- 繁瑣的文件依賴
歷史上,JavaScript一直沒有模塊(module)體系, 無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。 其他語言都有這項功能,比如Ruby的 require
、Python的 import
, 甚至就連CSS都有 @import
,但是JavaScript任何這方面的支持都沒有,這對開發大型的、複雜的項目形成了巨大障礙。
現實角度(手機、電腦、活動板房):
- 生產效率高
- 可維護性好
程序角度(就是把大一個文件中很多的代碼拆分到不同的小文件中,每個小文件就稱之爲一個模塊,例如我們看到的 jQuery 真正的源碼)
- 開發效率高(不需要在一個文件中翻來翻去,例如 jQuery 不可能在一個文件寫 1w+ 代碼,按照功能劃分到不同文件中)
- 可維護性好(哪個功能出問題,直接定位該功能模塊即可)
模塊化的概念有了,那程序中的模塊到底該具有哪些特性就滿足我們的使用了呢?
- 模塊作用域
- 好處就是模塊不需要考慮全局命名空間衝突的問題
- 模塊通信規則
- 所有模塊如果都是封閉自然不行,我們需要讓組件與組件相互組織聯繫起來,例如 CPU 需要讀取內存中的數據來進行計算,然後把計算結果又交給了我們的操作系統
- 所以我們的模塊也是需要具有通信的能力的
- 所謂的通信說白了也就是輸入與輸出
下面我們具體來看一下在 Node.js 中如何在多模塊之間進行輸入與輸出。
模塊通信規則
require
模塊導入
// 核心模塊
var fs = require('fs')
// 第三方模塊
// npm install marked
var marked = require('marked')
// 用戶模塊(自己寫的),正確的,正確的方式
// 注意:加載自己寫的模塊,相對路徑不能省略 ./
var foo = require('./foo.js')
// 用戶模塊(自己寫的),正確的(推薦),可以省略後綴名 .js
var foo = require('./foo')
exports
模塊導出
導出多個成員:寫法一(麻煩,不推薦):
// 導出多個成員:寫法一
module.exports.a = 123
module.exports.b = 456
module.exports.c = 789
導出多個成員:寫法二(推薦)
Node 爲了降低開發人員的痛苦,所以爲 module.exports
提供了一個別名 exports
(下面協大等價於上面的寫法)。
console.log(exports === module.exports) // => true
exports.a = 123
exports.b = 456
exports.c = 789
exports.fn = function () {
}
導出多個成員:寫法三(代碼少可以,但是代碼一多就不推薦了):
module.exports = {
d: 'hello',
e: 'world',
fn: function () {
fs.readFile(function () {
})
}
}
導出單個成員:(唯一的寫法):
// 導出單個成員:錯誤的寫法
// 因爲每個模塊最終導出是 module.exports 而不是 exports 這個別名
// exports = function (x, y) {
// return x + y
// }
// 導出單個成員:必須這麼寫
module.exports = function (x, y) {
return x + y
}
注意:導出單個只能導出一次,下面的情況後者會覆蓋前者:
module.exports = 'hello'
// 以這個爲準,後者會覆蓋前者
module.exports = function (x, y) {
return x + y
}
爲什麼 exports = xxx
不行
畫圖
exports 和 module.exports
的一個引用:
function fn() {
// 每個模塊內部有一個 module 對象
// module 對象中有一個成員 exports 也是一個對象
var module = {
exports: {}
}
// 模塊中同時還有一個成員 exports 等價於 module.exports
var exports = module.exports
console.log(exports === module.exports) // => true
// 這樣是可以的,因爲 exports === module.exports
// module.exports.a = 123
// exports.b = 456
// 這裏重新賦值不管用,因爲模塊最後 return 的是 module.exports
// exports = function () {
// }
// 這纔是正確的方式
module.exports = function () {
console.log(123)
}
// 最後導出的是 module.exports
return module.exports
}
var ret = fn()
console.log(ret)
exports 和 module.exports 的區別
- exports 和 module.exports 的區別
- 每個模塊中都有一個 module 對象
- module 對象中有一個 exports 對象
- 我們可以把需要導出的成員都掛載到 module.exports 接口對象中
- 也就是:
moudle.exports.xxx = xxx
的方式 - 但是每次都
moudle.exports.xxx = xxx
很麻煩,點兒的太多了 - 所以 Node 爲了你方便,同時在每一個模塊中都提供了一個成員叫:
exports
exports === module.exports
結果爲true
- 所以對於:
moudle.exports.xxx = xxx
的方式 完全可以:expots.xxx = xxx
- 當一個模塊需要導出單個成員的時候,這個時候必須使用:
module.exports = xxx
的方式 - 不要使用
exports = xxx
不管用 - 因爲每個模塊最終向外
return
的是 module.exports - 而
exports
只是module.exports
的一個引用 - 所以即便你爲
exports = xx
重新賦值,也不會影響module.exports
- 但是有一種賦值方式比較特殊:
exports = module.exports
這個用來重新建立引用關係的 - 之所以讓大家明白這個道理,是希望可以更靈活的去用它
特殊的導出方式
exports = module.exports = function () {
console.log('默認函數被調用了')
}
exports.ajax = function () {
console.log('ajax 方法被調用了')
}
exports.get = function () {
console.log('get 方法被調用了')
}
模塊分類
在開始瞭解具體的規則之前,我們先來了解一下在 Node 中對不模塊的一個具體分類,一共就三種類別:
- 核心模塊
- 由 Node 本身提供,具名的,例如 fs 文件操作模塊、http 網絡操作模塊
- 第三方模塊
- 由第三方提供,使用的時候我們需要通過 npm 進行下載然後纔可以加載使用,例如我們使用過的
mime
、art-template
、marked
- 注意:不可能有第三方包的名字和核心模塊的名字是一樣的,否則會造成衝突
- 由第三方提供,使用的時候我們需要通過 npm 進行下載然後纔可以加載使用,例如我們使用過的
- 用戶模塊(自己寫的)
- 我們在文件中寫的代碼很多的情況下不好編寫和維護,所以我們可以考慮把文件中的代碼拆分到多個文件中,那這些我們自己創建的文件就是用戶模塊
核心模塊
- 核心模塊就是 node 內置的模塊,需要通過唯一的標識名稱來進行獲取。
- 每一個核心模塊基本上都是暴露了一個對象,裏面包含一些方法供我們使用
- 一般在加載核心模塊的時候,變量的起名最好就和核心模塊的標識名同名即可
- 例如:
const fs = require('fs')
- 例如:
- 核心模塊本質上也是文件模塊
- 核心模塊已經被編譯到了 node 的可執行程序,一般看不到
- 可以通過查看 node 的源碼看到核心模塊文件
- 核心模塊也是基於 CommonJS 模塊規範
Node 中都以具名的方式提供了不同功能的模塊,例如操作文件就是:fs
核心模塊(系統模塊)由 Node 提供,使用的時候都必須根據特定的核心模塊名稱來加載使用。例如使用文件操作模塊:fs
var fs = require('fs')
// fs.readFile
// fs.writeFile
// fs.appendFile
模塊名稱 | 作用 |
---|---|
fs | 文件操作 |
http | 網絡操作 |
path | 路徑操作 |
url | url 地址操作 |
os | 操作系統信息 |
net | 一種更底層的網絡操作方式 |
querystring | 解析查詢字符串 |
util | 工具函數模塊 |
… | … |
文件模塊
以 ./
或 ../
開頭的模塊標識就是文件模塊,一般就是用戶編寫的。
第三方模塊
- moment
- marked
- …
一般就是通過 npm install
安裝的模塊就是第三方模塊。
加載規則如下:
- 如果不是文件模塊,也不是核心模塊
- node 會去 node_modules 目錄中找(找跟你引用的名稱一樣的目錄),例如這裏
require('underscore')
- 如果在 node_modules 目錄中找到
underscore
目錄,則找該目錄下的package.json
文件 - 如果找到
package.json
文件,則找該文件中的main
屬性,拿到 main 指定的入口模塊 - 如果過程都找不到,node 則取上一級目錄下找
node_modules
目錄,規則同上。。。 - 如果一直找到代碼文件的根路徑還找不到,則報錯。。。
注意:對於第三方模塊,我們都是 npm install
命令進行下載的,就放到項目根目錄下的 node_modules
目錄。
模塊加載機制
簡而言之,如果require絕對路徑的文件,查找時不會去遍歷每一個node_modules目錄,其速度最快。其餘流程如下:
1 . 從module path數組中取出第一個目錄作爲查找基準。
2. 直接從目錄中查找該文件,如果存在,則結束查找。如果不存在,則進行下一條查找。
3. 嘗試添加.js、.json、.node後綴後查找,如果存在文件,則結束查找。如果不存在,則進行下一條。
4. 嘗試將require的參數作爲一個包來進行查找,讀取目錄下的package.json文件,取得main參數指定的文件。
5. 嘗試查找該文件,如果存在,則結束查找。如果不存在,則進行第3條查找。
6. 如果繼續失敗,則取出module path數組中的下一個目錄作爲基準查找,循環第1至5個步驟。
7. 如果繼續失敗,循環第1至6個步驟,直到module path中的最後一個值。
8. 如果仍然失敗,則拋出異常。
整個查找過程十分類似原型鏈的查找和作用域的查找。所幸Node.js對路徑查找實現了緩存機制,否則由於每次判斷路徑都是同步阻塞式進行,會導致嚴重的性能消耗。
相關鏈接
- Node.js 官方文檔
- Node.js 中文文檔(非官方)
- 深入淺出 Node.js
- Node.js 權威指南
- Node.js 實戰
- Node.js 實戰
- Node.js實戰(第2季)
- Node.js 中文社區
- Node.js 包教不包會
- EcmaScript 6 入門
- 七天學會 NodeJS
分享