Node.js 初入门?持续记录

本站所有内容来自互联网收集,仅供学习和交流,请勿用于商业用途。如有侵权、不妥之处,请第一时间联系我们删除!Q群:迪思分享

[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.xz
tar -xvf node-v17.2.0-linux-x64.tar

然后设置软连接:

ln -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"
}

一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。

# 解释成 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');
})();

上面代码可以在 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';

这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是module.exports,是一个对象,无法被静态分析,所以只能整体加载。

加载单一的输出项,可以写成下面这样。

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;

上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。

你可以把这个文件的后缀名改为.mjs,或者将它放在一个子目录,再在这个子目录里面放一个单独的package.json文件,指明{ type: “module” }。

另一种做法是在package.json文件的exports字段,指明两种格式模块各自的加载入口。

"exports":{
    "require": "./index.js",
    "import": "./esm/wrapper.js"
}

上面代码指定require()和import,加载该模块会自动切换到不一样的入口文件。

[h1]模块[/h1]

[h2]1.EventEmitter[/h2]

EventEmitter的功能类似前端事件总线,Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

[lightbox title=”事件”]Node.js 初入门?持续记录[/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); 

[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("程序执行完毕");

[h3]3.2链式流[/h3]

链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。

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'

[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/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]

[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

[h2]2.编写 Controller[/h2]

// 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.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',
            },
        }
};

[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&param2=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' });

© 版权声明
THE END
★喜欢这篇文章吗?喜欢的话,麻烦动动手指支持一下!★
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容