一愈魏、異步編程
- 異步操作
- Node 采用 Chrome V8 引擎處理 JavaScript 腳本觅玻, V8 最大特點就是單線程運行,一次只能運行一個任務(wù)
- Node 大量采用異步操作(asynchronous operation)培漏,即任務(wù)不是馬上執(zhí)行溪厘,而是插在任務(wù)隊列的尾部,等到前面的任務(wù)運行完后再執(zhí)行
- 提高代碼的響應(yīng)能力
Node大量采用異步操作牌柄,即任務(wù)不是馬上執(zhí)行畸悬,而是直接插入任務(wù)隊列的尾部,等前面任務(wù)執(zhí)行完成后再只執(zhí)行友鼻。異步執(zhí)行傻昙,而不是單線程執(zhí)行(一次只能執(zhí)行一個任務(wù)),這大大提高代碼的響應(yīng)能力彩扔。
【Node中妆档,所有會發(fā)生阻塞的操作都是異步的〕娴铮】
- setTimeout()
- ajax
- 文件操作
...
- 異步操作回調(diào)
由于系統(tǒng)永遠(yuǎn)不知道用戶什么時候會輸入內(nèi)容贾惦,所以代 碼不能永遠(yuǎn)停在一個地方;
Node 中的操作方式就是以異步回調(diào)的方式解決無狀態(tài) 的問題;
- 回調(diào)函數(shù)的設(shè)計
- 回調(diào)函數(shù)一定作為參數(shù)的最后一個參數(shù)出現(xiàn):
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }
- 回調(diào)函數(shù)的第一個參數(shù)默認(rèn)接收錯誤信息,第二個參數(shù)才是真正 的回調(diào)數(shù)據(jù)(便于外界獲取調(diào)用的錯誤情況):
foo1('李明', 19, function(error, data) {
if(error) throw error;
console.log(data);
});
***錯誤優(yōu)先: ***因為之后的操作大多數(shù)都是異步的方式敦捧,無法通過 try catch 捕獲異常; 所以在node中錯誤優(yōu)先的回調(diào)函數(shù)须板,第一個參數(shù)為上一步的錯誤信息。
- 異步回調(diào)的問題
- 異步事件驅(qū)動的代碼不容易閱讀
- 不容易調(diào)試
- 不容易維護(hù)
二兢卵、進(jìn)程和線程
- 什么是進(jìn)程
- 一個正在運行 的應(yīng)用程序都稱之為進(jìn)程;
- 每一個應(yīng)用程序都至少有一個進(jìn)程;
- 進(jìn)程是用來給應(yīng)用程序提供一個運行的環(huán)境;
- 進(jìn)程是操作系統(tǒng)為應(yīng)用程序分配資源的一個單位;
- 什么是線程
- 用來執(zhí)行應(yīng)用程序中的代碼;
- 在一個進(jìn)程內(nèi)部习瑰,可以有很多的線程;
- 在一個線程內(nèi)部,同時只可以干一件事;
- 而且傳統(tǒng)的開發(fā)方式大部分都是 I/O 阻塞的;
- 所以需要多線程來更好的利用硬件資源;
- 給人帶來一種錯覺:線程越多越好;
多線程同時執(zhí)行秽荤,真實情況并不是"同時"甜奄,因為CPU只有一個柠横;
線程問題: 線程創(chuàng)建需要耗費資源,線程數(shù)量也不能無限添加课兄,線程同步操作牍氛,線程間數(shù)據(jù)共享,CPU中線程間的切換有上下文的轉(zhuǎn)換是需要耗時的....
在node中烟阐,實現(xiàn)異步非阻塞操作搬俊,并不是使用多線程實現(xiàn)了(常規(guī)的異步非阻塞是通過多線程實現(xiàn)的)。*** Node.js在設(shè)計上也是比較大膽蜒茄,它以單進(jìn)程唉擂、單線程模式運行。事件驅(qū)動機制是Node.js通過內(nèi)部單線程高效率地維護(hù)事件循環(huán)隊列來實現(xiàn)的扩淀,沒有多線程的資源占用和上下文切換楔敌,這意味著面對大規(guī)模的http請求,Node.js憑借事件驅(qū)動搞定一切驻谆。***
三卵凑、事件驅(qū)動
事件驅(qū)動是NodeJS中一大特性。事件驅(qū)動胜臊,就是通過監(jiān)聽事件的狀態(tài)變化來作出相應(yīng)的操作勺卢。例如文件存在,文件不存在象对,文件讀取完畢黑忱,文件讀取錯誤,觸發(fā)對應(yīng)的狀態(tài)勒魔,之后通過回調(diào)函數(shù)進(jìn)行處理甫煞。
- 線程驅(qū)動和事件驅(qū)動
-
線程驅(qū)動就是當(dāng)收到一個請求的時候,將會為該請求開一個新的線程來處理請求冠绢。一般存在一個線程池抚吠,線程池中有空閑的線程,會從線程池中拿取線程來進(jìn)行處理弟胀,如果線程池中沒有空閑的線程楷力,新來的請求將會進(jìn)入隊列排隊,直到線程池中空閑線程孵户;
- 事件驅(qū)動就是當(dāng)進(jìn)來一個新的請求的時萧朝,請求將會被壓入隊列中,然后通過一個循環(huán)來檢測隊列中的事件狀態(tài)變化夏哭,如果檢測到有狀態(tài)變化的事件检柬,那么就執(zhí)行該事件對應(yīng)的處理代碼,一般都是回調(diào)函數(shù)竖配;
-
在美國去看醫(yī)生何址,需要填寫大量表格酱固,比如保險、個人信息之類头朱,傳統(tǒng)的基于線程的系統(tǒng)(thread-based system),接待員叫到你龄减,你需要在前臺填寫完成這些表格项钮,你站著填單,而接待員坐著看你填單希停。你讓接待員沒辦法接待下一個客戶烁巫,除非完成你的業(yè)務(wù)。想讓這個系統(tǒng)能運行的快一些宠能,只有多加幾個接待員亚隙,人力成本需要增加不少。
基于事件的系統(tǒng)(event-based system)中违崇,當(dāng)你到窗口發(fā)現(xiàn)需要填寫一些額外的表格而不僅僅是掛個號阿弃,接待員把表格和筆給你,告訴你可以找個座位填寫羞延,填完了以后再回去找他渣淳。你回去坐著填表,而接待員開始接待下一個客戶伴箩。你沒有阻塞接待員的服務(wù)入愧。
你填完表格,返回隊伍中嗤谚,等接待員接待完現(xiàn)在的客戶棺蛛,你把表格遞給他。如果有什么問題或者需要填寫額外的表格巩步,他給你一份新的旁赊,然后重復(fù)這個過程。
這個系統(tǒng)已經(jīng)非常高效了渗钉,幾乎大部分醫(yī)生都是這么做的彤恶。如果等待的人太多,可以加入額外的接待員進(jìn)行服務(wù)鳄橘,但是肯定要比基于線程模式的少得多声离。
四、模塊化結(jié)構(gòu)
模塊與文件是一一對應(yīng)關(guān)系瘫怜,即加載一個模塊术徊,實際上
就是加載對應(yīng)的一個模塊文件模塊的分類
- 文件模塊
就是我們自己寫的功能模塊文件
- 核心模塊
Node 平臺自帶的一套基本的功能模塊,也有人稱之為 Node平臺的 API
- 第三方模塊
社區(qū)或第三方個人開發(fā)好的功能模塊鲸湃,可以直接拿回來用
- 模塊化開發(fā)的流程
- new compute.js 創(chuàng)建模塊(一個模塊就一個文件)
- module.exports = {} 導(dǎo)出成員
- var comp = require('./compute.js') 載入模塊
- comp.add(1, 1) 使用模塊
五赠涮、定義模塊
- 模塊內(nèi)全局環(huán)境
- __dirname
用于獲取當(dāng)前文件所在目錄的完整路徑
在 REPL 環(huán)境無效
- __filename
用來獲取當(dāng)前文件的完整路徑
在 REPL 環(huán)境同樣無效
- module
模塊對象
- exports
映射到module.exports的別名
- require()
require.cache
require.extensions
require.main
require.resolve()
文件操作中必須使用絕對路徑;
- module 對象
Node 內(nèi)部提供一個 Module 構(gòu)建函數(shù)子寓。所有模塊都是 Module 的實例;
– module.id 模塊的識別符笋除,通常是帶有絕對路徑的模塊文件名;
- module.filename 模塊定義的文件的絕對路徑;
– module.loaded 返回一個布爾值斜友,表示模塊是否已經(jīng)完成加載;
– module.parent 返回一個對象,表示調(diào)用該模塊的模塊;
– module.children 返回一個數(shù)組垃它,表示該模塊要用到的其他模塊;
– module.exports 表示模塊對外輸出的值;
- 模塊的定義
- 一個新的 JS 文件就是一個模塊;
- 一個合格的模塊應(yīng)該是有導(dǎo)出成員的鲜屏,否則模塊就失去了定義的價值;
- 模塊內(nèi)部是一個獨立(封閉)的作用域(模塊與模塊之間不會沖突);
- 模塊之間必須通過導(dǎo)出或?qū)氲姆绞絽f(xié)同;
- 導(dǎo)出方式
exports.name = value;
module.exports = { name: value };
- module.exports是用于為模塊導(dǎo)出成員的接口;
- exports是指向module.exports的別名,相當(dāng)于 在模塊開始的時候執(zhí)行
var exports = module.exports;
- 一旦為 module.exports 賦值国拇,就會切斷之前兩者的相關(guān)性;
- 最終模塊的導(dǎo)出成員以 module.exports 為準(zhǔn);
1洛史、每個模塊的內(nèi)部都是私有空間,不會污染全局作用域酱吝;
2也殖、模塊可以多次加載,但是只會在第一次加載時運行一次务热, 然后運行結(jié)果就被緩存了忆嗜,以后再加載,就直接讀取緩 存結(jié)果崎岂;
3霎褐、模塊加載的順序,按照其在代碼中出現(xiàn)的順序该镣;
六冻璃、載入模塊
require是什么
require 的基本功能是,讀入并執(zhí)行一個JavaScript文件损合,然后返回該模塊的exports對象;require擴展名
require 加載文件時可以省略擴展名;
require('./module');
// 1省艳、此時文件按 JS 文件執(zhí)行
require('./module.js');
// 2、此時文件按 JSON 文件解析
require('./module.json');
// 3嫁审、此時文件預(yù)編譯好的 C++ 模塊執(zhí)行
require('./module.node');
- require加載文件規(guī)則
- 通過 ./ 或 ../ 開頭:則按照相對路徑從當(dāng)前文件所在文件夾開始尋找模塊
require('../file.js'); // 上級目錄下找 file.js 文件
- 通過 / 開頭:則以系統(tǒng)根目錄開始尋找模塊
require('/Users/zyz/Documents/file.js'); // 以絕對路徑的方式找
- 如果參數(shù)字符串不以“./“ 或 ”/“ 開頭跋炕,則表示加載 的是一個默認(rèn)提供的核心模塊(位于 Node 的系統(tǒng)安 裝目錄中)
require('fs'); // 加載核心模塊中的文件系統(tǒng)模塊
- 或者從當(dāng)前目錄向上搜索 node_modules 目錄中的文件
require('my_module'); // 各級 node_modules文件夾中搜索 my_module.js 文件;
require('my_module'); 開始是在核心模塊中查找,這個其實不是核心模塊律适;接著各級 node_modules文件夾中搜索
- 模塊的緩存
- 第一次加載某個模塊時辐烂,Node會緩存該模塊。以后再 加載該模塊捂贿,就直接從緩存取出該模塊的 module.exports 屬性(不會再次執(zhí)行該模塊)
- 模塊的緩存可以通過require.cache拿到纠修,同樣也可以刪除
// 模塊的緩存的刪除
Object.keys(require.cache).forEach( (key) => {
delete require.cache[key];
} );
- 如果需要多次執(zhí)行模塊中的代碼,一般可以讓模塊暴露行為(函數(shù))【避免每次都要清理緩存的問題】
// 緩存的問題 -- 方式1
module.exports = () => {
return {time: new Date()};
}
// 緩存的問題 -- 方式2
function fn(){
return {time: new Date()};
}
module.exports = {fn};