💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[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)