nodejs小結(1)

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文件的完整路徑(絕對路徑)(目錄+文件名)

    +  `/`表示根目錄, 讀取文件或寫入文件的時候寫`/`目錄,在Windows下相當於當前磁盤根目錄(比如:c:\ 或 d:\ 或 e:\  等,在Mac下相當於硬盤根目錄 `/`)
// --------------------------------- 讀文件 -----------------------------
// 加載文件操作模塊
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`


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章