1. 程式人生 > >Node - 從0基礎到實戰企業官網

Node - 從0基礎到實戰企業官網

Create by jsliang on 2018-11-8 13:42:42
Recently revised in 2018-12-23 21:59:20

Hello 小夥伴們,如果覺得本文還不錯,記得點個贊或者給個 star,你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址

本文重點內容

  • Node 基礎 - 通過對 Node 基礎的瞭解學習,打下 Node 基礎
  • Node API - 開啟服務提供 API 給前端呼叫
  • Node 連線 MySQL - 通過 npm 安裝 mysql,從而實現資料庫的連結
  • Node 實戰 - 企業官網從 0 開始,打造能註冊、登入以及留言的企業官網
  • Node 部署 - 如何通過部署雲伺服器,讓小夥伴們可以檢視到你的網站

本文延伸連結

  • Node 部署專案、雲伺服器以及域名的使用:連結
  • 本文 Node 基礎程式碼下載地址:連結
  • 本文 Node 成品程式碼下載地址:連結

本文成品演示

一 目錄

不折騰的前端,和鹹魚有什麼區別

目錄
一 目錄
二 前言
三 基礎學習
3.1 HTTP - 開始 Node 之旅
3.2 URL 模組
3.3 CommonJS
3.4 包與 npm
3.5 fs 檔案管理
3.6 fs 案例
3.7 fs 流
3.8 建立 Web 伺服器
3.9 非阻塞 I/O 事件驅動
3.10 get 與 post
3.11 Node 連線 MySQL
四 Web 實戰 —— 企業官網
4.1 程式設計環境
4.2 後端介面
4.3 註冊功能
4.4 登入功能
4.5 留言功能
五 工具整合
5.1 supervisor - 監聽 Node 改動
5.2 PM2 - Node 程序管理
六 參考資料
七 線上部署
八 歸納總結

二 前言

返回目錄

 本文主要目的:

  1. 整合 Node 基礎,加深 jsliang 對 Node 的學習瞭解,並且方便日後複習。
  2. 整合 Node 工具,方便查詢在 Node 開發中,有哪些工具比較有利於開發。
  3. 給初學 Node 的小夥伴做一個參考,如有疑問還請在 QQ 群:798961601 中諮詢。

三 基礎

返回目錄

萬丈高樓平地起,地基還得自己起。

3.1 HTTP - 開始 Node 之旅

返回目錄

 話不多說,先上程式碼:

01_http.js

// 1. 引入 http 模組
var http = require("http");

// 2. 用 http 模組建立服務
/**
 * req 獲取 url 資訊 (request)
 * res 瀏覽器返回響應資訊 (response)
 */
http.createServer(function (req, res) {
  // 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });

  // 往頁面列印值
  res.write('<h1 style="text-align:center">Hello NodeJS</h1>');

  // 結束響應
  res.end();

}).listen(3000); // 監聽的埠
複製程式碼

 那麼,上面程式碼,我們要怎麼用呢?

首先,將上面的程式碼複製貼上到 01_http.js 中。
然後,啟動 VS Code 終端:Ctrl + ~
接著,輸入 node 01_http.js 並回車。
最後,開啟 localhost:3000

 OK,搞定完事,現在我們一一講解上面程式碼:

  1. 首先,我們需要先開啟仙人模式。哦,不是,是 HTTP 模式。我們都知道,像 PHP 這類老牌子的後端語言,需要 Apache 或者 Nginx 開啟 HTTP 服務。然而我們的 Node 不需要:
var http = require("http");
複製程式碼
  1. 然後,開啟 HTTP 服務,並設定開啟的埠:
/**
 * req 獲取 url 資訊 (request)
 * res 瀏覽器返回響應資訊 (response)
 */
http.createServer(function (req, res) {
  // ... 步驟 3 程式碼
}).listen(3000); // 監聽的埠
複製程式碼
  1. 接著,我們設定 HTTP 頭部,並往頁面列印值,最後結束響應:
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
  "Content-Type": "text/html;charset=UTF-8"
});

// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');

// 結束響應 
res.end();
複製程式碼
  1. 最後,我們往瀏覽器輸入 http://localhost:3000/,將訪問到我們開啟的 Node 服務,從而往頁面渲染頁面。

 至此,小夥伴們是不是也開啟了自己的 Node 之旅?

3.2 URL 模組

返回目錄

 URL 模組是什麼呢?
 我們在控制檯(終端)開啟 Node 模式,並打印出 url 來看一下:

 好傢伙,它有 UrlparseresolveresolveObjectformatURLURLSearchParamsdomainToASCIIdomainToUnicode 這麼多模組。
 那麼,這些模組都有什麼用呢?

 話不多說,先上程式碼:

