

[h1]起步[/h1]
教程:https://www.runoob.com/nodejs/nodejs-tutorial.html
事件循环:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
[h2]Linux安装node[/h2]
版本列表:https://nodejs.org/dist/,下载并解压:
xz -d node-v17.2.0-linux-x64.tar.xztar -xvf node-v17.2.0-linux-x64.tarxz -d node-v17.2.0-linux-x64.tar.xz tar -xvf node-v17.2.0-linux-x64.tarxz -d node-v17.2.0-linux-x64.tar.xz tar -xvf node-v17.2.0-linux-x64.tar
然后设置软连接:
ln -s/home/swoole/main/node/bin/npm /usr/local/bin/npmln -s/home/swoole/main/node/bin/node /usr/local/bin/nodeln -s/home/swoole/main/node/bin/npm /usr/local/bin/npm ln -s/home/swoole/main/node/bin/node /usr/local/bin/nodeln -s/home/swoole/main/node/bin/npm /usr/local/bin/npm ln -s/home/swoole/main/node/bin/node /usr/local/bin/node
[h2]1. 使用ES6[/h2]
使用E6语法引入模块,报错如上;依据报错提示,在package.json添加 “type”: “module”,然后再运行js文件,便不再报错。
[h2]2.关于Node[/h2]
- 每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。
- 主线程之外,还维护了一个”事件队列”(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
- 主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
- 主线程不断重复上面的第三步。
[h1]Node.js 如何处理 ES6 模块[/h1]
[success title=”资料来源”]https://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html[/success]
JS有多种格式的模块,一种是 ES6 模块,简称 ESM;另一种是 Node.js 专用的 CommonJS 模块,简称 CJS。这两种模块不兼容。
[h2]1.模块差异[/h2]
ES6 模块和 CommonJS 模块有很大的差异。
语法上面,CommonJS 模块使用require()加载和module.exports输出,ES6 模块使用import和export。
用法上面,require()是同步加载,后面的代码必须等待这个命令执行完,才会执行。import命令则是异步加载,或者更准确地说,ES6 模块有一个独立的静态解析阶段,依赖关系的分析是在那个阶段完成的,最底层的模块第一个执行。
[h2]2.Node.js 的区分[/h2]
Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定”use strict”。
如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。
Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定”use strict”。
如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。
{"type": "module"}{ "type": "module" }{ "type": "module" }
一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。
# 解释成 ES6 模块$ node my-app.js# 解释成 ES6 模块 $ node my-app.js# 解释成 ES6 模块 $ node my-app.js
如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成.cjs。如果没有type字段,或者type字段为commonjs,则.js脚本会被解释成 CommonJS 模块。
总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。
注意,ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import。
[h2]3.CommonJS 模块加载 ES6 模块[/h2]
CommonJS 的require()命令不能加载 ES6 模块,会报错,只能使用import()这个方法加载。
(async () => {await import('./my-app.mjs');})();(async () => { await import('./my-app.mjs'); })();(async () => { await import('./my-app.mjs'); })();
上面代码可以在 CommonJS 模块中运行。
[mark]require()[/mark]不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await
命令,导致无法被同步加载。
[h2]4.ES6 模块加载 CommonJS 模块[/h2]
ES6 模块的import命令可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项。
// 正确import packageMain from 'commonjs-package';// 报错import { method } from 'commonjs-package';// 正确 import packageMain from 'commonjs-package'; // 报错 import { method } from 'commonjs-package';// 正确 import packageMain from 'commonjs-package'; // 报错 import { method } from 'commonjs-package';
这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是module.exports
,是一个对象,无法被静态分析,所以只能整体加载。
加载单一的输出项,可以写成下面这样。
import packageMain from 'commonjs-package';const { method } = packageMain;import packageMain from 'commonjs-package'; const { method } = packageMain;import packageMain from 'commonjs-package'; const { method } = packageMain;
[h2]5.同时支持两种格式的模块[/h2]
一个模块同时要支持 CommonJS 和 ES6 两种格式,也很容易。
如果原始模块是 ES6 格式,那么需要给出一个整体输出接口,比如export default obj,使得 CommonJS 可以用import()进行加载。
如果原始模块是 CommonJS 格式,那么可以加一个包装层。
import cjsModule from '../index.js';export const foo = cjsModule.foo;import cjsModule from '../index.js'; export const foo = cjsModule.foo;import cjsModule from '../index.js'; export const foo = cjsModule.foo;
上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。
你可以把这个文件的后缀名改为.mjs,或者将它放在一个子目录,再在这个子目录里面放一个单独的package.json文件,指明{ type: “module” }。
另一种做法是在package.json文件的exports字段,指明两种格式模块各自的加载入口。
"exports":{"require": "./index.js","import": "./esm/wrapper.js"}"exports":{ "require": "./index.js", "import": "./esm/wrapper.js" }"exports":{ "require": "./index.js", "import": "./esm/wrapper.js" }
上面代码指定require()和import,加载该模块会自动切换到不一样的入口文件。
[h1]模块[/h1]
[h2]1.EventEmitter[/h2]
EventEmitter的功能类似前端事件总线,Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。
[lightbox title=”事件”][/lightbox]
Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。
//event.js 文件var EventEmitter = require('events').EventEmitter;var event = new EventEmitter();event.on('some_event', function() {console.log('some_event 事件触发');});setTimeout(function() {event.emit('some_event');}, 1000);//event.js 文件 var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() { console.log('some_event 事件触发'); }); setTimeout(function() { event.emit('some_event'); }, 1000);//event.js 文件 var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() { console.log('some_event 事件触发'); }); setTimeout(function() { event.emit('some_event'); }, 1000);
[h2]2.Buffer(缓冲区)[/h2]
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。
相关文档:https://www.runoob.com/nodejs/nodejs-buffer.html
[h2]3.Stream(流)[/h2]
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。
Node.js,Stream 有四种流类型:
- Readable – 可读操作。
- Writable – 可写操作。
- Duplex – 可读可写操作.
- Transform – 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data – 当有数据可读时触发。
- end – 没有更多的数据可读时触发。
- error – 在接收和写入过程中发生错误时触发。
- finish – 所有数据已被写入到底层系统时触发。
[h3]3.1管道流[/h3]
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
var fs = require("fs");// 创建一个可读流var readerStream = fs.createReadStream('input.txt');// 创建一个可写流var writerStream = fs.createWriteStream('output.txt');// 管道读写操作// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中readerStream.pipe(writerStream);console.log("程序执行完毕");var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log("程序执行完毕");var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log("程序执行完毕");
[h3]3.2链式流[/h3]
链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。
var fs = require("fs");var zlib = require('zlib');// 压缩 input.txt 文件为 input.txt.gzfs.createReadStream('input.txt').pipe(zlib.createGzip()).pipe(fs.createWriteStream('input.txt.gz'));console.log("文件压缩完成。");var fs = require("fs"); var zlib = require('zlib'); // 压缩 input.txt 文件为 input.txt.gz fs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('input.txt.gz')); console.log("文件压缩完成。");var fs = require("fs"); var zlib = require('zlib'); // 压缩 input.txt 文件为 input.txt.gz fs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('input.txt.gz')); console.log("文件压缩完成。");
[h2]4.全局对象[/h2]
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。
在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。
相关文档:https://www.runoob.com/nodejs/nodejs-global-object.html
[h2]5.其他模块[/h2]
- OS 模块,提供基本的系统操作函数。
- Path 模块,提供了处理和转换文件路径的工具。
- Net 模块,用于底层的网络通信。提供了服务端和客户端的的操作。
- DNS 模块,用于解析域名。
- Domain 模块,简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。
[h1]包[/h1]
[h2]1.dotenv库[/h2]
由于项目不同需求,需要配置不同环境变量,按需加载不同的环境变量文件,使用dotenv,可以完美解决这一问题。
Npm文档:https://www.npmjs.com/package/dotenv ,[mark]npm install dotenv –save[/mark]
Dotenv 是一个零依赖模块,它将项目根目录的环境变量从.env
文件加载到process.env
import 'dotenv/config'import 'dotenv/config'import 'dotenv/config'
[h2]2.Egg[/h2]
官方文档:https://www.eggjs.org/zh-CN/intro
[h2]3.http模块[/h2]
http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块
var http = require('http');var fs = require('fs');var url = require('url');// 创建服务器http.createServer( function (request, response) {// 解析请求,包括文件名var pathname = url.parse(request.url).pathname;// 输出请求的文件名console.log("Request for " + pathname + " received.");// 从文件系统中读取请求的文件内容fs.readFile(pathname.substr(1), function (err, data) {if (err) {console.log(err);// HTTP 状态码: 404 : NOT FOUND// Content Type: text/htmlresponse.writeHead(404, {'Content-Type': 'text/html'});}else{// HTTP 状态码: 200 : OK// Content Type: text/htmlresponse.writeHead(200, {'Content-Type': 'text/html'});// 响应文件内容response.write(data.toString());}// 发送响应数据response.end();});}).listen(8080);// 控制台会输出以下信息console.log('Server running at http://127.0.0.1:8080/');var http = require('http'); var fs = require('fs'); var url = require('url'); // 创建服务器 http.createServer( function (request, response) { // 解析请求,包括文件名 var pathname = url.parse(request.url).pathname; // 输出请求的文件名 console.log("Request for " + pathname + " received."); // 从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/html response.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/html response.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 response.write(data.toString()); } // 发送响应数据 response.end(); }); }).listen(8080); // 控制台会输出以下信息 console.log('Server running at http://127.0.0.1:8080/');var http = require('http'); var fs = require('fs'); var url = require('url'); // 创建服务器 http.createServer( function (request, response) { // 解析请求,包括文件名 var pathname = url.parse(request.url).pathname; // 输出请求的文件名 console.log("Request for " + pathname + " received."); // 从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/html response.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/html response.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 response.write(data.toString()); } // 发送响应数据 response.end(); }); }).listen(8080); // 控制台会输出以下信息 console.log('Server running at http://127.0.0.1:8080/');
[h2]4.常用软件包 [/h2]
- Node Redis客户端:https://github.com/luin/ioredis
- Node ORM:https://www.sequelize.cn/other-topics/upgrade-to-v6
- Node Webscoket :https://github.com/websockets/ws
- Node SocketIO:https://github.com/socketio/socket.io、https://socket.io/zh-CN/docs/v4/
- Node Nextjs:https://nextjs.org/
- “node-fetch”: “2”,支持require引入。
- Node Koa:https://www.koajs.net/
- 控制台输出带颜色的字:https://github.com/alexeyraspopov/picocolors
- 命令行交互工具:https://www.npmjs.com/package/inquirer
- 文件操作相关的函数:https://www.npmjs.com/package/fs-extra
- Node文件监控:https://www.npmjs.com/package/chokidar
- Node jquery:https://www.npmjs.com/package/cheerio
- Node tmp:https://www.npmjs.com/package/tmp
[h2]5.util库[/h2]
- util.inspect(): 将任意 JavaScript 对象转换为字符串形式,以便于调试和输出。
- util.format(): 类似于C语言中的printf函数,用于格式化字符串。
- util.promisify(): 将基于回调的函数转换为返回Promise的函数,以便于使用 async/await 进行异步编程。
- util.isArray(): 判断给定的对象是否是数组。
- util.isDate(): 判断给定的对象是否是日期对象。
- util.isError(): 判断给定的对象是否是一个Error对象。
- util.isPrimitive(): 判断给定的对象是否是原始类型(如字符串、数字、布尔值等)。
- util.formatWithOptions(): 类似于util.format(),但是可以指定格式选项。
- util.deprecate(): 标记函数已经废弃,并输出警告信息。
- util.callbackify(): 将异步函数转换为基于回调的函数。
[h1]Egg学习笔记[/h1]
[h2]1.安装egg[/h2]
# npm init egg --type=simple# yarn install# npm init egg --type=simple # yarn install# npm init egg --type=simple # yarn install
[h2]2.编写 Controller[/h2]
// app/controller/home.jsconst Controller = require('egg').Controller;class HomeController extends Controller {async index() {this.ctx.body = 'Hello world';}}module.exports = HomeController;// app/controller/home.js const Controller = require('egg').Controller; class HomeController extends Controller { async index() { this.ctx.body = 'Hello world'; } } module.exports = HomeController;// app/controller/home.js const Controller = require('egg').Controller; class HomeController extends Controller { async index() { this.ctx.body = 'Hello world'; } } module.exports = HomeController;
[h2]3.路由映射[/h2]
// app/router.jsmodule.exports = (app) => {const { router, controller } = app;router.get('/', controller.home.index);};// app/router.js module.exports = (app) => { const { router, controller } = app; router.get('/', controller.home.index); };// app/router.js module.exports = (app) => { const { router, controller } = app; router.get('/', controller.home.index); };
[h2]4.自定义监听的地址[/h2]
return {...config,...userConfig,cluster: {listen: {port: 7001,hostname: '0.0.0.0', // 不建议设置 hostname 为 '0.0.0.0',它将允许来自外部网络和来源的连接,请在知晓风险的情况下使用// path: '/var/run/egg.sock',},}};return { ...config, ...userConfig, cluster: { listen: { port: 7001, hostname: '0.0.0.0', // 不建议设置 hostname 为 '0.0.0.0',它将允许来自外部网络和来源的连接,请在知晓风险的情况下使用 // path: '/var/run/egg.sock', }, } };return { ...config, ...userConfig, cluster: { listen: { port: 7001, hostname: '0.0.0.0', // 不建议设置 hostname 为 '0.0.0.0',它将允许来自外部网络和来源的连接,请在知晓风险的情况下使用 // path: '/var/run/egg.sock', }, } };
[h1]实用函数封装[/h1]
[h2]1.file_get_contents[/h2]
import axios from "axios";import * as fs from "fs";import * as https from "https";/*** 读取文件* @param url* @param options* @return {Promise<unknown>}*/function file_get_contents(url, options = {}) {return new Promise((resolve, reject) => {if (url.startsWith('http')) {const config = {url,method: options.method || 'get',headers: options.headers || {},timeout: options.timeout || 5000,httpsAgent: options.verify === false ? new https.Agent({rejectUnauthorized: false}) : undefined,data: options.data || {},};axios(config).then(response => {resolve(response.data);}).catch(error => {reject(error);});} else {fs.readFile(url, 'utf8', (err, data) => {if (err) {reject(err);} else {resolve(data);}});}});}//demo(async () => {// 读取本地文件const data = await file_get_contents('./.gitignore');console.log(data);// 读取远程链接const content = await file_get_contents('https://nicen.cn', {headers: {'User-Agent': 'Mozilla/5.0','Accept-Language': 'en-US,en;q=0.9',},timeout: 5000,verify: false,method: 'POST',postData: 'param1=value1¶m2=value2',});console.log(content)})();import axios from "axios"; import * as fs from "fs"; import * as https from "https"; /** * 读取文件 * @param url * @param options * @return {Promise<unknown>} */ function file_get_contents(url, options = {}) { return new Promise((resolve, reject) => { if (url.startsWith('http')) { const config = { url, method: options.method || 'get', headers: options.headers || {}, timeout: options.timeout || 5000, httpsAgent: options.verify === false ? new https.Agent({rejectUnauthorized: false}) : undefined, data: options.data || {}, }; axios(config) .then(response => { resolve(response.data); }) .catch(error => { reject(error); }); } else { fs.readFile(url, 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } }); } }); } //demo (async () => { // 读取本地文件 const data = await file_get_contents('./.gitignore'); console.log(data); // 读取远程链接 const content = await file_get_contents('https://nicen.cn', { headers: { 'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'en-US,en;q=0.9', }, timeout: 5000, verify: false, method: 'POST', postData: 'param1=value1¶m2=value2', }); console.log(content) })();import axios from "axios"; import * as fs from "fs"; import * as https from "https"; /** * 读取文件 * @param url * @param options * @return {Promise<unknown>} */ function file_get_contents(url, options = {}) { return new Promise((resolve, reject) => { if (url.startsWith('http')) { const config = { url, method: options.method || 'get', headers: options.headers || {}, timeout: options.timeout || 5000, httpsAgent: options.verify === false ? new https.Agent({rejectUnauthorized: false}) : undefined, data: options.data || {}, }; axios(config) .then(response => { resolve(response.data); }) .catch(error => { reject(error); }); } else { fs.readFile(url, 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } }); } }); } //demo (async () => { // 读取本地文件 const data = await file_get_contents('./.gitignore'); console.log(data); // 读取远程链接 const content = await file_get_contents('https://nicen.cn', { headers: { 'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'en-US,en;q=0.9', }, timeout: 5000, verify: false, method: 'POST', postData: 'param1=value1¶m2=value2', }); console.log(content) })();
[h2]2.file_put_contents[/h2]
import * as fs from "fs";/*** 写入文件* @param file* @param data* @param options*/function file_put_contents(file, data, options) {options = options || {};const encoding = options.encoding || 'utf8';const flag = options.flag || 'w';fs.writeFileSync(file, data, { encoding, flag });}/* 使用 */file_put_contents('example.txt', 'Hello again!', { flag: 'a' });import * as fs from "fs"; /** * 写入文件 * @param file * @param data * @param options */ function file_put_contents(file, data, options) { options = options || {}; const encoding = options.encoding || 'utf8'; const flag = options.flag || 'w'; fs.writeFileSync(file, data, { encoding, flag }); } /* 使用 */ file_put_contents('example.txt', 'Hello again!', { flag: 'a' });import * as fs from "fs"; /** * 写入文件 * @param file * @param data * @param options */ function file_put_contents(file, data, options) { options = options || {}; const encoding = options.encoding || 'utf8'; const flag = options.flag || 'w'; fs.writeFileSync(file, data, { encoding, flag }); } /* 使用 */ file_put_contents('example.txt', 'Hello again!', { flag: 'a' });
暂无评论内容