Node.js核心內容

預熱

介紹模塊化編程,由於Node.js是一個高度模塊化的平臺,學習模塊化能夠幫助咱們快速理解和使用Node.js。詳見以下,這是摘自我去年寫的一篇博客筆記(博客目前已經沒有在維護了)javascript


title:前端模塊化演變
date:2018-10-23 23:27:56
tags:模塊化css

現狀

前端產品的交付是基於瀏覽器,這些資源是經過增量加載的方式運行到瀏覽器端,如何在開發環境組織好這些碎片化的代碼和資源,而且保證他們在瀏覽器端快速、優雅的加載和更新,就須要一個模塊化系統,這個理想中的模塊化系統是前端工程師多年來一直探索的難題。前端

  • 異步概念 js是單線程的,因爲執行ajax請求會消耗必定的時間,甚至出現了網絡故障而遲遲得不到返回結果;這時,若是同步執行的話,就必須等到ajax返回結果之後才能執行接下來的代碼,若是ajax請求須要1分鐘,程序就得等1分鐘。若是是異步執行的話,就是告訴ajax代碼「老兄,既然你遲遲不返回結果,我先不等你了,我還有一大堆代碼要執行,等你執行完了給我說一下」java

  • 模塊系統主要解決模塊的定義、依賴和導出,先來看看已經存在的模塊系統。node

模塊系統的演進

原始的 JavaScript 文件加載方式,若是把每個文件看作是一個模塊,那麼他們的接口一般是暴露在全局做用域下,也就是定義在 window 對象中,不一樣模塊的接口調用都是一個做用域中,一些複雜的框架,會使用命名空間的概念來組織這些模塊的接口,典型的例子如 YUI 庫jquery

這種原始的加載方式暴露了一些顯而易見的弊端:web

1.全局做用域下容易形成變量衝突ajax

2.文件只能按照 <script> 的書寫順序進行加載npm

3.開發人員必須主觀解決模塊和代碼庫的依賴關係編程

4.在大型項目中各類資源難以管理,長期積累的問題致使代碼庫混亂不堪 ​

CommonJS

服務器端的 Node.js 遵循 CommonJS規範,該規範的核心思想是容許模塊經過 require 方法來同步加載所要依賴的其餘模塊,而後經過 exports 或 module.exports 來導出須要暴露的接口。

require("module");
module.exports = module;
複製代碼

優勢:

  1. 服務器端模塊便於重用
  2. NPM 中已經有將近20萬個可使用模塊包
  3. 簡單並容易使用

缺點:

  1. 同步的模塊加載方式不適合在瀏覽器環境中,同步意味着阻塞加載,瀏覽器資源是異步加載的
  2. 不能非阻塞的並行加載多個模塊

實現:

  1. 服務器端的 Node.js
  2. Browserify,瀏覽器端的 CommonJS 實現,可使用 NPM 的模塊,可是編譯打包後的文件體積可能很大
  3. modules-webmake,相似Browserify,還不如 Browserify 靈活
  4. wreq,Browserify 的前身

AMD

Asynchronous Module Definition 規範其實只有一個主要接口 define(id?, dependencies?, factory),它要在聲明模塊的時候指定全部的依賴 dependencies,而且還要當作形參傳到 factory 中,對於依賴的模塊提早執行,依賴前置。

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
複製代碼

優勢:

  • 適合在瀏覽器環境中異步加載模塊
  • 能夠並行加載多個模塊
  • 缺點:
  • 提升了開發成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢
  • 不符合通用的模塊化思惟方式,是一種妥協的實現

實現:

  • RequireJS
  • curl

CMD

Common Module Definition 規範和 AMD 很類似,儘可能保持簡單,並與 CommonJS 和 Node.js 的 Modules 規範保持了很大的兼容性。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})
複製代碼

優勢:

  • 依賴就近,延遲執行
  • 能夠很容易在 Node.js 中運行

缺點:

  • 依賴 SPM 打包,模塊的加載邏輯偏重

實現:

  • Sea.js
  • coolie

UMD

Universal Module Definition 規範相似於兼容 CommonJS 和 AMD 的語法糖,是模塊定義的跨平臺解決方案。

ES6 模塊

ECMAScript6 標準增長了 JavaScript 語言層面的模塊體系定義。ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。

import "jquery";
export function doStuff() {}
module "localModule" {}
複製代碼

優勢:

  • 容易進行靜態分析
  • 面向將來的 ECMAScript 標準