02_url.js

// 1. 引入 url 模組
var url = require("url");

// 2. 引入 http 模組
var http = require("http");

// 3. 用 http 模組建立服務
/**
 * req 獲取 url 資訊 (request)
 * res 瀏覽器返回響應資訊 (response)
 */
http.createServer(function (req, res) {

  // 4. 獲取伺服器請求
  /**
   * 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23
   * 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的資訊:
   * /  ?userName=jsliang&userAge=23
   * /  /favicon.ico
   * 這裡為了防止重複執行,所以排除 req.url == /favicon.ico 的情況
   */
  if(req.url != "/favicon.ico") {
    
    // 5. 使用 url 的 parse 方法
    /**
     * parse 方法需要兩個引數:
     * 第一個引數是地址
     * 第二個引數是 true 的話表示把 get 傳值轉換成物件
     */ 
    var result = url.parse(req.url, true);
    console.log(result);
    /**
     * Url {
     *   protocol: null,
     *   slashes: null,
     *   auth: null,
     *   host: null,
     *   port: null,
     *   hostname: null,
     *   hash: null,
     *   search: '?userName=jsliang&userAge=23',
     *   query: { userName: 'jsliang', userAge: '23' },
     *   pathname: '/',
     *   path: '/?userName=jsliang&userAge=23',
     *   href: '/?userName=jsliang&userAge=23' }
     */

    console.log(result.query.userName); // jsliang

    console.log(result.query.userAge); // 23
  }

  // 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });

  // 往頁面列印值
  res.write('<h1 style="text-align:center">Hello NodeJS</h1>');

  // 結束響應
  res.end();

}).listen(3000);
複製程式碼

 在上面的程式碼中:

首先,我們引入該章節的主角 url 模組:

// 1. 引入 url 模組
var url = require("url");
複製程式碼

然後,我們引入 http 模組:

// 2. 引入 http 模組
var http = require("http");
複製程式碼

接著,我們建立 http 模組,因為 url 的監聽,需要 http 模組的開啟:

// 3. 用 http 模組建立服務
/**
 * req 獲取 url 資訊 (request)
 * res 瀏覽器返回響應資訊 (response)
 */
http.createServer(function (req, res) {
  // ... 第 4 步、第 5 步程式碼

  // 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });

  // 往頁面列印值
  res.write('<h1 style="text-align:center">Hello NodeJS</h1>');

  // 結束響應
  res.end();
}).listen(3000);
複製程式碼

最後,我們訪問我們給出的地址:http://localhost:3000/?userName=jsliang&userAge=23,並通過它檢視 urlparse 模組怎麼用,輸出啥:

// 4. 獲取伺服器請求
/**
  * 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23
  * 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的資訊:
  * /  ?userName=jsliang&userAge=23
  * /  /favicon.ico
  * 這裡為了防止重複執行,所以排除 req.url == /favicon.ico 的情況
  */
if(req.url != "/favicon.ico") {
  
  // 5. 使用 url 的 parse 方法
  /**
    * parse 方法需要兩個引數:
    * 第一個引數是地址
    * 第二個引數是 true 的話表示把 get 傳值轉換成物件
    */ 
  var result = url.parse(req.url, true);
  console.log(result);
  /**
    * Url {
    *   protocol: null,
    *   slashes: null,
    *   auth: null,
    *   host: null,
    *   port: null,
    *   hostname: null,
    *   hash: null,
    *   search: '?userName=jsliang&userAge=23',
    *   query: { userName: 'jsliang', userAge: '23' },
    *   pathname: '/',
    *   path: '/?userName=jsliang&userAge=23',
    *   href: '/?userName=jsliang&userAge=23' }
    */

  console.log(result.query.userName); // jsliang

  console.log(result.query.userAge); // 23
}
複製程式碼

 從中,我們可以看出,我們可以通過 query,獲取到我們想要的路徑欄位。

 當然,上面只講解了 parse 的用法,我們可以將上面程式碼中 if 語句裡面的程式碼全部清空。然後,輸入下面的內容,去學習 url 模組更多的內容:

  1. url 模組所有內容:
console.log(url);

/**
 * Console:
 { 
   Url: [Function: Url],
    parse: [Function: urlParse], // 獲取地址資訊
    resolve: [Function: urlResolve], // 追加或者替換地址
    resolveObject: [Function: urlResolveObject],
    format: [Function: urlFormat], // 逆向 parse,根據地址資訊獲取原 url 資訊
    URL: [Function: URL],
    URLSearchParams: [Function: URLSearchParams],
    domainToASCII: [Function: domainToASCII],
    domainToUnicode: [Function: domainToUnicode] 
  }
 */
