Node模塊原理分析
-
1.Node模塊
- 1.1在CommonJS規(guī)范中一個(gè)文件就是一個(gè)模塊
- 1.2在CommonJS規(guī)范中通過(guò)exports暴露數(shù)據(jù)
- 1.3在CommonJS規(guī)范中通過(guò)require()導(dǎo)入模塊
-
2.Node模塊原理分析
- 既然一個(gè)文件就是一個(gè)模塊,既然想要使用模塊必須先通過(guò)require()導(dǎo)入模塊.所以可以推斷出require()的作用其實(shí)就是讀取文件,所以要想了解Node是如何實(shí)現(xiàn)模塊的,必須先了解如何執(zhí)行讀取到的代碼
-
3.執(zhí)行從文件中讀取代碼
- 我們都知道通過(guò)fs模塊可以讀取文件,但是讀取到的數(shù)據(jù)要么是二進(jìn)制,要么是字符串,無(wú)論是二進(jìn)制還是字符串都無(wú)法直接執(zhí)行
- 但是我們知道如果是字符串,在JS中是有辦法讓它執(zhí)行的
- eval
- 缺點(diǎn):存在依賴關(guān)系,字符串可以訪問(wèn)外界數(shù)據(jù),不安全
- new Function;
- 缺點(diǎn):存在依賴關(guān)系,依然可以訪問(wèn)全局?jǐn)?shù)據(jù),不安全
- 通過(guò)NodeJS的vm虛擬機(jī)執(zhí)行代碼(==推薦==,具體看4)
- eval
let name1 = 'ws';
let str1 = "console.log(name1);";
eval(str1); // ws 能訪問(wèn)外部變量name1
let name2 = 'ws666';
let str2 = "console.log(name2);";
let fn = new Function(str2);
fn(); // ws666 能訪問(wèn)外部變量name2
- 4.通過(guò)NodeJS的vm虛擬機(jī)執(zhí)行代碼
-
runInThisContext()
- 提供了一個(gè)安全的環(huán)境給我們執(zhí)行字符串中的代碼
- 提供的環(huán)境==不能訪問(wèn)本地的變量,但是可以訪問(wèn)全局的變量==(也就是global上的變量)
-
runInNewContext()
無(wú)權(quán)訪問(wèn)外部變量,也不能訪問(wèn)global- 提供了一個(gè)安全的環(huán)境給我們自行字符串中的代碼
- 提供的環(huán)境==不能訪問(wèn)本地的變量,也不能訪問(wèn)全局的變量==(也就是global上的變量)
-
let vm = require('vm');
// let str = 'ws';
// let res = 'console.log(str)';
// vm.runInThisContext(res); // str is not defined
// global.str = 'ws';
// let res = 'console.log(str)';
// vm.runInThisContext(res); // ws
// let str = 'ws';
// let res = 'console.log(str)';
// vm.runInNewContext(res); // str is not defined
global.str = 'ws';
let res = 'console.log(str)';
vm.runInNewContext(res); // str is not defined
Node模塊官方加載流程分析
以下為扒的官方模塊
- 1.內(nèi)部實(shí)現(xiàn)了一個(gè)require方法
function require(path) {
return self.require(path);
}
- 2.通過(guò)Module對(duì)象的靜態(tài)__load方法加載模塊文件
Module.prototype.require = function(path) {
return Module._load(path, this, /* isMain */ false);
};
- 3.通過(guò)Module對(duì)象的靜態(tài)_resolveFilename方法, 得到絕對(duì)路徑并添加后綴名
var filename = Module._resolveFilename(request, parent, isMain);
- 4.根據(jù)路徑判斷是否有緩存,如果沒有就創(chuàng)建一個(gè)新的Module模塊對(duì)象并緩存起來(lái)
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
var module = new Module(filename, parent);
Module._cache[filename] = module;
function Module(id, parent) {
this.id = id;
this.exports = {};
}
-
5.利用tryModuleLoad方法加載模塊
tryModuleLoad(module, filename);
5.1取出模塊后綴
var extension = path.extname(filename);
5.2根據(jù)不同后綴查找不同方法并執(zhí)行對(duì)應(yīng)的方法, 加載模塊
Module._extensions[extension](this, filename);
5.3如果是JSON就轉(zhuǎn)換成對(duì)象
module.exports = JSON.parse(internalModule.stripBOM(content));
5.4如果是JS就包裹一個(gè)函數(shù)
var wrapper = Module.wrap(content); NativeModule.wrap = function(script) { return NativeModule.wrapper[0] + script + NativeModule.wrapper[1]; }; NativeModule.wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ];
5.5執(zhí)行包裹函數(shù)之后的代碼,拿到執(zhí)行結(jié)果(String -- Function)
var compiledWrapper = vm.runInThisContext(wrapper);
5.6利用call執(zhí)行fn函數(shù),修改module.exports的值
var args = [this.exports, require, module, filename, dirname]; var result = compiledWrapper.call(this.exports, args);
- 5.7返回module.exports
return module.exports;
EventLOOP 事件環(huán)
瀏覽器事件環(huán)
-
1.JS是單線程的
- JS中的代碼都是串行的,前面沒有執(zhí)行完畢后面不能執(zhí)行
-
2.執(zhí)行順序
- 2.1程序運(yùn)行會(huì)從上至下依次執(zhí)行所有的同步代碼
- 2.2在執(zhí)行的過(guò)程中如果遇到異步代碼會(huì)將異步代碼放到事件循環(huán)中
- 2.3當(dāng)所有同步代碼都執(zhí)行完畢后,JS會(huì)不斷檢測(cè)事件循環(huán)中的異步代碼是否滿足條件
- 2.4一旦滿足條件就執(zhí)行滿足條件的異步代碼
-
3.宏任務(wù)和微任務(wù)
- 在JS的異步代碼中又區(qū)分"宏任務(wù)(MacroTask)"和"微任務(wù)(MicroTask)"
- 宏任務(wù):宏/大的意思,可以理解為比較費(fèi)時(shí)比較慢的任務(wù)
- 微任務(wù):微/小的意思,可以理解為相對(duì)沒那么費(fèi)時(shí)沒那么慢的任務(wù)
- 在JS的異步代碼中又區(qū)分"宏任務(wù)(MacroTask)"和"微任務(wù)(MicroTask)"
-
4.常見的宏任務(wù)和微任務(wù)
-
MacroTask
:setTimeout, setInterval, setImmediate(IE獨(dú)有)...
-
MicroTask
:Promise, MutationObserver(監(jiān)聽節(jié)點(diǎn)變化) ,process.nextTick(node獨(dú)有) ...
- 注意點(diǎn):
- 所有的宏任務(wù)和微任務(wù)都會(huì)放到自己的執(zhí)行隊(duì)列中,也就是有一個(gè)宏任務(wù)隊(duì)列和一個(gè)微任務(wù)隊(duì)列
- 所有放到隊(duì)列中的任務(wù)都采用"先進(jìn)先出原則", 也就是多個(gè)任務(wù)同時(shí)滿足條件,那么會(huì)先執(zhí)行先放進(jìn)去的
-
-
5.完整執(zhí)行順序
- 1.從上至下執(zhí)行所有同步代碼
- 2.==在執(zhí)行過(guò)程中遇到宏任務(wù)就放到宏任務(wù)隊(duì)列中,遇到微任務(wù)就放到微任務(wù)隊(duì)列中==
- 3.當(dāng)所有同步代碼執(zhí)行完畢之后,就執(zhí)行微任務(wù)隊(duì)列中滿足需求所有回調(diào)
- 4.當(dāng)微任務(wù)隊(duì)列所有滿足需求回調(diào)執(zhí)行完畢之后, 就執(zhí)行宏任務(wù)隊(duì)列中滿足需求所有回調(diào)
- 注意點(diǎn):
- ==每執(zhí)行完一個(gè)宏任務(wù)都會(huì)立刻檢查微任務(wù)隊(duì)列有沒有被清空, 如果沒有就立刻清空==
NodeJS事件環(huán)
- 1.概述
- 和瀏覽器中一樣NodeJS中也有事件環(huán)(Event Loop),但是由于執(zhí)行代碼的宿主環(huán)境和應(yīng)用場(chǎng)景不同,所以兩者的事件環(huán)也有所不同.
擴(kuò)展閱讀: 在NodeJS中使用libuv實(shí)現(xiàn)了Event Loop.
源碼地址: https://github.com/libuv/libuv
別看了C/C++語(yǔ)言寫的, 你現(xiàn)在看不懂
- 2.NodeJS事件環(huán)和瀏覽器事件環(huán)區(qū)別
- 2.1任務(wù)隊(duì)列個(gè)數(shù)不同
- 瀏覽器事件環(huán)有2個(gè)事件隊(duì)列(宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列)
- NodeJS事件環(huán)有6個(gè)事件隊(duì)列
- 2.2微任務(wù)隊(duì)列不同
- 瀏覽器事件環(huán)中有專門存儲(chǔ)微任務(wù)的隊(duì)列
- NodeJS事件環(huán)中沒有專門存儲(chǔ)微任務(wù)的隊(duì)列
- 2.3微任務(wù)執(zhí)行時(shí)機(jī)不同
- 瀏覽器事件環(huán)中每執(zhí)行完一個(gè)宏任務(wù)都會(huì)去清空微任務(wù)隊(duì)列
- NodeJS事件環(huán)中只有同步代碼執(zhí)行完畢和其它隊(duì)列之間切換的時(shí)候回去清空微任務(wù)隊(duì)列
- 切換隊(duì)列:當(dāng)隊(duì)列為空(已經(jīng)執(zhí)行完畢或者沒有滿足條件回到),或者執(zhí)行的回調(diào)函數(shù)數(shù)量到達(dá)系統(tǒng)設(shè)定的閾值時(shí)任務(wù)隊(duì)列就會(huì)切換
- 2.4微任務(wù)優(yōu)先級(jí)不同
- 瀏覽器事件環(huán)中如果多個(gè)微任務(wù)同時(shí)滿足執(zhí)行條件, 采用先進(jìn)先出
- NodeJS事件環(huán)中如果多個(gè)微任務(wù)同時(shí)滿足執(zhí)行條件, 會(huì)按照優(yōu)先級(jí)執(zhí)行
- 在NodeJS中process.nextTick微任務(wù)的優(yōu)先級(jí)高于Promise.resolve微任務(wù)
- 2.1任務(wù)隊(duì)列個(gè)數(shù)不同
2.1NodeJS中的任務(wù)隊(duì)列
┌───────────────────────┐
┌> │timers │執(zhí)行setTimeout() 和 setInterval()中到期的callback
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │pending callbacks │執(zhí)行系統(tǒng)操作的回調(diào), 如:tcp, udp通信的錯(cuò)誤callback
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │idle, prepare │ 只在內(nèi)部使用
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │poll │ 執(zhí)行與I/O相關(guān)的回調(diào)(除了close回調(diào)跟衅、定時(shí)器回調(diào)和setImmediate()之外攀芯,幾乎所有回調(diào)都執(zhí)行);
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │check │執(zhí)行setImmediate的callback
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└─┤close callbacks │執(zhí)行close事件的callback蒸殿,例如socket.on("close",func)
└───────────────────────┘
2.2 nodejs環(huán)境中沒有宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列的概念
宏任務(wù)被放到了不同的隊(duì)列中, 但是沒有隊(duì)列是存放微任務(wù)的隊(duì)列
┌───────────────────────┐
┌> │timers │執(zhí)行setTimeout() 和 setInterval()中到期的callback
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │poll │執(zhí)行與I/O相關(guān)的回調(diào)(除了close回調(diào)、定時(shí)器回調(diào)和setImmediate()之外,幾乎所有回調(diào)都執(zhí)行);
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└─┤check │執(zhí)行setImmediate的callback
└───────────────────────┘
2.3 同步代碼執(zhí)行完畢和其它隊(duì)列之間切換的時(shí)候回去清空微任務(wù)隊(duì)列
┌───────────────────────┐
| 同步代碼 |
└──────────┬────────────┘
│
│ <---- 滿足條件微任務(wù)代碼
│
┌──────────┴────────────┐
┌> │timers │執(zhí)行setTimeout() 和 setInterval()中到期的callback
│ └──────────┬────────────┘
│ │
│ │ <---- 滿足條件微任務(wù)代碼
│ │
│ ┌──────────┴────────────┐
│ │poll │執(zhí)行與I/O相關(guān)的回調(diào)(除了close回調(diào)终畅、定時(shí)器回調(diào)和setImmediate()之外,幾乎所有回調(diào)都執(zhí)行);
│ └──────────┬────────────┘
│ │
│ │ <---- 滿足條件微任務(wù)代碼
│ │
│ ┌──────────┴────────────┐
└─ ┤check │執(zhí)行setImmediate的callback
└───────────────────────┘
- ==注意點(diǎn)==:
- 執(zhí)行完poll, 會(huì)查看check隊(duì)列是否有內(nèi)容, 有就切換到check
- 如果check隊(duì)列沒有內(nèi)容, 就會(huì)查看timers是否有內(nèi)容,有就切換到timers
- 如果check隊(duì)列和timers隊(duì)列都沒有內(nèi)容,為了避免資源浪費(fèi)就會(huì)==阻塞在poll==
- 執(zhí)行完poll, 會(huì)查看check隊(duì)列是否有內(nèi)容, 有就切換到check
自定義本地包和全局包
-
1.包的規(guī)范(了解)
- package.json必須在包的頂層目錄下
- 二進(jìn)制文件應(yīng)該在bin目錄下
- JavaScript代碼應(yīng)該在lib目錄下
- 文檔應(yīng)該在doc目錄下
- 單元測(cè)試應(yīng)該在test目錄下
-
2.package.json字段分析(了解)
- name:包的名稱竟闪,必須是唯一的离福,由小寫英文字母、數(shù)字和下劃線組成炼蛤,不能包含空格
- description:包的簡(jiǎn)要說(shuō)明
- version:符合語(yǔ)義化版本識(shí)別規(guī)范的版本字符串
- 主版本號(hào):當(dāng)你做了不兼容的 API 修改
- 子版本號(hào):當(dāng)你做了向下兼容的功能性新增
- 修訂號(hào):當(dāng)你做了向下兼容的問(wèn)題修正
- main:入口文件妖爷,一般是index.js
- scripts:指定了運(yùn)行腳本命令的npm命令行縮寫,默認(rèn)是空的test
- keywords:關(guān)鍵字?jǐn)?shù)組理朋,通常用于搜索
- maintainers:維護(hù)者數(shù)組赠涮,每個(gè)元素要包含name子寓、email(可選)、web(可選)字段
- contributors:貢獻(xiàn)者數(shù)組笋除,格式與maintainers相同斜友。包的作者應(yīng)該是貢獻(xiàn)者數(shù)組的第一- 個(gè)元素
- bugs:提交bug的地址,可以是網(wǎng)站或者電子郵件地址
- licenses:許可證數(shù)組垃它,每個(gè)元素要包含type(許可證名稱)和url(鏈接到許可證文本的- 地址)字段
- repositories:倉(cāng)庫(kù)托管地址數(shù)組鲜屏,每個(gè)元素要包含type(倉(cāng)庫(kù)類型,如git)国拇、url(倉(cāng)- 庫(kù)的地址)和path(相對(duì)于倉(cāng)庫(kù)的路徑洛史,可選)字段
- dependencies:生產(chǎn)環(huán)境包的依賴,一個(gè)關(guān)聯(lián)數(shù)組酱吝,由包的名稱和版本號(hào)組成
- devDependencies:開發(fā)環(huán)境包的依賴也殖,一個(gè)關(guān)聯(lián)數(shù)組,由包的名稱和版本號(hào)組成
-
3.自定義包實(shí)現(xiàn)步驟
- 1.創(chuàng)建一個(gè)包文件夾
- 2.初始化一個(gè)package.json文件
- 3.初始化一個(gè)包入口js文件
- ==注意點(diǎn)==:
- 如果沒有配置main,默認(rèn)會(huì)將index.js作為入口
- 如果包中沒有index.js,那么就必須配置main
- ==注意點(diǎn)==:
- 4.根據(jù)包信息配置package.json文件
- 注意點(diǎn):
- 通過(guò)scripts可以幫我們記住指令,然后通過(guò)npm run xxx方式就可以執(zhí)行該指令
- 如果指令的名稱叫做start或者test,那么執(zhí)行的時(shí)候可以不加run
- 注意點(diǎn):
- 5.給package.json添加bin屬性,告訴系統(tǒng)執(zhí)行全局命令時(shí)需要執(zhí)行哪一個(gè)JS文件
- 6.在全局命令執(zhí)行的JS文件中添加
#! /usr/bin/env node
- 7.通過(guò)
npm link
將本地包放到全局方便我們調(diào)試
-
4.將自定義包發(fā)布到官網(wǎng)
- 1.在https://www.npmjs.com/注冊(cè)賬號(hào)
- 2.在終端輸入npm addUser
- 3.在終端輸入npm publish