1、node.js 介紹
node.js 是什麼?
1. node.js 是一個開發平臺,就像Java開發平臺、.Net開發平臺、PHP開發平臺、Apple開發平臺一樣。- 何爲開發平臺?有對應的編程語言、有語言運行時、有能實現特定功能的API(SDK:Software Development Kit)
2. 該平臺使用的編程語言是 JavaScript 語言。
3. node.js 平臺是基於 Chrome V8 JavaScript 引擎構建。
4. 基於 node.js 可以開發控制檯程序(命令行程序、CLI程序)、桌面應用程序(GUI)(藉助 node-webkit、electron 等框架實現)、Web 應用程序(網站)
node.js 全棧開發技術棧: MEAN - MongoDB Express Angular Node.js
node.js 有哪些特點?
1. 事件驅動(當事件被觸發時,執行傳遞過去的回調函數)2. 非阻塞 I/O 模型(當執行I/O操作時,不會阻塞線程)
3. 單線程
4. 擁有世界最大的開源庫生態系統 —— npm。
node.js 網站
1. [node.js官方網站](https://nodejs.org/)2. [node.js中文網](http://nodejs.cn/)
3. [node.js 中文社區](https://cnodejs.org/)
爲什麼要學習Node.js?
1. 通過學習Node.js開發深入理解**服務器開發**、**Web請求和響應過程**、 **瞭解服務器端如何與客戶端配合**2. 學習服務器端渲染
3. 學習服務器端爲客戶端編寫接口
4. 工作需求,對 Node.js 開發有要求
5. 提問:
- 在Node.js平臺開發時,能使用Dom API嗎?比如:`document.getElementById('id'); window.location 等`?
6. 複習 瀏覽器端 JavaScript 組成:ECMAscript、Dom、Bom
Node.js安裝和配置
1. 下載地址+ [當前版本](https://nodejs.org/en/download/)
+ [歷史版本](https://nodejs.org/en/download/releases/)
2. 官網術語解釋
+ LTS 版本:Long-term Support 版本,長期支持版,即穩定版。
+ Current 版本:Latest Features 版本,最新版本,新特性會在該版本中最先加入。
3. 注意:
+ 安裝完畢後通過命令:`node -v`來確定是否安裝成功【注意:打開"命令窗口"的時候建議使用"管理員方式"打開】
+ 如果需要則配置環境變量。
![配置環境變量](imgs/env_path.png)
4. 通過 nvm-windows 管理一臺計算機上的多個 node 版本
Node.js 開發 Web 應用程序 和 PHP、Java、ASP.Net等傳統模式開發Web應用程序區別
1. **傳統模式**
- 有 Web 容器(通過中間件服務器監聽8080端口)
2. **Node.js開發Web應用程序**
- 沒有 Web 容器
2、 在 node.js 上編寫程序
REPL介紹
1. REPL 全稱: Read-Eval-Print-Loop(交互式解釋器)- R 讀取 - 讀取用戶輸入,解析輸入了Javascript 數據結構並存儲在內存中。
- E 執行 - 執行輸入的數據結構
- P 打印 - 輸出結果
- L 循環 - 循環操作以上步驟直到用戶兩次按下 ctrl-c 按鈕退出。
2. 在REPL中編寫程序 (類似於瀏覽器開發人員工具中的控制檯功能)
+ 直接在控制檯輸入 `node` 命令進入 REPL 環境
3. 按兩次 Control + C 退出REPL界面 或者 輸入 `.exit` 退出 REPL 界面
+ 按住 control 鍵不要放開, 然後按兩下 c 鍵
創建 JavaScript 文件編寫程序
編程注意事項+ 配置一下Sublime Text 的代碼縮進格式爲2個空格
JavaScript 文件名命名規則
+ 不要用中文
+ 不要包含空格
+ 不要出現node關鍵字
+ 建議以 '-' 分割單詞
3、案例
1. 案例1:編寫一個簡單的函數, 實現數字相加```javascript
var n = 10;
var m = 100;
function add(x, y) {
return x + y;
}
var result = add(m, n);
console.log('計算結果是:' + result);
```
2. 案例2:編寫一個輸出'三角形'的程序```javascript
// process 對象是一個 global (全局變量),提供有關信息,控制當前 Node.js 進程。
// 作爲一個對象,它對於 Node.js 應用程序始終是可用的,故無需使用 require()。
for (var i = 0; i < 10; i++) {
for (var j = 0; j <= i; j++) {
// 注意:console.log()輸出完畢後是帶換行的,所以這樣做不可以
// console.log('*');
process.stdout.write('* ');
}
process.stdout.write('\n');
}
```
3. 案例3:文件讀寫案例(api : https://nodejs.org/dist/latest-v8.x/docs/api/ ) - 使用到的模塊`var fs = require('fs');`
- 1、寫文件:`fs.writeFile(file, data[, options], callback);`
+ 參數1:要寫入的文件路徑,**必填**。
+ 參數2:要寫入的數據,**必填**。
+ 參數3:寫入文件時的選項,比如:文件編碼,選填。
+ 參數4:文件寫入完畢後的回調函數,**必填**。
+ 寫文件注意:
* 該操作採用異步執行(異步執行:方法進棧以後,io操作會被瀏覽器api調用,執行完進入Queue隊列,在執行完全部函數以後,就是主函數退出棧,自動執行callback回調函數,進入棧執行,彈棧結束函數)
* 如果文件已經存在則替換掉
* 默認寫入的文件編碼爲utf8
* 回調函數有1個參數:err,表示在寫入文件的操作過程中是否出錯了。
- 如果出錯了`err != null`,否則 `err === null`
```javascript
// --------------------------------- 寫文件 -----------------------------
// 加載文件操作模塊
var fs = require('fs');
// 創建要寫入的文件的字符串
var msg = '你好,世界!你好 Node.js.';
// 執行文件寫入操作
fs.writeFile('./data.txt', msg, 'utf8', function (err) {
console.log('---' + err + '----');
// /判斷是否寫入文件出錯了
if (err) {
console.log('文件寫入出錯了,詳細錯誤信息:' + err);
// 把錯誤繼續向上拋出
throw err;
} else {
console.log('文件寫入成功!');
}
});
- 2、讀文件:`fs.readFile(file[, options], callback)`
+ 參數1:要讀取的文件路徑,**必填**。
+ 參數2:讀取文件時的選項,比如:文件編碼。選填。
+ 參數3:文件讀取完畢後的回調函數,**必填**。
+ 讀文件注意:
- 該操作採用異步執行
- 回調函數有兩個參數,分別是err和data
- 如果讀取文件時沒有指定編碼,那麼返回的將是原生的二進制數據;如果指定了編碼,那麼會根據指定的編碼返回對應的字符串數據。
- 注意:
+ 文件操作中的`./`表示當前路徑,相對的是執行node命令的路徑,而不是當前被執行的`*.js`文件的實際路徑。
+ `__dirname`才永遠表示當前被執行的`*.js`文件的實際路徑
使用__dirname,表示當前正在執行的js文件所在目錄
__filename:表示當前正在執行的js文件的完整路徑(絕對路徑)(目錄+文件名)
// --------------------------------- 讀文件 -----------------------------
// 加載文件操作模塊
var fs = require('fs');
// 執行文件讀取操作
fs.readFile('./data.txt', 'utf8', function (err, data) {
// 輸出err 和 data
// console.log('error: ' + err);
// console.log('data: ' + data);
if (err) {
console.log('文件讀取出錯啦!詳細信息: ' + err);
} else {
console.log('文件讀取成功,以下是文件內容:');
console.log(data);
}
});
```
4. 案例4:創建目錄案例```javascript
// 創建一個文件夾
// 加載文件操作模塊
var fs = require('fs');
// 創建一個目錄
fs.mkdir('./test-mkdir', function (err) {
if (err) {
console.log('創建目錄出錯了,詳細信息如下:');
console.log(err);
} else {
console.log('目錄創建成功!');
}
});
注意:1. 異步操作無法通過 try-catch 來捕獲異常,要通過判斷 error 來判斷是否出錯。
2. 同步操作可以通過 try-catch 來捕獲異常。
3. 不要使用 `fs.exists(path, callback)` 來判斷文件是否存在,直接判斷 error 即可
4. 文件操作時的路徑問題
- 在讀寫文件的時候 './' 表示的是當前執行node命令的那個路徑,不是被執行的js文件的路徑
- __dirname, 表示的永遠是"當前被執行的js的目錄"
- __filename, 表示的是"被執行的js的文件名(含路徑)"
5. error-first 介紹(錯誤優先)
案例5:通過 node.js 編寫 http 服務程序 - 極簡版本(參考api:https://nodejs.org/dist/latest-v8.x/docs/api/http.html)
步驟:
1. 加載http模塊
2. 創建http服務
3. 爲http服務對象添加 request 事件處理程序
4. 開啓http服務監聽,準備接收客戶端請求
注意:
1. 瀏覽器顯示可能是亂碼,所以可以通過 `res.setHeader('Content-Type', 'text/plain; charset=utf-8');`設置瀏覽器顯示時所使用的編碼。
2. Chrome 瀏覽器默認無法手動設置編碼,需要安裝"Set Character Encoding"擴展。
3. 設置`Content-Type=text/html` 和 `Content-Type=text/plain`的區別。
```javascript
// 1. 加載http模塊
var http = require('http');
// 2. 創建http服務
var server = http.createServer();
// 3. 開始監聽'request'事件
// 詳細解釋一下request對象和response對象
server.on('request', function (req, res) {
// body...
console.log('有人請求了~~');
});
// 4. 啓動服務,開始監聽
server.listen(9000, function () {
console.log('服務已經啓動,請訪問: http://localhost:9000');
});
```
案例6:通過 node.js 編寫 http 服務程序 - 根據不同請求作出不同響應 說明:
- 根據不同請求,顯示index頁面、login頁面、register頁面、list頁面。
- 請求 / 或 /index
- 請求 /login
- 請求 /register
- 請求 /list
#### 參考代碼```javascript
// 加載http模塊
var http = require('http');
// 創建http server
var server = http.createServer(function (req, res) {
// body...
console.log(req.url);
if (req.url === '/' || req.url === '/index') {
// 表示請求網站首頁
res.end('這是 index.html');
} else if (req.url === '/login') {
// 表示請求登錄頁面
res.end('這是 login.html');
} else if (req.url === '/register') {
// 表示請求註冊頁面
res.end('這是 register.html');
} else if (req.url === '/list') {
// 表示請求列表頁面
res.end('這是 list.html');
} else {
// 表示請求的頁面不存在
res.writeHead(404, 'Not Found');
res.end('Sorry, page not found.');
}
});
// 監聽端口的網絡請求
server.listen(9000, function () {
console.log('http://localhost:9000');
});
```
案例7:通過 node.js 編寫 http 服務程序 - 通過讀取靜態 HTML 文件來響應用戶請求
步驟:
1. 創建index.html、login.html、register.html、list.html、404.html文件。
2. 演示通過讀取最簡單的 HTML 文件來響應用戶。
3. 演示通過讀取"具有引入外部CSS樣式表"的HTML文件來響應用戶。
4. 演示通過讀取"具有img標籤"的HTML文件來響應用戶。
注意:
- 1、注意在發送不同類型的文件時,要設置好對應的`Content-Type`
+ [Content-Type參考 OSChina](http://tool.oschina.net/commons)
+ [Content-Type參考 MDN]
(https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
- 2、HTTP狀態碼參考+ [w3org參考](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
+ [w3schools參考](https://www.w3schools.com/tags/ref_httpmessages.asp)
- 3、在html頁面中寫相對路徑'./' 和 絕對路徑 '/'的含義 。
+ 網頁中的這個路徑主要是告訴瀏覽器向哪個地址發起請求用的
+ './' 表示本次請求從相對於當前頁面的請求路徑(即服務器返回當前頁面時的請求路徑)開始
+ '/' 表示請求從根目錄開始
補充知識點:
1. path 模塊的 join() 方法(設置路徑)(var filename = path.join(__dirname,'hello.txt');)
參考代碼:
```javascript
// 1. 加載 http 模塊
var http = require('http');
// 加載文件操作模塊
var fs = require('fs');
// 加載path模塊,這個模塊主要用來處理各種路徑。
var path = require('path');
// 2. 創建http server
var server = http.createServer(function (req, res) {
// 1. 獲取用戶請求的URL
var url = req.url.toLowerCase();
// 2. 根據用戶的不同請求,做出不同響應
if (url === '/' || url === '/index') {
// 讀取index.html文件,把該文件響應給用戶
fs.readFile(path.join(__dirname, 'index.html'), function (err, data) {
if (err) {
throw err;
}
res.writeHead(200, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data);
});
} else if (url === '/login') {
// 讀取login.html文件,把該文件響應給用戶
fs.readFile(path.join(__dirname, 'login.html'), function (err, data) {
if (err) {
throw err;
}
res.writeHead(200, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data);
});
} else if (url === '/register') {
// 讀取register.html文件,把該文件響應給用戶
fs.readFile(path.join(__dirname, 'register.html'), function (err, data) {
if (err) {
throw err;
}
res.writeHead(200, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data);
});
} else if (url === '/404') {
// 讀取register.html文件,把該文件響應給用戶
fs.readFile(path.join(__dirname, '404.html'), function (err, data) {
if (err) {
throw err;
}
res.writeHead(200, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data);
});
}
});
// 3. 啓動服務
server.listen(9090, function () {
// body...
console.log('please visit: http://localhost:9090');
});
```
案例8:模擬 Apache 實現靜態資源服務器
步驟:
- 單獨創建一個目錄來實現,比如:創建一個"07-Apache"的目錄。
- 在該目錄下新建 `public` 目錄,假設該目錄爲靜態資源目錄。
- 根據用戶請求的路徑在 public 目錄下尋找對應路徑下的資源。
- 如果找到了,那麼將該資源返回給用戶,如果沒找到則返回404錯誤。
- 通過 mime 模塊設置不同類型資源的Content-Type
- 實現完畢後把素材中的'An Ocean of Sky' 和 'Hacker News'分別拷貝到靜態資源目錄下, 測試是否成功
其他:
- 介紹 NPM
- 介紹 mime 第三方模塊
+ `npm install mime`
+ 在代碼中直接 `var mime = require('mime')`
參考代碼:
```javascript
// 1. 加載對應模塊
// 1.1 加載http模塊
var http = require('http');
// 1.2 加載path模塊,方便路徑拼接
var path = require('path');
// 1.3 加載文件讀取模塊
var fs = require('fs');
// 1.4 加載判斷文件MIME類型的模塊
var mime = require('mime');
// 2. 創建http server
var server = http.createServer();
// 3. 監聽用戶request事件
server.on('request', function (req, res) {
// 1. 獲取用戶的請求路徑, 並轉換爲小寫
var url = req.url.toLowerCase();
// 判斷如果請求的路徑是 '/' 那麼等價於 '/index.html'
url = (url === '/') ? '/index.html' : url;
// 2. 根據用戶請求的url路徑, 去public目錄下查找對應的靜態資源文件。找到後讀取該文件,並將結果返回給用戶
// 2.1 根據用戶請求的url拼接本地資源文件的路徑
var filePath = path.join(__dirname, 'public', url);
// 2.2 根據請求的文件路徑設置Content-Type
res.setHeader('Content-Type', mime.lookup(url));
// 2.2 根據路徑去讀取對應的文件
// 【注意】讀取文件前無需判斷文件是否已經存在,而是在讀取文件的回調函數中根據error的錯誤信息來判斷讀取文件是否成功以及發生的錯誤
fs.readFile(filePath, function (err, data) {
// 判斷是否有錯誤
if (err) {
if (err.code === 'ENOENT') { // 判斷是否是請求的文件是否不存在
res.setHeader('Content-Type', 'text/html; charset=utf8');
res.statusCode = 404;
res.statusMessage = 'Not Found';
res.end('<h1>請求的資源不存在!</h1>');
} else if (err.code === 'EACCES') { // 判斷文件是否有訪問權限
res.setHeader('Content-Type', 'text/html; charset=utf8');
res.statusCode = 403;
res.statusMessage = 'Forbidden';
res.end('<h1>Permission denied!</h1>');
} else {
throw err;
}
} else {
// 如果沒有錯誤則將讀取到的文件返回給用戶
res.statusCode = 200;
res.statusMessage = 'OK';
res.end(data);
}
})
});
// 4. 啓動服務
server.listen(9000, function () {
// body...
console.log('server is running, please visit: http://localhost:9000');
});
```
Common System Errors - 常見錯誤號
- EACCES (Permission denied)
+ An attempt was made to access a file in a way forbidden by its file access permissions.
+ 訪問被拒絕
- EADDRINUSE (Address already in use)
+ An attempt to bind a server (net, http, or https) to a local address failed due to another server on the local system already occupying that address.
+ 地址正在被使用(比如:端口號備佔用)
- EEXIST (File exists)
+ An existing file was the target of an operation that required that the target not exist.
+ 文件已經存在
- EISDIR (Is a directory)
+ An operation expected a file, but the given pathname was a directory.
+ 給定的路徑是目錄
- ENOENT (No such file or directory)
+ Commonly raised by fs operations to indicate that a component of the specified pathname does not exist -- no entity (file or directory) could be found by the given path.
+ 文件 或 目錄不存在
- ENOTDIR (Not a directory)
+ A component of the given pathname existed, but was not a directory as expected. Commonly raised by fs.readdir.
+ 給定的路徑不是目錄
同步文件操作 和 異步文件操作
- `fs.readFile(file[, options], callback)`
- `fs.readFileSync(file[, options])`
通過設置 http 響應報文頭實現彈框下載功能
1. 設置 `Content-Type: application/octet-stream`
2. 設置 `Content-Disposition: attachment; filename=demo.txt`