node---模塊

目錄

背景介紹

模塊規範

模塊實現

核心模塊

模塊調用棧

包與NPM

背景介紹

在Web發展歷程中,瀏覽器湧現了豐富多彩的標準API供JavaScript調用,這些過程發生在前端,後端JavaScript的規範卻遠遠落後。

主要存在以下缺陷:

  • 沒有模塊系統
  • 標準庫較少
  • 沒有標準接口
  • 缺乏報管理系統

CommonJS規範提出,主要彌補當前JavaScript沒有標準的缺陷,以達到像Python、Ruby和Java、具備開發大型應用的基礎能力,而不是停留在小腳本程序的階段。他們期望那些用CommonJS API寫出的應用可以具備跨宿主環境執行的能力,這樣不僅可以利用Javacript開發客戶端應用,還可以編寫以下應用

  • 服務端JavaScript應用程序
  • 命令行工具
  • 桌面圖形界面應用程序
  • 混合應用

模塊規範

主要分爲模塊引用、模塊定義和模塊標識三個部分

# 模塊引用
require方法,引入一個模塊的API到當前上下文中
ex: let fs = require('fs');

# 模塊定義
exports對象用於導出當前模塊的方法或者變量, 並且是唯一導出的出口。在模塊中存在一個module對象,代表模塊本身,而exports是module的屬性

# 模塊標識
模塊標識其實就是傳給require方法的參數,它必須是符合小駝峯命名,或者以...開頭的相對路徑或者絕對路徑。

PS:每個模塊具有獨立的空間,他們互不干擾,在引用時也顯得乾淨利落。命名空間方案與之相比相形見絀

模塊實現

實現的過程主要分爲以下三部分:

​ 1) 路徑分析

​ 2) 文件定位

​ 3) 模塊編譯

Node在實現規範的同時,也增加了少許自身需要的特性。

# 寫在前面
在Node中,模塊分Node提供的核心模塊與用戶編寫的文件模塊兩種。同瀏覽器一樣,Node也存在緩存機制,只不過前者緩存的是文件,後者緩存的是引用過的模塊(編譯和執行之後的對象),兩者都是爲了減少二次引用時的開銷。而核心模塊的優點不僅僅在於它原生被Node自帶(fs、http、path),而且在緩存機制上也存在優先的待遇。
// 注意:二次引入模塊不需要路徑分析、文件定位以及編譯執行的過程

# 路徑分析
 A 核心模塊 ex:http、fs
 B ...開始的相對路徑文件模塊
 C/開始的絕對路徑文件模塊
 D 非路徑形式的文件模塊,如自定義的connect模塊
耗時排行:D > B | C > A
自定義模塊之所以最慢,是因爲存在一個路徑分析的性能消耗,如果require('xxx')的模塊不再當前模塊的node_modules裏面,會向上一層(父目錄)下的node_modules目錄查找,以此類推直到根下面的/node_modules找不到的話即報錯返回

# 文件定位
主要包括擴展名的分析、目錄和包的處理。CommonJS允許在標識符不包含文件擴展名,這種情況下Node會按.js、.json、.node的次序補足擴展名,依次嘗試。在分析標識符的過程中,requrie可能得到一個目錄,那麼會將目錄當成一個包來處理

包的處理方式:
 1. 是否存在package.json文件,存在的話獲取裏面main字段的值作爲文件定位的入口路徑
 2. 不存在package.json,會默認將index當作默認文件名,然後依次查找index.json,index.node

# 模塊編譯
在Node中,每個文件模塊都是一個對象。對不同文件(.js、.json、.node)的對象,處理方式也有所不同。
每一個編譯成功的模塊都會將其文件路徑作爲索引緩存在Module._cache對象上,以提高二次引入的性能。
模塊的定義
function Module(id, parent) {
	this.id = id; 
    this.exports = {};
    this.parent = parent;
    if(parant && parent.children) {
		parent.children.push(this);
	}
    this.filename = null;
    this.loaded = false;
    this.children = [];
}
Q:模塊定義裏面沒有__dirname、__filename、exports、module、require這三個變量?
A:在編譯過程中,Node對JavaScript文件內容進行了頭尾包裝,在資源函數裏面傳入了(exports, require, module, __filename, __dirname)這五大參數