複製程式碼
  1. parse 如何使用
console.log(url.parse("http://www.baidu.com"));
/**
 * Console:
  Url {
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.baidu.com',
    port: null,
    hostname: 'www.baidu.com',
    hash: null,
    search: null,
    query: null,
    pathname: '/',
    path: '/',
    href: 'http://www.baidu.com/' 
  }
 */
複製程式碼
  1. parse 帶引數:
console.log(url.parse("http://www.baidu.com/new?name=zhangsan"));

/**
 * Console:
  Url {
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.baidu.com',
    port: null,
    hostname: 'www.baidu.com',
    hash: null,
    search: '?name=zhangsan',
    query: 'name=zhangsan',
    pathname: '/new',
    path: '/new?name=zhangsan',
    href: 'http://www.baidu.com/new?name=zhangsan' 
  }
 */
複製程式碼
  1. format 的使用:
console.log(url.format({
  protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'www.baidu.com',
  port: null,
  hostname: 'www.baidu.com',
  hash: null,
  search: '?name=zhangsan',
  query: 'name=zhangsan',
  pathname: '/new',
  path: '/new?name=zhangsan',
  href: 'http://www.baidu.com/new?name=zhangsan' 
}))

// Console:
// http://www.baidu.com/new?name=zhangsan
複製程式碼
  1. resolve 的使用:
console.log(url.resolve("http://www.baidu.com/jsliang", "樑峻榮"));

// Console:
// http://www.baidu.com/樑峻榮
複製程式碼

 當然,url 這裡我們只講解了個入門,更多的還請看官網 API:url | Node.js v10.14.1 文件

3.3 CommonJS

返回目錄

  • 什麼是 CommonJS?

 CommonJS 就是為 JS 的表現來制定規範,因為 JS 沒有模組系統、標準庫較少、缺乏包管理工具,所以 CommonJS 應運而生,它希望 JS 可以在任何地方執行,而不只是在瀏覽器中,從而達到 Java、C#、PHP 這些後端語言具備開發大型應用的能力。

  • CommonJS 的應用?
  1. 伺服器端 JavaScript 應用程式。(Node.js)
  2. 命令列工具
  3. 桌面圖形介面應用程式。
  • CommonJS 與 Node.js 的關係?

 CommonJS 就是模組化的標準,Node.js 就是 CommonJS(模組化)的實現。

  • Node.js 中的模組化?
  1. 在 Node 中,模組分為兩類:一是 Node 提供的模組,稱為核心模組;二是使用者編寫的模組,成為檔案模組。核心模組在 Node 原始碼的編譯過程中,編譯進了二進位制執行檔案,所以它的載入速度是最快的,例如:HTTP 模組、URL 模組、FS 模組;檔案模組是在執行時動態載入的,需要完整的路勁分析、檔案定位、編譯執行過程等……所以它的速度相對核心模組來說會更慢一些。
  2. 我們可以將公共的功能抽離出一個單獨的 JS 檔案存放,然後在需要的情況下,通過 exports 或者 module.exports 將模組匯出,並通過 require 引入這些模組。

 現在,我們通過三種使用方式,來講解下 Node 中的模組化及 exports/require 的使用。

 我們先檢視下目錄:

方法一

 首先,我們新建 03_CommonJS.js03_tool-add.jsnode_modules/03_tool-multiply.jsnode_modules/jsliang-module/tools.js 這 4 個檔案/資料夾。
 其中 package.json 我們暫且不理會,稍後會講解它如何自動生成。

 在 03_tool-add.js 中:

03_tool-add.js

// 1. 假設我們檔案其中有個工具模組
var tools = {
  add: (...numbers) => {
    let sum = 0;
    for (let number in numbers) {
      sum += numbers[number];
    }
    return sum;
  }
}

/**
 * 2. 暴露模組
 * exports.str = str;
 * module.exports = str;
 * 區別:
 * module.exports 是真正的介面
 * exports 是一個輔助工具
 * 如果 module.exports 為空,那麼所有的 exports 收集到的屬性和方法,都賦值給了 module.exports
 * 如果 module.exports 具有任何屬性和方法,則 exports 會被忽略
 */

// exports 使用方法
// var str = "jsliang is very good!";
// exports.str = str; // { str: 'jsliang is very good!' }

// module.exports 使用方法
module.exports = tools;
複製程式碼

 那麼,上面的程式碼有啥含義呢?
 第一步,我們定義了個工具庫 tools
 第二步,我們通過 modules.exportstools 進行了匯出。
 所以,我們在 03_CommonJS.js 可以通過 require 匯入使用:

var http = require("http");

var tools1 = require('./03_tool-add');

