[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架构图如下:
![](https://img.kancloud.cn/2d/f6/2df6feeedebfc6a8c510b9dc213ea189_660x281.png)
## 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
![libuv](https://box.kancloud.cn/204fa36e4858953873b10854a70e6078_1024x768.png)
它是一**多平台支持 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 以及定时器等函数,就会把这些异步回调函数放入到事件队列中。当前执行栈执行完成后,从事件队列中,按照一定的顺序执行事件队列中的异步回调函数。
![](https://img.kancloud.cn/77/ec/77ec5f5a4de27096dc47a49e7d1aa7d6_474x336.png)
上图中从执行栈,到事件队列,最后事件队列中按照一定的顺序执行回调函数,整个过程就是一个简化版的 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` 对象上了,本身它自己也拥有很多的属性。
![](https://box.kancloud.cn/3f178b6bb1ad8a70894e73cce10bc9b0_845x339.png)
## `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)