缺點:

  • 原生瀏覽器端尚未實現該標準
  • 全新的命令字,新版的 Node.js才支持

實現:

  • Babel

前端模塊加載

前端模塊要在客戶端中執行,因此他們須要增量加載到瀏覽器中。 模塊的加載和傳輸,咱們首先能想到兩種極端的方式,一種是每一個模塊文件都單獨請求,另外一種是把全部模塊打包成一個文件而後只請求一次。顯而易見,每一個模塊都發起單獨的請求形成了請求次數過多,致使應用啓動速度慢;一次請求加載全部模塊致使流量浪費、初始化過程慢。這兩種方式都不是好的解決方案,它們過於簡單粗暴。 分塊傳輸,按需進行懶加載,在實際用到某些模塊的時候再增量更新,纔是較爲合理的模塊加載方案。 要實現模塊的按需加載,就須要一個對整個代碼庫中的模塊進行靜態分析、編譯打包的過程。

全部資源都是模塊

在上面的分析過程當中,咱們提到的模塊僅僅是指JavaScript模塊文件。然而,在前端開發過程當中還涉及到樣式、圖片、字體、HTML 模板等等衆多的資源。這些資源還會以各類方言的形式存在,好比 coffeescript、 less、 sass、衆多的模板庫、多語言系統(i18n)等等。 若是他們均可以視做模塊,而且均可以經過require的方式來加載,將帶來優雅的開發體驗,好比:如何使用reuqire管理.

require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
複製代碼

靜態分析

在編譯的時候,要對整個代碼進行靜態分析,分析出各個模塊的類型和它們依賴關係,而後將不一樣類型的模塊提交給適配的加載器來處理。好比一個用 LESS 寫的樣式模塊,能夠先用 LESS 加載器將它轉成一個CSS 模塊,在經過 CSS 模塊把他插入到頁面的 <style> 標籤中執行。Webpack 就是在這樣的需求中應運而生。 同時,爲了能利用已經存在的各類框架、庫和已經寫好的文件,咱們還須要一個模塊加載的兼容策略,來避免重寫全部的模塊。 那麼接下來,讓咱們開始 Webpack 的神奇之旅吧。

模塊化解決問題:

命名衝突,文件依賴,多人協做,可維護性,開發效率

演變過程-案例說明

以一個計算器模塊爲案例說明

1. 全局函數:全部變量和函數所有暴露在全局

```javascript
function add(x,y){
	return pareseInt(x) + parseInt(y)
}
function subtract(x,y){
	return pareseInt(x) - parseInt(y)
}
function multiply(x,y){
	return pareseInt(x) * parseInt(y)
}
function divide(x,y){
	return pareseInt(x) / parseInt(y)
}

```
複製代碼

2. 對象命名空間

>用來解決上述問題 可是命名衝突還在,若是有子命名空間就會致使命名空間愈來愈長

```javascript
var calculator={};
calculator.add=function(x,y){
	return pareseInt(x) + parseInt(y)
}
...
...
calculator.add(1,3)  //命名空間調用
...
...
//若是有子命名空間就會致使命名空間愈來愈長
calculator.sub={
    foo:function(){
       ...
    }
}
calculator.sub.foo()
```
複製代碼

3. 函數做用域(閉包)

> 全局函數和對象命名空間都不能很好的解決命名衝突的問題,並且開發中會有一些不想被外部訪問的私有屬性,那麼咱們能夠經過封裝函數的私有空間去讓一些屬性和方法私有化,也就是閉包
```javascript
var calculator=(function(){
	function add(x,y){
		return parseInt(x) + parseInt(y)
	}
	function subtract(x,y){
		return pareseInt(x) - parseInt(y)
	}
	function multiply(x,y){
		return pareseInt(x) * parseInt(y)
	}
	function divide(x,y){
		return pareseInt(x) / parseInt(y)
	}
	return {
		add:add,
		subtarct:subtract,
		multiply:multiply,
		divide:divide
	}
})()
calculator.add(x,y)  //經過匿名函數.函數名
		```
複製代碼

4. 維護和擴展

需求:添加一個取餘的方法,若是這個計算模塊由第三方提供<br>
解決:經過參數的形式將原來的模塊和第三方庫傳遞進去<br>
```javascript
var calculator=(function(cal){
function add(x,y){
	return parseInt(x) + parseInt(y)
}
function subtract(x,y){
	return pareseInt(x) - parseInt(y)
}
function multiply(x,y){
	return pareseInt(x) * parseInt(y)
}
function divide(x,y){
	return pareseInt(x) / parseInt(y)
}
	cal.add:add,
	cal.subtarct:subtract,
	cal.multiply:multiply,
	cal.divide:divide
	return cal;

})(calculator||{})

var carculator=(function(){
	cal.mod=function(x,y){
		return x % y
	}
	return cal;
})(calculator||{})
```
複製代碼