http.createServer(function (req, res) {

  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });

  res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
  
  console.log(tools1.add(1, 2, 3));
  /**
   * Console:
   * 6
   * 6
   * 這裡要記得 Node 執行過程中,它請求了兩次,
   * http://localhost:3000/ 為一次,
   * http://localhost:3000/favicon.ico 為第二次
   */
  
  res.end();

}).listen(3000);
複製程式碼

 這樣,我們就完成了 exportsrequire 的初次使用。

方法二

 當我們模組檔案過多的時候,應該需要有個存放這些模組的目錄,Node 就很靠譜,它規範我們可以將這些檔案都放在 node_modules 目錄中(大家都放在這個目錄上,就不會有其他亂七八糟的命名了)。

 所以,我們在 node_modules 中新建一個 03_tool-multiply.js 檔案,其內容如下:

03_tool-multiply.js

var tools = {
  multiply: (...numbers) => {
    let sum = numbers[0];
    for (let number in numbers) {
      sum = sum * numbers[number];
    }
    return sum;
  }
}

module.exports = tools;
複製程式碼

 在引用方面,我們只需要通過:

// 如果 Node 在當前目錄沒找到 tool.js 檔案,則會去 node_modules 裡面去查詢
var tools2 = require('03_tool-multiply');

console.log(tools2.multiply(1, 2, 3, 4));
複製程式碼

 這樣,就可以成功匯入 03_tool-multiply.js 檔案了。

方法三

 如果全部單個檔案丟在 node_modules 上,它會顯得雜亂無章,所以我們應該定義個自己的模組:jsliang-module,然後將我們的 tools.js 存放在該目錄中:

jsliang-module/tools.js

var tools = {
  add: (...numbers) => {
    let sum = 0;
    for (let number in numbers) {
      sum += numbers[number];
    }
    return sum;
  },
  multiply: (...numbers) => {
    let sum = numbers[0];
    for (let number in numbers) {
      sum = sum * numbers[number];
    }
    return sum;
  }
}

module.exports = tools;
複製程式碼

 這樣,我們就定義好了自己的工具庫。
 但是,如果我們通過 var tools3 = require('jsliang-module'); 去匯入,會發現它報 error 了,所以,我們應該在 jsliang-module 目錄下,通過下面命令列生成一個 package.json

PS E:\MyWeb\node_modules\jsliang-module> npm init --yes

 這樣,在 jsliang-module 中就有了 package.json
 而我們在 03_CommonJS.js 就可以引用它了:

03_CommonJS.js

var http = require("http");

var tools1 = require('./03_tool-add');

// 如果 Node 在當前目錄沒找到 tool.js 檔案,則會去 node_modules 裡面去查詢
var tools2 = require('03_tool-multiply');

/**
 * 通過 package.json 來引用檔案
 * 1. 通過在 jsliang-module 中 npm init --yes 來生成 package.json 檔案
 * 2. package.json 檔案中告訴了程式入口檔案為 :"main": "tools.js",
 * 3. Node 通過 require 查詢 jsliang-module,發現它有個 package.json
 * 4. Node 執行 tools.js 檔案
 */
var tools3 = require('jsliang-module');

http.createServer(function (req, res) {

  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });

  res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
  
  console.log(tools1.add(1, 2, 3));
  console.log(tools2.multiply(1, 2, 3, 4));
  console.log(tools3.add(4, 5, 6));
  /**
   * Console:
   * 6
   * 24
   * 15
   * 6
   * 24
   * 15
   * 這裡要記得 Node 執行過程中,它請求了兩次,
   * http://localhost:3000/ 為一次,
   * http://localhost:3000/favicon.ico 為第二次
   */
  
  res.end();

}).listen(3000);
複製程式碼

 到此,我們就通過三種方法,瞭解了各種 exportsrequire 的姿勢以及 Node 模組化的概念啦~

 參考文獻:

3.4 包與 npm

