[TOC]
# 簡介
簡單的說 Node.js 就是運行在服務端的 JavaScript。
Node.js 是一個基于Chrome JavaScript 運行時建立的一個平臺。
Node.js是一個事件驅動I/O服務端JavaScript環境,基于Google的V8引擎,V8引擎執行Javascript的速度非常快,性能非常好。
> [Node.js ECMAScript兼容性表](https://node.green/)
> [Node.js 中 LTS 和 Current 的有啥區別?](https://zhuanlan.zhihu.com/p/43213315)
# Node 架構圖
Node.js主要有兩種類型的組件 - 核心組件和node.js API(模塊)。 核心組件使用C和C ++編寫,node.js API使用JavaScript編寫。 Node.js架構圖如下:

## Node.js API
這些是用 JavaScript 編寫的,并直接暴露給外部世界,以與 Node.js 內部組件進行交互。 Node.js Binding 是核心API,它將JavaScript與C / C ++庫綁定。
## C/C++ Add-ons
您還可以使用C / C ++開發 Node.js 插件(Add-ons)以使用Node.js。
## V8
https://v8.dev/
它是 Google 的開源 JavaScript 引擎, 用C ++ 編寫。 實際上,它是一個 JavaScript VM,它不是解釋 JS 代碼 而是將 JavaScript 代碼編譯為本機代碼。 它是 JavaScript 中最快的JIT(Just-In-Time)編譯器。
## Libuv

它是一**多平臺支持 C++ 庫**,它封裝了 Libev、Libeio 以及 IOCP,保證了跨平臺的通用性。它負責處理 Node.js 中的線程池,事件循環和異步 I/O 操作。 在 Node.js 中,阻塞 I/O 操作被委托給 Libuv 模塊,Libuv 模塊具有固定大小的 C++ 線程池來處理這些操作。 完成這些操作后,會通知事件循環。
想深入的話:推薦書籍:《Node.js:來一打C++擴展》
>[兄 déi,libuv 了解一下](https://zhuanlan.zhihu.com/p/50497450)
## C-ares
一個用于并行處理異步DNS請求、名稱解析和多個DNS查詢的 C 庫。
## http_parser
一個用于解析HTTP請求和響應的C庫。
## OpenSSL
一個用于實現安全套接字層(SSL v2/v3)和傳輸層安全性(TLS v1)協議的C庫。 它還提供了所有必要的加密方法,如哈希,密碼,解密,簽名和驗證等。
## Zlib
一個用于數據壓縮和解壓縮的C庫。
# node 是單線程的嗎?
提到 node,我們就可以立刻想到單線程、異步 IO、事件驅動等字眼。首先要明確的是 node 真的是單線程的嗎,如果是單線程的,那么異步 IO,以及定時事件(setTimeout、setInterval 等)又是在哪里被執行的。
Node 中最核心的是 v8 引擎,在 Node 啟動后,會創建 v8 的實例,這個實例是多線程的。包括:
```
主線程:編譯、執行代碼。
編譯 / 優化線程:在主線程執行的時候,可以優化代碼。
分析器線程:記錄分析代碼運行時間,為 Crankshaft 優化代碼執行提供依據。
垃圾回收的幾個線程。
....
```
我們平時所說的**Node 是單線程的指的是 JavaScript 的執行是單線程的(開發者編寫的代碼運行在單線程環境中)**。Javascript 的宿主環境,無論是 Node 還是瀏覽器都是多線程的。
* 其他異步 IO 和事件驅動相關的線程通過 libuv 來實現內部的線程池和線程調度。
解釋:nodejs 執行 異步 IO 等操作時,會先從 js 代碼通過 **node-bindings** 調用到 C/C++ 代碼,然后通過 C/C++ 代碼封裝一個叫 “請求對象”(**傳入的參數和回調函數封裝成一個請求對象**) 的東西交給 `libuv`,給 `libuv` 執行以及執行完實現回調。
* libv 中存在了一個 Event Loop,通過 Event Loop 來切換實現類似于多線程的效果。
Event Loop 是 libuv 的核心所在,上面我們提到 js 會把回調和任務交給 libuv,**libuv 何時來調用回調就是 Event Loop 來控制的**。
簡單的來講 Event Loop 就是維持一個執行棧和一個事件隊列,當前執行棧中的如果發現異步 IO 以及定時器等函數,就會把這些異步回調函數放入到事件隊列中。當前執行棧執行完成后,從事件隊列中,按照一定的順序執行事件隊列中的異步回調函數。

上圖中從執行棧,到事件隊列,最后事件隊列中按照一定的順序執行回調函數,整個過程就是一個簡化版的 Event Loop。此外回調函數執行時,同樣會生成一個執行棧,在回調函數里面還有可能嵌套異步的函數,也就是說執行棧存在著嵌套。
也就是說 node 中的單線程是指 js 引擎只在唯一的主線程上運行,其他的異步操作,也是有獨立的線程去執行,**通過 `libv` 的 Event Loop 實現了類似于多線程的上下文切換以及線程池調度**。線程是最小的進程,因此 node 也是單進程的。這樣就解釋了為什么 node 是單線程和單進程的。
> [nodejs 真的是單線程嗎?](https://segmentfault.com/a/1190000014926921)
# Node.js的文件路徑
> * `__dirname` : 獲得當前執行文件所在目錄的完整目錄名
> * `__filename` : 獲得當前執行文件的帶有完整絕對路徑的文件名
> * `process.cwd()`:獲得當前執行node命令時候的文件夾目錄名
> * `./`:不使用 `require` 時候,`./` 與 `process.cwd()` 一樣,使用 `require` 時候,與 `__dirname` 一樣
只有在?`require()`?時才使用相對路徑`(./, ../)`的寫法,其他地方一律使用絕對路徑,如下:
```
// 當前目錄下
path.dirname(__filename) + '/path.js';
// 相鄰目錄下
path.resolve(__dirname, '../regx/regx.js');
```
最后看看改過之后的結果,不會報錯找不到文件了,不管在哪里執行這個腳本文件,都不會出錯了,防止以后踩坑。
# global 模塊
Node 中我們有 `global` 對象可以進行掛載,很多共用的屬性就可以掛載到 `global` 對象上了,本身它自己也擁有很多的屬性。

## `global`
全局命名空間,通過 `global` 定義的變量,在任何地方都可以使用,類似于瀏覽器端定義在全局范圍中的變量。
```
// foo.js
global.foo = 'hello';
```
```
// bar.js
require('./foo');
console.log(foo);
//hello
```
定義在 `global` 上面的變量,不需要在模塊中通過 `exports` 輸出,其他模塊中也能使用。
## `__dirname`
`__dirname` 實際上不是一個全局變量,在命令行模式下直接調用會提示 `__dirname` 未定義,但是在模塊中可以直接使用,返回當前腳本執行的目錄。
```
console.log(__dirname);
```
## `__filename`
返回當前執行代碼文件的名稱(包含文件的絕對路徑)。和 `__dirname` 一樣,`__filename` 也不是一個全局變量,但在模塊中可以直接使用。
```
console.log(__filename);
```
`__filename` 返回的是包含路徑的文件名。
# node 中的 `this`
在 nodejs 中的 `this` 而非 javascript 中的 `this`,nodejs 中的 `this` 和在瀏覽器中 javascript 中的 `this` 是不一樣的。
1、**全局中的 `this` 默認是一個空對象**。并且在全局中 `this` 與 `global` 對象沒有任何的關系:
```
console.log(this); {}
this.num = 10;
console.log(this.num); // 10
console.log(global.num); // undefined
```
2、在函數中的 `this`
```
function fn(){
this.num = 10;
}
fn();
console.log(this); // {}
console.log(this.num); // undefined
console.log(global.num); // 10
```
在函數中 `this` 指向的是 `global` 對象,和全局中的 `this` 不是同一個對象,簡單來說,你在函數中通過 `this` 定義的變量就是相當于給 `global` 添加了一個屬性,此時與全局中的 `this` 已經沒有關系了。
如果不相信,看下面這段代碼可以證明。
```
function fn(){
function fn2(){
this.age = 18;
}
fn2();
console.log(this); // global
console.log(this.age); // 18
console.log(global.age); // 18
}
fn();
```
3、構造函數中的 `this`
```
function Fn(){
this.num = 998;
}
var fn = new Fn();
console.log(fn.num); // 998
console.log(global.num); // undefined
```
在構造函數中 `this` 指向的是它的實例,而不是 `global` 。
4、全局中的 `this` 指向的是 `module.exports`。
```
this.num = 10;
console.log(module.exports); {num:10}
console.log(module.exports.num);
```
# nodejs中require的路徑是一個文件夾時發生了什么
## 參考
[nodejs的require模塊及路徑](http://www.cnblogs.com/pigtail/archive/2013/01/14/2859929.html)
http://blog.csdn.net/theanarkh/article/details/54783375
# `require`和`import`的區別
ES6標準發布后,module成為標準,標準的使用是以export指令導出接口,以import引入模塊,但是在我們一貫的node模塊中,我們采用的是CommonJS規范,使用require引入模塊,使用module.exports導出接口。
不把require和import整清楚,會在未來的標準編程中死的很難看。
請記住,現在還沒有JavaScript引擎,可以原生支持ES6模塊,在不久的未來應該可以支持。你說自己在使用Babel。默認情況下,`Babel` 轉化`import` 和 `export` 聲明為 CommonJS (`require/module.exports`) 。因此,即使您使用了ES6模塊語法,如果您在Node中運行代碼,其實您將會使用轉化后的CommonJS。
CommonJS允許您動態加載`require`模塊。甚至不需要賦值給某個變量之后再使用,比如:
```js
require('./a')(); // a模塊是一個函數,立即執行a模塊函數
var data = require('./a').data; // a模塊導出的是一個對象
var a = require('./a')[0]; // a模塊導出的是一個數組
```
你在使用時,完全可以忽略模塊化這個概念來使用require,僅僅把它當做一個node內置的全局函數,它的參數甚至可以是表達式:
```js
require(process.cwd() + '/a');
```
但是ES6`import`的則不同,它必須放在文件開頭。它不會將整個模塊運行后賦值給某個變量,而是只選擇import的接口進行編譯,這樣在性能上比require好很多。
`import` 具有聲明提升效果,會首先執行。所以最好不要混用`import`和`require`。 import 是 ES6 標準,如果可能,首先使用 import, 如果不行,就用`require`。
因為ES6 模塊是標準,所以現在還是推薦使用`import`,避免未來不必要的更改。
### 參考
[編寫瀏覽器和Node.js通用的JavaScript模塊](http://harttle.com/2016/10/12/js-modules-for-browser-and-node.html)
[不要混用 import 和 require](https://robin-front.github.io/2017/07/10/dont-mixin-import-and-require/)
# `exports` 和 `module.exports` 的區別
node系統會自動給 執行文件增加2個變量 `exports` 和 `module`, 同時,`module`對象會創建一個叫`exports`的屬性。
兩者唯一的關系:默認他們初始都指向同一個空對象`{}`:
于是就有了
```
exports => {} <=module.exports.
```
如果其中一個**不指向這個空對象了, 那么他們的關系就沒有了**。
其實大家用內存塊的概念去理解,就會很清楚了。
然后呢,為了避免糊涂,盡量都用 `module.exports` 導出,然后用 `require` 導入。
## 示例
那么,這樣寫是沒問題的:
```
exports.name = function(x){
console.log(x);
};
//和下面這個一毛一樣,因為都是修改的同一內存地址里的東西
module.exports.name = function(x){
console.log(x);
};
```
但是這樣寫就有了區別了:
```
exports = function(x){
console.log(x);
};
// 上面的 function 是一塊新的內存地址,導致exports與module.exports不存在任何關系,而require方能看到的只有 module.exports 這個對象,看不到exports對象,所以這樣寫是導不出去的。
//下 面的寫法是可以導出去的。說句題外話,module.exports 除了導出對象,函數,還可以導出所有的類型,比如字符串、數值等。
module.exports = function(x){
console.log(x);
};
```
如果 `module.exports` 已經被改變了,那么 `exports` 上的所有屬性都會被忽略。
## 總結
用白話講就是,`exports` 只輔助 `module.exports` 操作內存中的數據,辛辛苦苦各種操作數據完,累得要死,結果到**最后真正被 `require` 出去的內容還是 `module.exports` 的**,真是好苦逼啊。
如果你只是添加方法或屬性,只要操作`exports`就可以了。
除非您打算將模塊的對象類型從傳統的 ‘module instance’ 更改為其他對象類型,否則 `exports` 是官方推薦的(如果你的模塊是一個典型的node模塊實例,那么使用 `exports`。)。
## 參考
> [export 和 module.export 的區別](https://www.jianshu.com/p/e452203d56c4)
> [Node.js 模塊里 exports 與 module.exports 的區別?](https://www.zhihu.com/question/26621212)
[Node.js Module – exports vs module.exports](http://www.hacksparrow.com/node-js-exports-vs-module-exports.html)
> https://nodejs.org/api/modules.html#modules_module_exports
## 為什么會加上`.default`
**在文件中使用 CommonJS 模塊導入方式 require 引入 ESM,則需要使用`.default`來獲取實際的組件選項**
其中是因為 bable 轉換的變化:
babel@5 及之前的版本可以把`export`和`import`轉成 node 的`module.exports`和`require` ,
babel@6 版本開始不再把`export default`轉成 node 的`module.exports`,參考[https://github.com/babel/babel/issues/2212](https://github.com/babel/babel/issues/2212)。
如使用Babel@6 編譯下面的模塊:
~~~
export default 'router'
~~~
可得到以下編譯結果,你也可以打開[babeljs.io](https://babeljs.io/repl)在線編譯試試看:
~~~
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 'router';
~~~
因此,需要`require`形式引入模塊,需要添加`.default`:
~~~
require('./router.js') // {默認值:'router'}
require('./router.js').default // 'router'
~~~
## 解決
通過引入 [babel-plugin-add-module-exports](https://github.com/59naga/babel-plugin-add-module-exports) 這個plugin 可以解決這個問題,以下是編譯效果:
~~~
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 'router';
module.exports = exports['default'];
~~~
> [require一個node模塊加上.default](https://www.cnblogs.com/PeunZhang/p/12736940.html)
# glob 模式匹配
使用 extglob 的 [glob模式匹配](https://en.wikipedia.org/wiki/Glob_%28programming%29) 表示法,類似于 Git 處理 [`gitignore`](http://git-scm.com/docs/gitignore) 的規則和 [Bower](https://github.com/bower/bower) 處理 `ignore` 規則。 [此Wiki頁面](http://mywiki.wooledge.org/glob) 是更詳細的參考,以下是使用的示例的說明:
* `**` — 匹配任意子目錄中的任何文件或文件夾
* `**/.*` — 匹配任意子目錄中以'`.`'開頭的任何文件(通常是隱藏文件,例如`.git`文件夾中的文件)
* `**/*.@(jpg|jpeg|gif|png)` — 匹配任意子目錄中以以下任意一個結尾的任何文件:`.jpg`,`.jpeg`,`.gif`, 或者`.png`
> [firebase-full-config](https://firebase.google.com/docs/hosting/full-config#glob_pattern_matching)