Node.js基本介紹

Node.js的特色

1. js的runtime,讓js脫離瀏覽器在服務端單獨執行

2. 依賴於V8引擎進行解析代碼

3. 事件驅動 事件觸發纔會執行響應的處理函數,這種機制就是標準的事件驅動機制

4. 非阻塞IO 

5. 輕量可伸縮,適用於實時數據交互應用 利用socket能夠實現雙向通訊,例如聊天室的

6. 單線程和單進程
	進程就是一個application的一次執行過程,
	而線程是進程中的一部分,進程包含多個線程在運行,單線程就是進程中只有一個線程
	阻塞Io模式下一個線程只能處理一個任務非阻塞Io下,一個線程永遠在處理任務,這樣的cpu利用率是100%
	因此node.js採用單線程,利用事件驅動的異步編程模式來實現了非阻塞Io
複製代碼

global對象和模塊做用域

node中的global相似瀏覽器的window對象,用於定義全局命名空間,因此除了global以外都是他的屬性	
node中定義的變量默認是當前文件下的,不是全局global的,可是咱們能夠手動掛載到globa上
	var foo=10;
	global.foo=foo;  
	consoloe.log(global.foo) 
複製代碼

require/exports/module.exports

exports只是返回一個Object對象,不能單獨定義並返回數據類型
module.exports能夠單獨定義,返回數據類型
複製代碼

全局可用變量,函數,對象

全局做用域中任何變量函數和對象都是global對象的一個屬性
全局可用就是node提供的一下全局可用變量函數以及對象不須要進行模塊加載就能夠直接使用
如:require是能夠在每一個模塊做用域中存在,不加載就可使用,能夠說他全局可用而不是全局函數

全局變量
	_dirname 當前文件所在目錄
	_filename  
	當前正在執行腳本的文件名,輸出文件所在位置的絕對路徑,若是在模塊中則顯示模塊文件的路徑
全局函數
	定時器
	console對象   info error warn dir time timeEnd trace(測試函數運行)  
複製代碼

require()的模塊加載規則

加載模塊分爲兩大類:文件模塊和核心模塊
文件模塊:
	-使用「/」開頭的模塊表示 執行當前文件所屬的盤符根路徑  c盤d盤...
	- 以"./""../"開頭的相對路徑模塊標識  能夠省略後綴名js json node

核心模塊:是被編譯的二進制文件,保存在源碼lib目錄下
	全局對象
	經常使用工具
	事件機制
	文件系統訪問
	http服務器與客戶端
複製代碼

模塊的緩存

foo.js  ==>   console.log('foo模塊被加載了')
index.js   下面只會加載一次(輸出一次)。由於模塊的緩存 
  reuqire("./foo")
  reuqire("./foo")
  reuqire("./foo")
  reuqire("./foo")  
 
 不但願模塊緩存能夠在被加載的模塊(foo.js)中添加以下代碼
 delete require.cache[module.filename] 結果會輸出四次
複製代碼

異步編程

js的執行環境是單線程,一次只能完成一個任務,因此常見的瀏覽器無響應就是由於某代碼運行時間過長,致使後面任務沒法執行,因此NodeJS加入了異步編程的概念,解決單線程阻塞問題

1. 同步和異步

異步案例 setTimeout(()=>{},0)
複製代碼

2. 回調函數

-同步代碼中使用try...catch處理異常
-異步代碼中不能使用try...catch處理異常
	對於異步代碼 try..catch是沒法捕捉異步代碼中出現的異常的
複製代碼

3. 使用回調函數接受異步代碼的執行結果

異步編程提出了回調函數的設計
三個約定:
	1.優先把callback當作最後一個形參
	2.將代碼出現的錯誤做爲callback第一個參數
		callback(err,result)
	3.將代碼成功返回的結果做爲callback的第二個參數
		callback(err,result)
** 異步編程的’事件驅動‘思路
	異步函數執行時,不肯定什麼時候執行完畢,回調函數會被壓入到一個事件循環(Event Loop)隊列,
	而後往下執行其餘代碼,直至異步函數執行完畢後,纔會開始處理事件循環,調用響應的回調函數
	EventL Loop是一個先進先出的隊列