返回目錄

 Node 中除了它自己提供的核心模組之外,還可以自定義模組,以及使用 第三方模組
 Node 中第三方模組由包組成,可以通過包來對一組具有相互依賴關係的模組進行統一管理。

 那麼,假如我們需要使用一些第三方模組,應該去哪找呢?

  1. 百度。百度查詢你需要安裝的第三方模組的對應內容。
  2. npm 官網。如果你已經知道包的名字或者包的作用。那麼,直接在 npm 官網上搜索,想必會更快找到想要安裝的包。

 那麼,npm 是啥?
 npm 是世界上最大的開放原始碼的生態系統。我們可以通過 npm 下載各種各樣的包。
 在我們安裝 Node 的時候,它預設會順帶給你安裝 npm。

  • npm -v:檢視 npm 版本。
  • npm list:檢視當前目錄下都安裝了哪些 npm 包。
  • npm info 模組:檢視該模組的版本及內容。
  • npm i 模組@版本號:安裝該模組的指定版本。

 在平時使用 npm 安裝包的過程中,你可能需要知道一些 npm 基本知識:

  • i/install:安裝。使用 install 或者它的簡寫 i,都表明你想要下載這個包。
  • uninstall:解除安裝。如果你發現這個模組你已經不使用了,那麼可以通過 uninstall 解除安裝它。
  • g:全域性安裝。表明這個包將安裝到你的計算機中,你可以在計算機任何一個位置使用它。
  • --save/-S:通過該種方式安裝的包的名稱及版本號會出現在 package.json 中的 dependencies 中。dependencies 是需要釋出在生成環境的。例如:ElementUI 是部署後還需要的,所以通過 -S 形式來安裝。
  • --save-dev/-D:通過該種方式安裝的包的名稱及版本號會出現在 package.json 中的 devDependencies 中。devDependencies 只在開發環境使用。例如:gulp 只是用來壓縮程式碼、打包的工具,程式執行時並不需要,所以通過 -D 形式來安裝。

 例子:

  • cnpm i webpack-cli -D
  • npm install element-ui -S

 那麼,這麼多的 npm 包,我們通過什麼管理呢?
 答案是 package.json
 如果我們需要建立 package.json,那麼我們只需要在指定的包管理目錄(例如 node_modules)中通過以下命名進行生成:

  • npm init:按步驟建立 package.json
  • npm init --yes:快速建立 package.json

 當然,因為國內網路環境的原因,有些時候通過 npm 下載包,可能會很慢或者直接卡斷,這時候就要安裝淘寶的 npm 映象:cnpm

  • npm install -g cnpm --registry=https://registry.npm.taobao.org

3.5 fs 檔案管理

返回目錄

 本章節我們講解下 fs 檔案管理:

如需快速找到下面某個內容,請使用 Ctrl + F

  1. fs.stat 檢測是檔案還是目錄
  2. fs.mkdir 建立目錄
  3. fs.writeFile 建立寫入檔案
  4. fs.appendFile 追加檔案
  5. fs.readFile 讀取檔案
  6. fs.readdir 讀取目錄
  7. fs.rename 重新命名
  8. fs.rmdir 刪除目錄
  9. fs.unlink 刪除檔案

此章節檔案目錄:

首先,我們通過 fs.stat 檢查一個讀取的是檔案還是目錄:

05_fs.js

//  1. fs.stat
let fs = require('fs');
fs.stat('index.js', (error, stats) => {
  if(error) {
    console.log(error);
    return false;
  } else {
    console.log(stats);
    /**
     * Console:
     * Stats {
     *  dev: 886875,
     *  mode: 33206,
     *  nlink: 1,
     *  uid: 0,
     *  gid: 0,
     *  rdev: 0,
     *  blksize: undefined,
     *  ino: 844424931461390,
     *  size: 284,
     *  blocks: undefined,
     *  atimeMs: 1542847157494,
     *  mtimeMs: 1543887546361.2158,
     *  ctimeMs: 1543887546361.2158,
     *  birthtimeMs: 1542847157493.663,
     *  atime: 2018-11-22T00:39:17.494Z,
     *  mtime: 2018-12-04T01:39:06.361Z,
     *  ctime: 2018-12-04T01:39:06.361Z,
     *  birthtime: 2018-11-22T00:39:17.494Z }
     */

    console.log(`檔案:${stats.isFile()}`); 
    // Console:檔案:true
    
    console.log(`目錄:${stats.isDirectory()}`); 
    // Console:目錄:false

    return false;
  }
})
複製程式碼

 通過 Console 打印出來的資訊,我們基礎掌握了 fs.stat 的作用。

然後,我們嘗試通過 fs.mkdir 建立目錄:

05_fs.js

//  2. fs.mkdir
let fs = require('fs');

/**
 * 接收引數
 * path - 將建立的目錄路徑
 * mode - 目錄許可權(讀寫許可權),預設 0777
 * callback - 回撥,傳遞異常引數 err
 */
fs.mkdir('css', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("建立目錄成功!");
    // Console:建立目錄成功!
  }
})
複製程式碼

 通過 node 05_fs.js,我們發現目錄中多了一個 css 資料夾。

那麼,有建立就有刪除,建立的目錄如何刪除呢?這裡講解下 fs.rmdir

05_fs.js

//  8. fs.rmdir
let fs = require('fs');

/**
 * 接收引數
 * path - 將建立的目錄路徑
 * mode - 目錄許可權(讀寫許可權),預設 0777
 * callback - 回撥,傳遞異常引數 err
 */
