有助於理解前端工具的 node 知識

緣起

平時寫慣了業務代碼以後,若是想要了解下 webpack 或者 vue-cli,好像是件很難上手的事情🙁 。拿 webpack 來講,咱們可能會對配置熟悉點,但經常一段時間事後又忘了,感受看起來不是很好懂。其實相似這種打包工具、構建工具咱們最好應該先去學習一下 node 的一些基礎知識,而後再回過頭來看這些工具,就會有柳暗花明又一村的感受,由於這些工具是用 node 寫出來的🤯。
想一想咱們是否是時常看到過這種東西:const path = require('path');。假設你學過前端框架但沒學過 node,你看到這句話的時候就會一頭霧水,好像知道它是弄路徑的,但具體這是哪裏來的,經常使用來作什麼就不得而知了,我起初看的感受就是這樣🤨。
後來才知道這實際上是 node 的內置模塊,由於這些構建工具或打包工具是用 node 來執行的,只要咱們有裝 node,它裏面的內置模塊就能直接引用,不用另外安裝。因此強烈建議你們要是想了解這類工具最好先學習一下 node,否則會老是懵逼的🧐。
言歸正傳,本篇就來簡要講述一下 node 的一些經常使用內置模塊。html

node 初識

node 是什麼

首先 node 不是一門後臺語言而是一個環境,一個可以讓 js 運行在服務器的環境,這個環境就比如是服務器上的瀏覽器(雖然不是很恰當),但正是由於有了它才使得 js 變成了一門後臺語言。前端

node 遵循的規範

其次 node 遵循的是 CommonJs 規範,什麼意思?其實就是規定了導入導出的方式😬,就向下面這樣:vue

require('./module')
module.exports = {
    a: 1,
}
exports.a = 1;
複製代碼

這就是 node 的規範,用 require 導入、用 module.exports 導出。那 node 爲何不支持 ESM(就是用 import 導入、用 export 導出)規範呢,由於它出現的比較早,僅此而已,而後一時半會兒還改不過來,之後應就會支持了。另外,咱們時常在 webpack 裏看到 require() 字樣卻沒有看見 import() 就是由於 webpack 是要用 node 來執行的,而 node 目前只支持 require()
這裏順帶來一張各類規範圖(這種東西容易忘,看成歷史看看就行🙄),以下: node

require 尋找依賴

require() 裏面的參數有兩種寫法,一種帶路徑一種不帶路徑。就像下面這樣:webpack

require('./module'); // 帶相對路徑
require('/module'); // 帶絕對路徑
require('module'); // 不帶路徑
複製代碼

這種不帶路徑的 require('module') 引入方式,多是內置模塊,也多是第三方模塊,內置模塊優先查找,沒有的話就是第三方模塊了,它會先從當前目錄的 node_modules 裏面查找,沒有的話就到父目錄下的 node_modules 裏面去找,如此向上追溯,直到根目錄下的 node_modules 目錄,要是尚未的話就會到全局裏面去找,大概是這麼一個搜索過程。
另一種帶路徑的方式,就會沿着路徑去找,若是沒有找到則會嘗試將當前目錄做一個包來加載。此外,使用絕對路徑的速度查找最快,固然了,node 也對路徑查找作了緩存機制。web

node 模塊包裝

node 在解析每一個模塊(js 文件)時,會對每一個模塊進行包裝,就是在代碼外面加一個閉包,而且向裏傳遞五個參數,這樣就保證了每一個模塊之間的獨立,就像下面這樣:vue-cli

(function(exports, require, module, __filename, __dirname) {
    // module: 表示當前模塊
    // __filename: 當前模塊的帶有完整絕對路徑的文件名
    // __dirname: 當前模塊的完整絕對路徑
    module.exports = exports = this = {};
    // 咱們的代碼就在這裏...
    return module.exports;
})()
複製代碼

想一想咱們平時是否是常在 webpack 裏面看到 __dirname 這種東西,咱們既沒有引入也沒有聲明它,爲何可以直接使用呢,就是由於這個緣由😮。數據庫

node 的應用場景

通常來講,node 主要應用於如下幾個方面:後端

  • 自動化構建等工具
  • 中間層
  • 小項目

第一點對於前端同窗來講應該是重中之重了,什麼工程化、自動構建工具就是用 node 寫出來的,它是前端的一大分水嶺之一,是塊難啃的骨頭,因此咱們必須拿下,否則瓶頸很快就到了。若是你能熟練應用 node 的各類模塊(系統模塊 + 第三方模塊),那麼恭喜你,你又比別人牛逼了一截😎。數組

node 的優勢

  • 適合前端大大們
  • 基於事件驅動和無阻塞的I/O(適合處理併發請求)
  • 性能較好(別人作過性能分析)

node 內置模塊

ok,廢話了這麼多,我們趕忙來看看一些常見的 node 基礎模塊吧。相信掌握這些對你學習 webpack 和 vue-cli 等工具是有很大幫助的✊ 。