複製代碼

NodeJS中的包和npm

CommonJS是規範,開篇預熱中已經介紹過,Node.js是這種規範的部分實現

規範的包目錄結構:

-package.json 頂層目錄的包描述文件,說明文件(開發者拿到第三方包的時候一目瞭然)
	文件屬性說明  屬性和值經過json字符串形式描述
		-name  包名
		-description 包的描述
		-version 版本號
		-keywords 關鍵詞用於npm包市場搜索
		-author  包的做者
		-main  配置包的入口,默認就是模塊根目錄下的index.js
		-dependencies 包的依賴項,npm會自行下載
		-scripts  指定運行腳本命令的npm命令行縮寫
-bin 存放可執行的二進制文件
-lib 存放js的目錄
-doc 存放文檔的目錄 
-test 存放單元測試用例的代碼
複製代碼

npm經常使用命令

-npm init --yes  		初始一個package.json文件
-npm install 名字  		安裝包
-npm install 包名 --save 		將安裝的包添加到package.json的依賴中
-npm install 包名 -g  		全局安裝一個命令行工具
-npm install docs 			查看包文檔,很是有用
-npm root -g 					查看全局包安裝路徑
-npm config set prefix '路徑'		 修改全局包安裝路徑
-npm list 				查看當前目錄下安裝的全部包
-npm list -g			 查看全局包的安裝路徑下的全部包
-npm uninstall 包   	卸載當前目錄下的某個包
-npm uninstall 包 -g
-npm update 包       更新當前目錄下的某個包
複製代碼

Node.js文件模塊

基本文件操做

API: File System  簡寫fs
開發中建議使用異步函數,比起同步函數性能更高,速度更快,沒有阻塞
方法:同步 Sync和異步   
	1.文件寫入
		fs.writeFileSync(file,data)  同步必須使用tryCatc捕獲,防止出錯程序意外退出
		fs.writeFile(file,data,cllback)  在回調中判斷err參數 
	2.向文件中追加內容   
		fs.appendFile(file,data,callback)
	3.文件讀取
		fs.readFile(file,callback(err,data))  data.toString()轉換二進制數據
	4.文件複製 
		node沒有提供這個函數,咱們本身能夠封裝,思路:讀取一個文件寫入另外一個文件
	5.獲取文件信息
		fs.stat(path,callback)
複製代碼

案例:控制歌詞滾動

1.建立歌詞文件lrc.txt
[ti:我愛你中國(Live)]
[ar:汪峯]
[al:歌手第二季 歌王之戰]
[by:天龍888]
[00:00.00]汪峯 - 我愛你中國(Live)
[00:00.02]詞:汪峯
[00:00.03]曲:汪峯
[00:00.04]原唱:汪峯
[00:00.05]編曲:黃毅
[00:00.06]Program:黃毅
[00:00.07]現場Program:汪濤
[00:00.08]製做人:黃毅
[00:00.09]音樂總監:梁翹柏
.....
複製代碼
2.編寫js文件
const fs = require("fs");
fs.readFile("./lrc.txt", function(err, data) {
    if (err) {
        return console.log("讀取歌詞文件失敗")
    }
    data = data.toString();
    var lines = data.split("\n");

    var reg = /\[(\d{2})\:(\d{2})\.(\d{2})\]\s*(.+)/;
    for (var i = 0; i < lines.length; i++) {
        (function(index) {
            var line = lines[index];
            var matches = reg.exec(line);
            if (matches) {
                var m = parseFloat(matches[1]); //獲取分
                var s = parseFloat(matches[2]); //獲取秒
                var ms = parseFloat(matches[3]); //獲取毫秒
                var content = matches[4] //獲取定時器要輸出的內容
                var time = m * 60 * 1000 + s * 1000 + ms; //將分+秒+毫秒轉化爲毫秒
                setTimeout(() => {
                    console.log(content)
                }, time);
            }
        })(i)
    }
})
複製代碼

文件操做的相關Api

1.Path模塊之路徑字符串模塊
		basename  獲取文件名
		dirname   獲取文件目錄
		extname    獲取文件擴展名
		isAbsolute  判斷是否爲絕對路徑  
		join(path1,path2,..)  拼接路徑字符串  \\轉義後爲\
		normalize(p)   將非標準路徑轉換爲標準路徑
		sep		獲取操做系統的文件路徑分隔符
	2.目錄操做
		對文件目錄增長,讀取,刪除等操做
		fs.mkdir(path,mode,callback)  mode設置目錄權限 默認0777
		fs.rmdir(path,callback)  回調無參數  刪除目錄時目錄中必須爲空目錄,須要提早讀取目錄和刪除目錄中的文件