fs.rmdir('css', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("建立目錄成功!");
    // Console:建立目錄成功!
  }
})
複製程式碼

 通過 node 05_fs.js,我們發現目錄中的 css 資料夾被刪除了。

接著,我們通過 fs.writeFile 來建立寫入檔案:

05_fs.js

//  3. fs.writeFile
let fs = require('fs');

/**
 * filename (String) 檔名稱
 * data (String | Buffer) 將要寫入的內容,可以是字串或者 buffer 資料。
 * · encoding (String) 可選。預設 'utf-8',當 data 是 buffer 時,該值應該為 ignored。
 * · mode (Number) 檔案讀寫許可權,預設 438。
 * · flag (String) 預設值 'w'。
 * callback { Function } 回撥,傳遞一個異常引數 err。
 */
fs.writeFile('index.js', 'Hello jsliang', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log('寫入成功!');
  }
})
複製程式碼

 值得注意的是,這樣的寫入,是清空原檔案中的所有資料,然後新增 Hello jsliang 這句話。即:存在即覆蓋,不存在即建立。

 有建立就有刪除,感興趣的可以使用 fs.unlink 進行檔案的刪除,再次不做過多講解。

既然,上面的是覆蓋檔案,那麼有沒有追加檔案呢?有的,使用 fs.appendFile 吧:

05_fs.js

//  4. fs.appendFile
let fs = require('fs');

fs.appendFile('index.js', '這段文字是要追加的內容', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("追加成功");
  }
})
複製程式碼

 這樣,我們就成功往裡面追加了一段話,從而使 index.js 變成了:

index.js

Hello jsliang這段文字是要追加的內容
複製程式碼

在上面,我們已經做了:新增、修改、刪除操作。那麼小夥伴一定很熟悉下一步驟是做什麼了:

  • fs.readFile 讀取檔案
  • fs.readdir 讀取目錄

05_fs.js

let fs = require('fs');

// 5. fs.readFile
fs.readFile('index.js', (err, data) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("讀取檔案成功!");
    console.log(data);
    // Console:
    // 讀取檔案成功!
    // <Buffer 48 65 6c 6c 6f 20 6a 73 6c 69 61 6e 67 e8 bf 99 e6 ae b5 e6 96 87 e6 9c ac e6 98 af e8 a6 81 e8 bf bd e5 8a a0 e7 9a 84 e5 86 85 e5 ae b9>
  }
})

// 6. fs.readdir 讀取目錄
fs.readdir('node_modules', (err, data) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("讀取目錄成功!");
    console.log(data);
    // Console:
    // 讀取目錄成功!
    // [ '03_tool-multiply.js', 'jsliang-module' ]
  }
})
複製程式碼

 如上,我們成功做到了讀取檔案和讀取目錄。

最後,我們再回顧一開始的目標:

1. fs.stat 檢測是檔案還是目錄
2. fs.mkdir 建立目錄
3. fs.writeFile 建立寫入檔案
4. fs.appendFile 追加檔案
5. fs.readFile 讀取檔案
6. fs.readdir 讀取目錄
7. fs.rename 重新命名
8. fs.rmdir 刪除目錄
9. fs.unlink 刪除檔案

 很好,我們就剩下重新命名了:

05_fs.js

let fs = require('fs');

// 7. fs.rename 重新命名
fs.rename('index.js', 'jsliang.js', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("重新命名成功!");
  }
})
複製程式碼

 當然,如果 fs.rename 還有更勁爆的功能:剪下

05_fs.js

let fs = require('fs');

// 7. fs.rename 重新命名
fs.rename('jsliang.js', 'node_modules/jsliang.js', (err) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    console.log("剪下成功!");
  }
})
複製程式碼

 OK,通通搞定,現在目錄變成了:

3.6 fs 案例

返回目錄

 在上一章節中,我們瞭解了 fs 的檔案管理。
 那麼,在這裡,我們嘗試使用 fs 做點小事情:

06_fsDemo.js

/**
 * 1. fs.stat 檢測是檔案還是目錄
 * 2. fs.mkdir 建立目錄
 * 3. fs.writeFile 建立寫入檔案
 * 4. fs.appendFile 追加檔案
 * 5. fs.readFile 讀取檔案
 * 6. fs.readdir 讀取目錄
 * 7. fs.rename 重新命名
 * 8. fs.rmdir 刪除目錄
 * 9. fs.unlink 刪除檔案
 */

// 1. 判斷伺服器上面有沒有 upload 目錄,沒有就建立這個目錄
// 2. 找出 html 目錄下面的所有的目錄,然後打印出來

let fs = require('fs');