http 模塊

這是 node 最最基礎的功能了,咱們用 node http.js 運行一下下面的文件就能開啓一個服務器,在瀏覽器中輸入 http://localhost:8888 便可訪問,http.js 具體內容以下:

// http.js
const http = require('http');
http.createServer((req, res) => { // 開啓一個服務
  console.log('請求來了'); // 若是你打開 http://localhost:8888,控制檯就會打印此消息
  res.write('hello'); // 返回給頁面的值,也就是頁面會顯示 hello
  res.end(); // 必須有結束的標識,不然頁面會一直處於加載狀態
}).listen(8888); // 端口號
複製代碼

fs 文件系統

因爲 js 一開始是用來開發給瀏覽器用的,因此它的能力就侷限於瀏覽器,不能直接對客戶端的本地文件進行操做,這樣作的目的是爲了保證客戶端的信息安全,固然了,經過一些手段也能夠操做客戶端內容(就像 <input type='file'>),可是須要用戶手動操做才行。
可是當 js 做爲後臺語言時,就能夠直接對服務器上的資源文件進行 I/O 操做了。這也是 node 中尤其重要的模塊之一(操做文件的能力),這在自動化構建和工程化中是很經常使用的。它的主要職責就是讀寫文件,或者移動複製刪除等。fs 就比如對數據庫進行增刪改查同樣,不一樣的是它操做的是文件。下面咱們來具體看看代碼用例:

const fs = require('fs');

// 寫入文件:fs.writeFile(path, fileData, cb);
fs.writeFile('./text.txt', 'hello xr!', err => {
  if (err) {
    console.log('寫入失敗', err);
  } else {
    console.log('寫入成功');
  }
});

// 讀取文件:fs.readFile(path, cb);
fs.readFile('./text.txt', (err, fileData) => {
  if (err) {
    console.log('讀取失敗', err);
  } else {
    console.log('讀取成功', fileData.toString()); // fileData 是二進制文件,非媒體文件能夠用 toString 轉換一下
  }
});
複製代碼

須要注意的是 readFile 裏面的 fileData 是原始的二進制文件🤨(em...就是計算機纔看的懂的文件格式),對於非媒體類型(如純文本)的文件能夠用 toString() 轉換一下,媒體類型的文件之後則會以流的方式進行讀取,要是強行用 toString() 轉換的話會丟失掉原始信息,因此不能亂轉。二進制和 toString 的效果就像下面這樣:

另外,和 fs.readFile(異步) 和 fs.writeFile(異步)相對應的還有 fs.readFileSync(同步)和 fs.writeFileSync(同步),fs 的大多方法也都有同步異步兩個版本,具體取決於業務選擇,通常都用異步,不知道用啥的話也用異步。

path 路徑

這個模塊想必你們應該都並不陌生,🧐瞟過 webpack 的都應該看過這個東東。很顯然,path 就是來處理路徑相關東西的,咱們直接看下面的常見用例就可以體會到:

const path = require('path');

let str = '/root/a/b/index.html';
console.log(path.dirname(str)); // 路徑
// /root/a/b
console.log(path.extname(str)); // 後綴名
// .html
console.log(path.basename(str)); // 文件名
// index.html

// path.resolve() 路徑解析,簡單來講就是拼湊路徑,最終返回一個絕對路徑
let pathOne = path.resolve('rooot/a/b', '../c', 'd', '..', 'e');

// 通常用來打印絕對路徑,就像下面這樣,其中 __dirname 指的就是當前目錄
let pathTwo = path.resolve(__dirname, 'build'); // 這個用法很常見,你應該在 webpack 中有見過

console.log(pathOne, pathTwo, __dirname);
// pathOne => /Users/lgq/Desktop/node/rooot/a/c/e
// pathTwo => /Users/lgq/Desktop/node/build
// __dirname => /Users/lgq/Desktop/node
複製代碼

嗯,下次看到 path 這個東西就不會迷茫了。

url 模塊

很顯然這是個用來處理網址相關東西的,也是咱們必需要掌握的,主要用來獲取地址路徑和參數的,就像下面這樣:

const url = require('url');

let site = 'http://www.xr.com/a/b/index.html?a=1&b=2';
let { pathname, query } = url.parse(site, true); // url.parse() 解析網址,true 的意思是把參數解析成對象

console.log(pathname, query);
// /a/b/index.html { a: '1', b: '2' }
複製代碼

querystring 查詢字符串

這個主要是用來把形如這樣的字符串 a=1&b=2&c=3(&和=能夠換成別的)解析成 { a: '1', b: '2', c: '3' } 對象,反過來也能夠把對象拼接成字符串,上面的 url 參數也能夠用 querystring 來解析,具體演示以下:

const querystring = require('querystring');

let query = 'a=1&b=2&c=3'; // 形如這樣的字符串就能被解析
let obj = querystring.parse(query);
console.log(obj, obj.a); // { a: '1', b: '2', c: '3' } '1'