複製代碼

Node.js中處理數據I/O

Buffer緩衝區

Buffer緩衝區 爲Node提供存儲原始數據的方法,用來在內存區域建立一個存放二進制數據的緩衝區

首先知道什麼是二進制數據和亂碼
鼠標右鍵直接建立文件編碼通常爲ANSI 這時候文件中包含中文字符,這個編碼不支持中文字符。因此會出現亂碼
能夠修改.txt文件編碼爲UTF-8,從新打印就沒問題了
複製代碼
Buffer的構造函數
** 緩衝區模塊支持開發者在緩衝區結構中建立,讀取,寫入和操做二進制數據
	** 此模塊是全局的,使用時不須要require()函數來加載
	建立方式:
		1.傳入字節 var buf=new Buffer(size)  size=5表明建立了一個5字節的內存空間
		2.傳入數組 var buf=new Buffer([10,20,30,40])
		3.傳入字符串和編碼 var buf=new Buffer('hello world','utf-8') utf-8爲默認支持的編碼方式能夠省略
	寫入緩衝區:
		首先將源文件的數據讀取處理而後寫入到Buffer緩衝區中

	從緩衝區讀取數據
		buf.toString(encoding,start,end)
			encoding 編碼默認uft-8
			start 指定開始讀取的索引位置,默認0
			end  緩衝區結束位置
	
	拼接緩衝區
		實際開發中遇到的需求:
			輸出多個緩衝區的內容的組合
		buf.concat(list,totalLength)
			list是合併的Buffer對象數組列表
			totalLength用於指定合併後的Buffer對象總長度
複製代碼

Stream文件流

問題:因爲Buffer緩衝區限制在1GB,超過限制的文件沒法直接完成讀寫操做,讀寫大文件的時候,讀寫資源一直持續不停,node將沒法繼續其餘工做,因此採用文件流的方式

解決: stream文件流來解決大數據文件操做問題,node讀寫很容易是內存爆倉(1GB),因此文件流的讀寫會防止這一現象
1.文件流的概念:
	stream文件流方式: 讀一部分,寫一部分,好處是能夠提早處理,縮短等待時間,提升速度
	案例模擬:觀看在線視頻的時候,下載一點播放一點

	Stream有4中流類型:
		1.Readable 可讀操做(可讀流)
		2.Writable 可寫操做(可寫流)
		3.Duplex 可讀可寫操做(雙向流,雙工流)
		4.Transform 操做被寫入數據,而後讀出結果(變換流)

	NodeJS中不少模塊涉及流的讀寫,以下:
		HTTP requests and responses
		Standard input/output
		File reads and writes
	Node.js中的I/O是異步的,因此對磁盤和網絡的讀寫須要經過回到函數來讀取數據,而回調函數須要經過事件來觸發,全部的Stream對象都是EventEmitter(時間觸發器)的實例
	Stream經常使用事件以下:
		1.data 當有數據可讀時觸發
		2.end 沒有更多的數據可讀時觸發
		3.error 在接受和寫入的過程當中發生錯誤時觸發
		4.finish 全部的數據被寫入到底層系統時觸發
2. Node.js的可讀流和可寫流
	與buffer的讀寫操做相似,Stream中的可讀流和可寫流也用於讀寫操做
	1.使用文件流進行文件複製,首先要建立一個可讀流(Readable Stream) 可讀流可讓用戶在源文件中分塊讀取文件中的數據,而後再從可讀流中讀取數據
		fs.createReadStream(path,options)
		options是一組 key-value值,經常使用設置以下
			flags  對文件如何操做,默認爲r 讀文件
			encoding	
			start
			end
	2.可寫流(Writable Stream)
		fs.createWriteStream(path,options)
		方法:write將一個數據寫入到可寫流中

	3.使用pipe()處理大文件
		若是把數據比做水,chunk就至關於盆,使用盆來完成水的傳遞
		在可讀流中還有一個函數叫作pipe()  是一個很高效的文件處理方式,能夠簡化複製文件的操做
		pipe中文管子,至關於用管子替換盆,經過管道來完成數據的讀取和寫入

複製代碼

後續更新中...

相關文章
相關標籤/搜索