Q:既然存在module.exports,那麼exports存在的意義何在?
A: exports與module.exports的關係就像前端JavaScript的export
有點不同的是,在前端導出多個屬性可以通過下面這種方式導出多個屬性
export = {
    [key]: value
}
後端JavaScript則是
exports[key]=value;
exports[key]=value;
而導出一個對象則是
module.exports = {
    [key]: value
}
最後在這裏要注意的是不能直接給exports賦值,因爲在模塊編譯裏面我們已經知道,exports對象是作爲一個形參傳進模塊函數的,如果直接給exports賦值,那麼require出來的對象雖然改變了引用,但是沒有改變作用域。

核心模塊

# 轉存爲C/C++代碼
Node採用了V8附帶的js2c.py工具,將所有內置的JavaScript代碼轉換成C++裏的數組,生成node_native.h頭文件。
在啓動Node進程時,JavaScript代碼直接加載進內存中,在加載的過程中,JavaScript核心模塊經歷標識符分析後直接定位到內存中,比普通的文件模塊從磁盤中一處一處查找要快很多

# C/C++核心模塊的編譯過程
在覈心模塊中,有些模塊全部由C/C++編寫,有些模塊則由C/C++完成核心部分,其它部分則由JavaScript實現包裝或向外導出,以滿足性能需求。後面這種C++模塊完成核心,JavaScript實現封裝的模式時Node能夠提高性能的常見方式。通常,腳本語言的開發速度優於靜態語言,但是其性能則弱於靜態語言。而Node的這種符合模式可以在開發速度和性能之間找到平衡點。我們將那些由純C/C++編寫的部分統一稱爲內建模塊,因爲他們通常不被用戶直接調用。Node的buffer、crypto、eval、fs、os等模塊都是部分通過C/C++編寫的。

# 內建模塊的導出
文件模塊可能會依賴核心模塊,核心模塊可能依賴內建模塊。不建議直接調用內建模塊。如需調用,直接調用核心模塊即可,因爲核心模塊中基本都封裝了內建模塊。Node在啓動時,會生成一個全局變量process,並提供Binding()方法來協助加載內建模塊。
require('os') > NativeModule.require('os') > process.binding('os') > get_builtin_module('node_os') > NODE_MODULE(node_os, reg_func)

# C/C++擴展模塊的加載
require('hello.node') > process.dlopen('hello.node', exports) > libuv(uv_dlopen/uv_dlsym) > 
    *nix > dlopen/dlsym
	windows > LoadLibraryExW/GetProcAddress

模塊調用棧

C/C++內建模塊 > JavaScript核心模塊 > JavaScript文件模塊
				   C/C++擴展模塊 >

包與NPM

Node對模塊規範的實現,一定程度上解決了變量依賴、依賴關係等代碼組織性問題。包的出現,則是在模塊的基礎上進一步組織JavaScript代碼。CommonJS的包規範的定義其實也很簡單,它由包結構和包描述兩個部分組成,前者用於組織包中的各種文件,後者則用於描述包的相關信息,以供外部獨區分析。

# 安裝依賴包
可以通過本地安裝,也可以通過--registry=安裝

# 包下面的bin目錄的作用
在npm或者yarn包管理器的全局包下面創建一個xxx.cmd,注意,環境變量一定要配上,不然執行不了哈~~,而且有一個比較奇葩的點就是bin下面的命令不能使用test,真實go die

# 規範
前端JavaScript有ECMAScript作爲規範,後端JavaScript有CommonJS,那麼出現了一個問題,前後端寫的包不能很好的互相兼容,然後就出現了AMDCMD規範,不過自從ES6出了包的規範後,這兩種規範的市場份額漸漸銷聲匿跡
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章