query = 'a=1&b=2&c=3&a=3'; // 若是參數重複,其所對應的值會變成數組
obj = querystring.parse(query);
console.log(obj); // { a: [ '1', '3' ], b: '2', c: '3' }

// 相反的咱們能夠用 querystring.stringify() 把對象拼接成字符串
query = querystring.stringify(obj);
console.log(query); // a=1&a=3&b=2&c=3
複製代碼

assert 斷言

這個咱們直接看下面代碼就知道它的做用了:

// assert.js
const assert = require('assert');

// assert(條件,錯誤消息),條件這部分會返回一個布爾值
assert(2 < 1, '斷言失敗');
複製代碼

node assert.js 運行一下代碼就能看到以下結果:

上圖是斷言失敗的例子,若是斷言正確的話,則不會有任何提示,程序會繼續默默往下執行。因此斷言的做用就是先判斷條件是否正確(有點像 if),若是條件返回值爲 false 則阻止程序運行,並拋出一個錯誤,若是返回值爲 true 則繼續執行,通常用於函數中間和參數判斷。
另外,這裏再介紹兩種 equal 用法(assert 裏面有好多種 equal,這裏舉例其中的兩種):

// assert.js
const assert = require('assert');

const obj1 = { a: { b: 1 } };
const obj2 = { a: { b: 1 } };
const obj3 = { a: { b: '1' } };

// assert.deepEqual(變量,預期值,錯誤信息) 變量 == 預期值
// assert.deepStrictEqual(變量,預期值,錯誤信息) 變量 === 預期值
// 一樣也是錯誤的時候拋出信息,正確的時候繼續默默執行
assert.deepEqual(obj1, obj2, '不等哦'); // true
assert.deepEqual(obj1, obj3, '不等哦'); // true
assert.deepStrictEqual(obj1, obj2, '不等哦'); // true
assert.deepStrictEqual(obj1, obj3, '不等哦'); // false,這個會拋出錯誤信息
複製代碼

stream 流

stream 又叫作流,你們或多或少應該有聽過這個概念,那具體是什麼意思呢?在這裏,你能夠把它當作是前面說過的 fs.readFilefs.writeFile 的升級版。
咱們要知道 readFilewriteFile 的工做流程 是先把整個文件讀取到內存中,而後再一次寫入,這種方式對於稍大的文件就不適用了,由於這樣容易致使內存不足,因此更好的方式是什麼呢?就是邊讀邊寫啦,業界常說成管道流,就像水流通過水管同樣,進水多少,出水就多少,這個水管就是佔用的資源(內存),就那麼大,這咱們樣就能合理利用內存分配啦,而不是一口氣吃成個胖子,有吃撐的風險(就是內存爆了🤐)。

const fs = require('fs');

// 讀取流:fs.createReadStream();
// 寫入流:fs.createWriteStream();
let rs = fs.createReadStream('a.txt'); // 要讀取的文件
let ws = fs.createWriteStream('a2.txt'); // 輸出的文件

rs.pipe(ws); // 用 pipe 將 rs 和 ws 銜接起來,將讀取流的數據傳到輸出流(就是這麼簡單的一句話就能搞定)

rs.on('error', err => {
  console.log(err);
});
ws.on('finish', () => {
  console.log('成功');
})
複製代碼

流式操做,就是一直讀取,它是個連續的過程,若是一邊快一邊慢,或者一邊出錯沒銜接上也不要緊,它會自動處理,不用咱們本身去調整其中的偏差,是個優秀的模塊沒錯了👍。另外,咱們沒有直接使用 stream 模塊,是由於 fs 模塊引用了它並對其作了封裝,因此用 fs 便可。

zlib 壓縮

這個用法簡單,做用也明瞭,直接看下面的代碼就能理解:

const fs = require('fs');
const zlib = require('zlib');

let rs = fs.createReadStream('tree.jpg');
let gz = zlib.createGzip();
let ws = fs.createWriteStream('tree.jpg.gz');

rs.pipe(gz).pipe(ws);  // 原始文件 => 壓縮 => 寫入

rs.on('error', err => {
  console.log(err);
});
ws.on('finish', () => {
  console.log('成功');
})
複製代碼

小結

ok👌,以上就是本章要講的一些 node 知識(比較基礎,你們湊合看看)。固然除此以外,還有 util、Buffer、Event、crypto 和 process 等其餘內置模塊,這裏就不一一贅述了,但願你們可以多動手多敲兩下代碼多實踐,畢竟紙上得來終覺淺嘛💪。若是你能用好 node 的各類模塊,那麼轉後端也就擁有了無限可能性😋(其實前端的坑大的超乎你想像😭)。
最後的最後,安利一下本身的文章,勿噴,哈哈!
一、基於 vue-cli3 打造屬於本身的 UI 庫
二、仿 vue-cli 搭建屬於本身的腳手架
三、this.$toast() 瞭解一下?

相關文章
相關標籤/搜索