零穴店、什么是Node.js?
引用Node.js官方網站的解釋如下:
Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
翻譯成中文就是:
Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環(huán)境途茫。
Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型簿晓,使其輕量又高效症革。
1、 運行環(huán)境(Runtime)
如果做一個類比仔掸,Node.js與JavaScript關系脆贵,就像JDK(Java Development Kit)與Java的關系。
總的來說起暮,Node.js不是一門語言卖氨,而是用來進行Web開發(fā)的Runtime。
2、事件驅動(Event-driven)
在前端web開發(fā)中比較常見的事件驅動例子是筒捺,給一個按鈕綁定一個事件處理程序柏腻,這個事件處理程序就是事件驅動的,JavaScript進程并不知道什么時候調用它系吭,點擊按鈕五嫂,觸發(fā)Click事件,此時主程序得到相應的通知肯尺,就知道調用綁定的的事件處理程序了沃缘。
因為Node.js是JavaScript的Runtime,所以天然就可以使用這種模式通知主進程的I/O 完成则吟。
3槐臀、非阻塞式 I/O(Non-blocking I/O)
阻塞:I/O 時進程休眠等待 I/O 完成后進行下一步
非阻塞:I/O 時函數(shù)立即返回,進程不等待I/O 完成
一氓仲、Node.js 究竟好在哪里水慨?
1、為什么偏愛Node.js
① 前端需求變得重要敬扛、職責范圍變大讥巡,統(tǒng)一開發(fā)體驗
② 在處理高并發(fā)、I/O 密集型場景性能優(yōu)勢明顯
Node.js 使用了事件驅動和非阻塞的 I/O 模型舔哪,使 Node 輕量高效欢顷,非常適合 I/O 密集的 Web 場景。
CPU密集型 VS I/O密集型
CPU密集型:計算等邏輯判斷的操作捉蚤,如:壓縮抬驴、解壓、加密和解密等缆巧。
I/O 密集型:存取設備布持,網絡設施的讀取操作,如:文件的存取陕悬,http等網絡操作题暖,數(shù)據庫操作等。
2捉超、Web常見場景
① 靜態(tài)資源讀取
html胧卤,css,js等文件的讀取
② 數(shù)據庫操作
把數(shù)據存取到物理設磁盤或內存中
③ 渲染頁面
讀取模板文件拼岳,根據數(shù)據生成html
3枝誊、高并發(fā)應對之道
高并發(fā),簡而言之就是單位時間內訪問量特別大惜纸。
對應生活中的場景叶撒,一家菜館做菜招待顧客绝骚,老板剛開始就雇了一個廚師,做菜好吃不貴祠够,顧客很多压汪,顧客排好一條隊,然后顧客選好菜古瓤,廚師拿到菜單開始做菜蛾魄,做好菜,給顧客端上來湿滓,再招待下個顧客滴须。
客人增多,一個廚師忙不過來了叽奥,老板于是又招了2個廚師扔水,這樣顧客可以排3條隊,快了很多朝氓。
隨著菜館名氣增大魔市,顧客越來越多,老板本想再用之前的方法多招幾個廚師赵哲,但是老板想待德,多招廚師好像不太劃算,有些廚師做飯快枫夺,有些做飯慢将宪,經過調查,老板發(fā)現(xiàn)做飯快2倍的廚師只需要花費原來廚師工資的1.5倍橡庞,于是精明的老板炒掉了原來的3個廚師较坛,招來了比原來廚師做飯速度快2倍的另外3個廚師,菜館比之前運轉的更好了扒最。
回到Web開發(fā)場景丑勤,廚師就是物理服務器,應對高并發(fā)的方法如下:① 增加機器數(shù)
機器多了吧趣,流量還是一樣的大法竞,通過Nginx負載均衡分到不同的機器上處理
② 增加每臺機器的CPU數(shù)——多核
單位機器,核數(shù)增多强挫,運算能力增強了
4岔霸、進程與線程
進程在百度百科中的解釋如下:
進程(Process)是計算機中的程序關于某數(shù)據集合上的一次運行活動,是系統(tǒng)進行資源分配和調度的基本單位纠拔。
換成正常的人話就是:電腦桌面的程序秉剑,如QQ音樂泛豪,當我們雙擊圖標時稠诲,實際上是把這個程序加載到內存中執(zhí)行侦鹏,我們稱這個執(zhí)行中的程序就是進程,操作系統(tǒng)都是用進程作為基本單位進行分配和調度的臀叙。
多進程:啟動多個進程略水,多個進程可以一塊執(zhí)行多個任務。
線程劝萤,進程內一個相對獨立的渊涝、可調度的執(zhí)行單元,與同屬一個進程的線程共享進程的資源床嫌。
多線程跨释,啟動一個進程,在一個進程內啟動多個線程厌处,這樣鳖谈,多個線程也可以一塊執(zhí)行多個任務。
5阔涉、Node.js工作模型
傳統(tǒng)的server處理請求(如多線程高并發(fā)模式的Apache)對應生活中的場景缆娃,如下:
一個老板開了一家飯店,不同于之前那個菜館瑰排,這家的每個廚師配備了一個服務員贯要,專門負責點菜,然后把菜單給廚師椭住,廚師負責做菜崇渗,做完后給服務員,服務員端給客人京郑,然后再接待顧客隊伍中的下一個显押。
如果這個飯店是Web的話,點菜這個動作很快傻挂,相當于CPU的運算乘碑,如訪問一個靜態(tài)資源,CPU運算后知道是哪個文件了金拒,去相應盤讀取兽肤,類似于廚師做飯,是一個相對較慢的阻塞I/O操作绪抛,當顧客很多時候就相當于高并發(fā)了资铡。忙不過來的時候,可以選擇增加廚師數(shù)量和服務員數(shù)量幢码,即并發(fā)多進程處理多個請求的概念笤休。
但是這個飯店老板慢慢發(fā)現(xiàn),增加服務員和廚師的同時症副,飯店的空間是有限的店雅,慢慢的變得擁擠(阻塞)政基,而且更為頭疼的是另一個問題:服務員太悠閑了,2分鐘就把點菜的事干完了闹啦,廚師做菜10分鐘沮明,那他就有8分鐘在那干等著,沒事干窍奋,因為廚師沒把菜做完給他荐健,他也不能接待下一個顧客。
同樣類似于Apache開發(fā)web時候琳袄,CPU分配的最大進程數(shù)是有限的江场,并不能沒完沒了的分配進程的,并發(fā)到一定數(shù)目的時候窖逗,必須得排隊(阻塞)了扛稽,更大的問題是,CPU處理的速度遠遠快于I/O滑负,在Web場景中在张,CPU運用場景很少,大頭都在I/O上矮慕,CPU大部分進程情況下都是在等待帮匾,等待I/O,CPU的資源被浪費掉了痴鳄,相當于飯店的服務員一直干等著沒事干瘟斜。
Node.js很好的解決了上面的問題??????,來看下Node.js對應的生活中的場景,如下:
另一個老板也開了一家飯店痪寻,這家飯店只雇傭了一個服務員螺句,這個服務員接待所有的顧客,顧客來了依次點菜橡类,點完菜拿個號找地方坐著去了蛇尚,然后服務員把菜單交給廚師們,然后再去給下一波客人點菜下單顾画,等后廚什么時候說取劫,服務員幾號的菜好了,然后服務員端好菜給制定號碼的顧客研侣,無論哪個顧客來了立刻響應谱邪,廚師一直做菜,服務員一直接單庶诡,顧客的體驗也比上一家要好惦银,不用等到上一個顧客拿到菜才開始點單等待。對應Web體驗就是:一直在那轉圈等待,要比直接顯示連不上要好扯俱。
同樣在Node.js中书蚪,用戶(顧客)發(fā)來請求,有一個主進程(服務員)蘸吓,對應有一個事件輪詢(Event Loop)善炫,來處理用戶的各種請求過來的進程(菜單)撩幽,如qq音樂(小蔥拌豆腐)库继,Photoshop(魚香肉絲)等,然后給Worker threads即多線程(廚師)的去處理窜醉,處理完后完成回調(上菜)宪萄,CPU的利用率達到最大。在Node.js中榨惰,一個CPU上只開一個進程拜英,一個進程里只有一個線程。
6琅催、Node.js單線程
Node.js單線程指的是居凶,一個CPU上只開一個進程,一個進程里只有一個線程藤抡。但這個單線程只是針對主進程侠碧,I/O 等其它各種異步操作都是操作系統(tǒng)底層在多線程調度的 。Node.js就是主進程發(fā)起一個請求缠黍,請求交給I/O 之后弄兜,是由操作系統(tǒng)底層多進程多線程進行調度,然后達到最佳性能瓷式。
Node.js是單線程的替饿,但是不要理解為它做所有事都是單線程的,有一部分不是自己做的贸典,而是交給操作系統(tǒng)做的视卢,它只負責單進程的在那聽,操作系統(tǒng)好了廊驼,就告訴它單進程的做另外的事情腾夯,操作系統(tǒng)怎么處理I/O,它不管蔬充。
單線程并不就是單進程蝶俱,Node.js有個多核處理模塊叫cluster,專門用來處理多CPU饥漫,CPU如果有8個核榨呆,用了cluster之后,Node.js就啟了8個進程庸队,不會浪費CPU的能力积蜻。
7闯割、Node.js能干嘛
- Web Server
- 本地代碼編譯構建(grunt、babel等工具都是基于Node.js開發(fā)的)
- 實用工具的開發(fā)(爬蟲等)
三竿拆、Node.js的基礎API
1宙拉、path(路徑)
path
模塊提供了一些工具函數(shù),用于處理文件與目錄的路徑丙笋⌒怀海可以通過以下方式使用:
const path = require('path');
path
常用方法:
① path.normalize(path)
會規(guī)范化給定的 path,并解析 '..' 和 '.' 片段,如:
const { normalize } = require('path');
console.log(normalize.('/usr///local/bin')); // /usr/local/bin
console.log(normalize.('/usr//local/../bin')); // /usr/bin
/*或者這樣寫:
const path = require('path');
console.log(path.normalize.('/usr///local/bin'));
*/
② path.join([...paths])
使用平臺特定的分隔符把全部給定的 path 片段連接到一起御板,并規(guī)范化生成的路徑,也能解析 '..' 和 '.' 锥忿,如:
const { join } = require('path');
console.log(join.('/usr', 'local', 'bin/')); // /usr/local/bin
console.log(join.('/usr', '../local', 'bin/')); // /usr/bin
③ path.resolve([...paths])
會把一個路徑或路徑片段的序列解析為一個絕對路徑,如:
const { resolve } = require('path');
console.log(resolve.('./')); // /Users/peng/Desktop 返回當前路徑的絕對路徑
④ path.basename(path[, ext])
返回文件名
path.dirname(path)
返回所在文件夾名
path.extname(path)
返回擴展名
const { basename, dirname, extname } = require('path');
const filePath = '/usr/local/bin/test.html';
console.log(basename.(filePath)); // test.html
console.log(dirname.(filePath)); // /usr/local/bin
console.log(extname.(filePath)); // .html
⑤ path.parse(path)
返回一個對象怠肋,對象的屬性表示 path 的元素
path.format()
會從一個對象返回一個路徑字符串敬鬓。 與 path.parse()
方法相反
const { parse, format } = require('path');
const filePath = '/usr/local/bin/test.html';
const ret = parse(filePath);
console.log(ret);
/*
{ root: '/',
dir: '/usr/local/bin',
base: 'test.html',
ext: '.html',
name: 'test' }
*/
console.log(format(ret)); // /usr/local/bin/test.html
另外:
__dirname
、__filename
總是返回文件的絕對路徑
process.cwd()
總是返回執(zhí)行node命令所在的文件夾
2笙各、Buffer (緩沖)
Buffer
類被引入作為 Node.js API 的一部分钉答,使其可以在 TCP 流或文件系統(tǒng)操作等場景中處理二進制數(shù)據流。
Buffer 類的實例類似于整數(shù)數(shù)組杈抢,但 Buffer 的大小是固定的数尿、且在 V8 堆外分配物理內存。 Buffer 的大小在被創(chuàng)建時確定春感,且無法調整砌创。
Buffer 類在 Node.js 中是一個全局變量(global),因此無需使用require('buffer').Buffer
鲫懒。
常用方法:
① Buffer.byteLength()
返回一個字符串的實際字節(jié)長度嫩实。 這與 String.prototype.length不同,因為那返回字符串的字符數(shù)窥岩。
console.log(Buffer.byteLength('test')); // 4
console.log(Buffer.byteLength('中國')); // 6
② Buffer.from(array)
通過一個八位字節(jié)的 array 創(chuàng)建一個新的 Buffer 甲献,如果 array 不是一個數(shù)組,則拋出 TypeError 錯誤颂翼。
console.log(Buffer.from([1, 2, 3])); // <Buffer 01 02 03>
③ Buffer.isBuffer(obj)
如果 obj 是一個 Buffer 則返回 true 晃洒,否則返回 false
console.log(Buffer.isBuffer({ 'a': 1 })); // false
console.log(Buffer.isBuffer(Buffer.from([1, 2, 3]))); // true
④ Buffer.concat(list)
如果 obj 是一個 Buffer 則返回 true ,否則返回 false
const buf1 = Buffer.from('hello ');
const buf2 = Buffer.from('world');
const buf = Buffer.concat([buf1, buf2]);
console.log(buf.toString()); // hello world
常用屬性:
① buf.length
長度
buf.toString()
轉為字符串
buf.fill()
填充
buf.equals()
判斷是否相等
buf.indexOf()
是否包含朦乏,如果包含返回位置值球及,不包含返回-1
const buf = Buffer.from('hello world');
const buf2 = Buffer.from('hello world!');
console.log(buf.length); // 15
console.log(buf.toString()); // hello world
console.log(buf.fill(10, 2, 6)); // <Buffer 68 65 0a 0a 0a 0a 77 6f 72 6c 64> 這里從第3個到第6個都被替換成了0a,a就是16進制的數(shù)字10
console.log(buf.equals(buf2)); // false
console.log(buf.indexOf('h')); // 0
3、events(事件)
大多數(shù) Node.js 核心 API 都采用慣用的異步事件驅動架構呻疹,其中某些類型的對象(觸發(fā)器)會周期性地觸發(fā)命名事件來調用函數(shù)對象(監(jiān)聽器)吃引。
所有能觸發(fā)事件的對象都是 EventEmitter 類的實例。 這些對象開放了一個 eventEmitter.on() 函數(shù),允許將一個或多個函數(shù)綁定到會被對象觸發(fā)的命名事件上镊尺。 事件名稱通常是駝峰式的字符串朦佩,但也可以使用任何有效的 JavaScript 屬性名。
官網例子:一個綁定了一個監(jiān)聽器的 EventEmitter 實例庐氮。 eventEmitter.on() 方法用于注冊監(jiān)聽器语稠,eventEmitter.emit() 方法用于觸發(fā)事件。
const EventEmitter = require('events');
class CustomEvent extends EventEmitter {}
const myEmitter = new CustomEvent();
myEmitter.on('error', err => {
console.log(err);
})
myEmitter.emit('error', new Error('This is an error!'));
當有一個錯誤的時候弄砍,會顯示Error: This is an error!
仙畦,然后顯示具體錯誤內容。
4输枯、fs(文件系統(tǒng))
通過 require('fs') 使用該模塊议泵。 所有的方法都有異步和同步的形式占贫。
異步方法的最后一個參數(shù)都是一個回調函數(shù)桃熄。 傳給回調函數(shù)的參數(shù)取決于具體方法,但回調函數(shù)的第一個參數(shù)都會保留給異常型奥。 如果操作成功完成瞳收,則第一個參數(shù)會是 null 或 undefined。
常用方法如下:
① fs.readFile(path[, options], callback)
異步地讀取一個文件的全部內容
const fs = require('fs');
fs.readFile('./test.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
此時如果test.txt文件內容只有一個字母a厢汹,那么打印出來的就是<Buffer 61>
回調有兩個參數(shù) (err, data)螟深,其中 data 是文件的內容。如果未指定字符編碼烫葬,則返回原始的 buffer界弧。
指定編碼格式后,就會按照編碼格式打印文件內容:
const fs = require('fs');
fs.readFile('./test.txt', 'utf-8',(err, data) => {
if (err) throw err;
console.log(data);
});
此時如果test.txt文件內容只有一個字母a搭综,那么打印出來的就是a
② fs.writeFile(file, data[, options], callback)
異步地寫入數(shù)據到文件垢箕,如果文件已經存在,則替代文件兑巾。
const fs = require('fs');
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
③ fs.stat(path,callback)
可用來判斷一個文件是否存在
回調有兩個參數(shù) (err, stats)条获,其中 stats是一個 fs.Stats對象。
const fs = require('fs');
fs.stat('./message.txt', (err, stats)=>{
if (err){
console.log('文件不存在');
return;
};
console.log(stats.isFile()); // true 判斷是否是一個文件
console.log(stats.isDirectory()); // false 判斷是否是一個文件夾
});
④ fs.rename(oldPath, newPath, callback)
用來修改文件名
const fs = require('fs');
fs.rename('./message.txt', 'm.txt', err=>{
if (err) throw err;
console.log('修改成功蒋歌!');
})
⑤ fs.unlink(path, callback)
刪除文件
const fs = require('fs');
fs.unlink('./m.txt', err=>{
if (err) throw err;
console.log('刪除成功帅掘!');
})
⑥ fs.readdir(path[, options], callback)
讀取指定路徑下的所有文件
const fs = require('fs');
fs.readdir('./', (err, files)=>{
if (err) throw err;
console.log(files);
/*
[ '.DS_Store',
'node_modules',
'package.json',
'test.js',
'test.txt' ]
*/
})
⑦ fs.mkdir(path[, mode], callback)
在指定路徑里創(chuàng)建一個文件夾
const fs = require('fs');
// 在當前目錄創(chuàng)建一個叫test的文件夾
fs.mkdir('./test', err=>{
if (err) throw err;
console.log('文件夾創(chuàng)建成功');
})
⑧ fs.rmdir(path, callback)
刪除指定路徑下的文件夾
const fs = require('fs');
fs.rmdir('./test', err=>{
if (err) throw err;
console.log('文件夾刪除成功');
})
⑨ fs.watch(filename[, options][, listener])
和gulp里的watch很像,用來監(jiān)視 filename的變化堂油,filename 可以是一個文件或一個目錄修档。
監(jiān)聽器回調有兩個參數(shù) (eventType, filename)。 eventType 可以是 'rename' 或 'change'府框,filename 是觸發(fā)事件的文件的名稱吱窝。
const fs = require('fs');
fs.watch('./', {
recursive: true // 指明是否全部子目錄應該被監(jiān)視
}, (eventType, filename) =>{
console.log(eventType, filename);
})
注意,在大多數(shù)平臺,當一個文件出現(xiàn)或消失在一個目錄里時癣诱,'rename' 會被觸發(fā)计维。
⑩ fs.createReadStream(path[, options])
返回一個新建的 ReadStream 對象
const fs = require('fs');
const rs = fs.createReadStream('./test.txt');
rs.pipe(process.stdout); // 在終端輸出test.txt內容