// 圖片上傳
fs.stat('upload', (err, stats) => {
  // 判斷有沒有 upload 目錄
  if(err) {
    // 如果沒有
    fs.mkdir('upload', (error) => {
      if(error) {
        console.log(error);
        return false;
      } else {
        console.log("建立 upload 目錄成功!");
      }
    })
  } else {
    // 如果有
    console.log(stats.isDirectory());
    console.log("有 upload 目錄,你可以做更多操作!");
  }
})

// 讀取目錄全部檔案
fs.readdir('node_modules', (err, files) => {
  if(err) {
    console.log(err);
    return false;
  } else {
    // 判斷是目錄還是資料夾
    console.log(files);

    let filesArr = [];

    (function getFile(i) {
      
      // 迴圈結束
      if(i == files.length) {
        // 打印出所有目錄
        console.log("目錄:");
        console.log(filesArr);
        return false;
      }

      // 判斷目錄是檔案還是資料夾
      fs.stat('node_modules/' + files[i], (error, stats) => {

        if(stats.isDirectory()) {
          filesArr.push(files[i]);
        }

        // 遞迴呼叫
        getFile(i+1);
        
      })
    })(0)
  }
})
複製程式碼

3.7 fs 流

返回目錄

 話不多說,我們瞭解下 fs 流及其讀取:

// 新建 fs
const fs = require('fs');
// 流的方式讀取檔案
let fileReadStream = fs.createReadStream('index.js');
// 讀取次數
let count = 0;
// 儲存資料
let str = '';
// 開始讀取
fileReadStream.on('data', (chunk) => {
  console.log(`${++count} 接收到:${chunk.length}`);
  // Console:1 接收到:30
  str += chunk;
})
// 讀取完成
fileReadStream.on('end', () => {
  console.log("——結束——");
  console.log(count);
  console.log(str);

  // Console:——結束——
  // 1
  // console.log("Hello World!");
})
// 讀取失敗
fileReadStream.on('error', (error) => {
  console.log(error);
})
複製程式碼

 在這裡,我們通過 fs 模組的 createReadStream 建立了讀取流,然後讀取檔案 index.js,從而最後在控制檯輸出了:

1 接收到:259
——結束——
1
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
複製程式碼

 其中 console.log() 那三行就是 index.js 的文字內容。

 然後,我們試下流的存入:

let fs = require('fs');
let data = 'console.log("Hello World! 我要存入資料!")';

// 建立一個可以寫入的流,寫入到檔案 index.js 中
let writeStream = fs.createWriteStream('index.js');
// 開始寫入
writeStream.write(data, 'utf8');
// 寫入完成
writeStream.end();
writeStream.on('finish', () => {
  console.log('寫入完成!');
  // Console:寫入完成
});
複製程式碼

 我們開啟 index.js,會發現裡面的內容變成了 console.log("Hello World! 我要存入資料!"),依次,我們通過流的形式進行了讀取和寫入的操作。

3.8 建立 Web 伺服器

返回目錄

 在這裡,我們利用 http 模組、url 模組、path 模組、fs 模組建立一個 Web 伺服器。

 什麼是 Web 伺服器?
 Web 伺服器一般指網站伺服器,是指駐留於因特網上某種型別計算機的程式,可以像瀏覽器等 Web 客戶端提供文件,也可以放置網站檔案,讓全世界瀏覽;可以放置資料檔案,讓全世界下載。目前最主流的三個 Web 伺服器是 Apache、Nginx、IIS。

 下面,我們使用 Node 來建立一個 Web 服務:

08_WebService.js

// 引入 http 模組
let http = require("http");

// 引入 fs 模組
let fs = require("fs");

http.createServer((req, res) => {
  // 獲取響應路徑
  let pathName = req.url;

  // 預設載入路徑
  if (pathName == "/") {
    // 預設載入的首頁
    pathName = "index.html";
  }

  // 過濾 /favicon.ico 的請求
  if (pathName != "/favicon.ico") {
    // 獲取 08_WebService 下的 index.html
    fs.readFile("./08_WebService/" + pathName, (err, data) => {
      if (err) {
        
        // 如果不存在這個檔案
        
        console.log("404 Not Found!");
        fs.readFile('./08_WebService/404.html', (errorNotFound, dataNotFound) => {
          if(errorNotFound) {
            console.log(errorNotFound);
          } else {
            res.writeHead(200, {
              "Content-Type": "text/html; charset='utf-8'"
            });
            // 讀取寫入檔案
            res.write(dataNotFound);
            // 結束響應
            res.end();
          }
        })
        return;
      } else {

        // 返回這個檔案
        
        // 設定請求頭
        res.writeHead(200, {
          "Content-Type": "text/html; charset='utf-8'"
        });
        // 讀取寫入檔案
        res.write(data);
        // 結束響應
        res.end();
      }
    });
  }
}).listen(8080);
複製程式碼

 這樣,我們在瀏覽器輸入 localhost:8080 即可以看到:

 好傢伙,感情它就載入了整個 index.html 檔案,連 CSS 這些沒引入麼?
 所以,下一步,我們要動態載入 htmlcss 以及 js

