node模塊原理

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)
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ù)
  • 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.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==

自定義本地包和全局包

  • 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
    • 4.根據(jù)包信息配置package.json文件
      • 注意點(diǎn):
        • 通過(guò)scripts可以幫我們記住指令,然后通過(guò)npm run xxx方式就可以執(zhí)行該指令
        • 如果指令的名稱叫做start或者test,那么執(zhí)行的時(shí)候可以不加run
    • 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末务热,一起剝皮案震驚了整個(gè)濱河市忆嗜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崎岂,老刑警劉巖捆毫,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冲甘,居然都是意外死亡绩卤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門江醇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)濒憋,“玉大人,你說(shuō)我怎么就攤上這事陶夜×萃裕” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵律适,是天一觀的道長(zhǎng)辐烂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捂贿,這世上最難降的妖魔是什么纠修? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮厂僧,結(jié)果婚禮上扣草,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好辰妙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布鹰祸。 她就那樣靜靜地躺著,像睡著了一般密浑。 火紅的嫁衣襯著肌膚如雪蛙婴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天尔破,我揣著相機(jī)與錄音街图,去河邊找鬼。 笑死懒构,一個(gè)胖子當(dāng)著我的面吹牛餐济,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胆剧,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼絮姆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了秩霍?” 一聲冷哼從身側(cè)響起篙悯,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎前域,沒想到半個(gè)月后辕近,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體韵吨,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匿垄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了归粉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椿疗。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖糠悼,靈堂內(nèi)的尸體忽然破棺而出届榄,到底是詐尸還是另有隱情,我是刑警寧澤倔喂,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布铝条,位于F島的核電站,受9級(jí)特大地震影響席噩,放射性物質(zhì)發(fā)生泄漏班缰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一悼枢、第九天 我趴在偏房一處隱蔽的房頂上張望埠忘。 院中可真熱鬧,春花似錦、人聲如沸莹妒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旨怠。三九已至渠驼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鉴腻,已是汗流浹背渴邦。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拘哨,地道東北人谋梭。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像倦青,于是被迫代替她去往敵國(guó)和親瓮床。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348