08_WebService.js

// 引入 http 模組
let http = require("http");

// 引入 fs 模組
let fs = require("fs");

// 引入 url 模組
let url = require("url");

// 引入 path 模組
let path = require("path");

http.createServer((req, res) => {
  
  // 獲取響應路徑
  let pathName = url.parse(req.url).pathname;

  // 預設載入路徑
  if (pathName == "/") {
    // 預設載入的首頁
    pathName = "index.html";
  }

  // 獲取檔案的字尾名
  let extName = path.extname(pathName);

  // 過濾 /favicon.ico 的請求
  if (pathName != "/favicon.ico") {
    // 獲取 08_WebService 下的 index.html
    fs.readFile("./08_WebService/" + pathName, (err, data) => {
      // 如果不存在這個檔案
      if (err) {
        console.log("404 Not Found!");
        fs.readFile(
          "./08_WebService/404.html",
          (errorNotFound, dataNotFound) => {
            if (errorNotFound) {
              console.log(errorNotFound);
            } else {
              res.writeHead(200, {
                "Content-Type": "text/html; charset='utf-8'"
              });
              // 讀取寫入檔案
              res.write(dataNotFound);
              // 結束響應
              res.end();
            }
          }
        );
        return;
      }
      // 返回這個檔案
      else {
        // 獲取檔案型別
        let ext = getExt(extName);

        // 設定請求頭
        res.writeHead(200, {
          "Content-Type": ext + "; charset='utf-8'"
        });
        // 讀取寫入檔案
        res.write(data);
        // 結束響應
        res.end();
      }
    });
  }
}).listen(8080);

// 獲取字尾名
getExt = (extName) => {
  switch(extName) {
    case '.html': return 'text/html';
    case '.css': return 'text/css';
    case '.js': return 'text/js';
    default: return 'text/html';
  }
}
複製程式碼

 這樣,當我們再次請求的時候,瀏覽器就變成了:

 當然,在上面,我們僅僅模擬了 htmlcssjs 這三種檔案型別而已,我們需要模擬更多的檔案型別:

08_ext.json

 程式碼詳情請點選上面的連結
複製程式碼

 在上面的 json 檔案中,我們定義了各種的檔案型別,此刻檔案目錄如下所示:

 這時候,我們需要修改下我們的 js 檔案,讓它適應多種請求響應了:

08_WebService.js

// 引入 http 模組
let http = require("http");

// 引入 fs 模組
let fs = require("fs");

// 引入 url 模組
let url = require("url");

// 引入 path 模組
let path = require("path");

http.createServer((req, res) => {
  
  // 獲取響應路徑
  let pathName = url.parse(req.url).pathname;

  // 預設載入路徑
  if (pathName == "/") {
    // 預設載入的首頁
    pathName = "index.html";
  }

  // 獲取檔案的字尾名
  let extName = path.extname(pathName);

  // 過濾 /favicon.ico 的請求
  if (pathName != "/favicon.ico") {
    // 獲取 08_WebService 下的 index.html
    fs.readFile("./08_WebService/" + pathName, (err, data) => {
      // 如果不存在這個檔案
      if (err) {
        console.log("404 Not Found!");
        fs.readFile(
          "./08_WebService/404.html",
          (errorNotFound, dataNotFound) => {
            if (errorNotFound) {
              console.log(errorNotFound);
            } else {
              res.writeHead(200, {
                "Content-Type": "text/html; charset='utf-8'"
              });
              // 讀取寫入檔案
              res.write(dataNotFound);
              // 結束響應
              res.end();
            }
          }
        );
        return;
      }
      // 返回這個檔案
      else {
        // 獲取檔案型別
        let ext = getExt(extName);
        console.log(ext);

        // 設定請求頭
        res.writeHead(200, {
          "Content-Type": ext + "; charset='utf-8'"
        });
        // 讀取寫入檔案
        res.write(data);
        // 結束響應
        res.end();
      }
    });
  }
}).listen(8080);

// 獲取字尾名
getExt = (extName) => {
  // readFile 是非同步操作,所以需要使用 readFileSync
  let data = fs.readFileSync('./08_ext.json');
  let ext = JSON.parse(data.toString());
  return ext[extName];
}
複製程式碼

 如此,我們做了個簡單的 Web 伺服器。

3.9 非阻塞 I/O 事件驅動

返回目錄