【前后端】Node.js一本通

建议关注收藏点赞。

实用模块/工具

模块 说明
jsonwebtoken JWT 登录鉴权(签发和验证)
cors 解决跨域请求问题
dotenv 使用 .env 文件管理配置
axios 在 Node 中发起请求(后端调用第三方接口)
multer 上传文件(表单文件)
cookie-parser 操作 Cookie(登录相关)
child_process 执行命令
events 事件系统
stream 大文件处理、管道、压缩
Buffer 二进制操作、音视频、协议层
需求 推荐库/工具
Web 框架 Express, Koa, Fastify
数据库 ORM Sequelize, Prisma, Mongoose
环境变量 dotenv
单测 Jest, Mocha, Supertest
部署 PM2, Docker, Vercel, Render

复习

  1. 为什么JS可以在浏览器中执行
    原理:待执行JS代码->JS解析引擎
    不同浏览器使用不同的 JavaScript 解析引擎:Chrome 浏览器的 V8 解析引擎性能最好
    Chrome 浏览器 => V8
    Firefox 浏览器 => OdinMonkey(奥丁猴)
    Safari 浏览器 => JSCore
    IE 浏览器 => Chakra(查克拉)
  2. 为什么 JavaScript 可以操作 DOM 和 BOM
    每个浏览器都内置 DOM、BOM API
  3. 浏览器中JS运行环境
    在这里插入图片描述

基础语法 & 环境

  • 推荐的项目结构分层
层级 职责 举例
router(路由层) 导出router:接收请求,转发给 controller /user/:id
controller(控制器) 处理请求参数、返回响应 调用 service、处理错误
service(业务层) 写具体逻辑或数据库操作 User.findById(id)
model(模型层) 定义数据结构 Mongoose 模型
middleware(中间件层) 登录验证、错误处理等 auth.jserror.js
project/
├── app.js
├── routes/
│   └── user.js
├── controllers/
│   └── userController.js
├── services/
│   └── userService.js
├── models/
│   └── user.js
├── middleware/
│   ├── auth.js
│   └── errorHandler.js

  • 定义
    Node.js 是基于 Chrome V8 引擎的 JavaScript 运行环境。nodejs官网
    Node.js 用作中间件。例如:在浏览器端和 Java 端使用 Node.js 作为中间件,Node.js 调用 Java 后端发布的接口,同时 Node.js 可以发布 HTTP 接口给浏览器端调用。
    默认使用 CommonJS 模块规范
  • 优点
  1. 高并发能力。在 Java、PHP 或者 .Net 等服务端语言中,会为每一个客户端的连接创建一个新的线程,而每个线程需要耗费大约 2 MB 内存。也就是说,理论上一个 8GB 的服务器,可以同时连接的最大用户数为 4000 个左右。而 Node.js 不会为每个客户创建新的线程,仅仅使用一个线程。所以,使用 Node.js,一个 8GB 的服务器,可以同时处理超过 4 万用户的连接。
  2. 高性能服务器。Node.js 基于 V8 引擎,V8 引擎是 Google 公司使用 C++ 开发的一种高性能引擎。与开发者编写的低端的 C 语言具有非常相近的执行效率。
  3. 强大的工具和框架:
    基于 Express 框架(http://www.expressjs.com.cn/)快速构建 Web 应用
    基于 Electron 框架(https://electronjs.org/)跨平台的桌面应用
    基于 restify 框架(http://restify.com/)快速构建 API 接口项目
    读写和操作数据库、创建实用的命令行工具辅助前端开发
工具/框架 简介 特点
Express.js 用于构建Web应用和API的轻量级框架 简洁、快速、支持RESTful API、强大的路由功能
NestJS 基于TypeScript的现代化Node.js框架,适用于企业级应用 支持TypeScript、模块化架构、易于集成各种库
Koa.js 由Express原班人马开发的框架,目标是更小更强大 支持async/await、灵活的中间件机制
Socket.io 实现实时、双向通信的JavaScript库 支持WebSocket、易于集成、支持广播和房间功能
Electron.js 使用Web技术构建跨平台桌面应用 跨平台、支持丰富的原生API
PM2 Node.js进程管理工具,支持生产环境的应用管理 进程管理、负载均衡、日志管理、性能监控
Mongoose MongoDB的ODM库,简化与MongoDB的交互 Schema支持、数据验证、查询构建、模型功能强大
  • Node 和浏览器的区别
    浏览器是 JavaScript 的前端运行环境。
    Node.js 是 JavaScript 的后端运行环境。无法调用 DOM 和 BOM 等浏览器内置 API。
  • 安装运行
    LTS是长期稳定版 企业项目推荐安装
node -v #查看版本
node xx.js #运行某个nodejs文件

导入导出

require 和 module.exports(CommonJS)

// math.js
const add = (a, b) => a + b;
module.exports = {
   
   
  add
};
// app.js
const {
   
    add } = require('./math');//同步 加载阻塞执行
console.log(add(2, 3)); // 5

模块化

  • 加载模块
    require方法加载执行这个模块代码。
    模块的加载机制:
    1. 优先从缓存中加载。模块第一次加载后会缓存,即多次调用require()并不会使模块代码被执行多次。提高加载效率。
    2. 内置模块的加载优先级最高。require(‘fs’) 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。
    3. 自定义模块加载:require() 加载自定义模块必须指定以 ./ 或 …/ 开头路径标识符。如果没有,则 node 会把它当作内置模块或第三方模块加载。
    4. 第三方模块加载:如果不是一个内置模块,也没有以 ‘./’ 或 ‘…/’ 开头,则 Node.js 会从当前模块父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
      如果没有找到,则移动到再上一层父目录中,直到文件系统的根目录。
    5. 目录作为模块:三种加载方式:
      目录下查找 package.json 的文件,并寻找 main 属性,作为 require() 加载入口
      如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 试图加载目录下 index.js 文件。
      如果以上两步都失败,则在终端打印错误消息,Error: Cannot find module ‘xxx’
    6. require() 导入自定义模块,如果省略文件扩展名,则按顺序分别尝试加载以下的文件:
      按照确切文件名加载
      补全 .js 扩展名进行加载
      补全 .json 扩展名进行加载
      补全 .node 扩展名进行加载
      加载失败,终端报错
  • 模块有模块作用域,防止全局变量污染。每个.js自定义模块都有一个module对象,存储有关信息。
  • module.exports 对象
    自定义模块中使用 module.exports 对象,将模块内成员共享出去,供外界使用。外界用 require() 导入自定义模块时,得到 module.exports 所指向的对象。
  • exports 对象
    简化。默认exports 和 module.exports 指向同一个对象。最终共享结果以 module.exports 指向的对象为准。
  • exports 和 module.exports 的使用误区
    require() 模块时永远是 module.exports 指向的对象
    注意:为了防止混乱,建议大家不要在同一个模块中同时使用 exports 和 module.exports
    在这里插入图片描述
  • Node.js 中的模块化规范
    Node.js 遵循CommonJS 模块化规范
    CommonJS 规定:每个模块内部 module对象代表当前模块,exports 属性(即 module.exports)是对外接口。
const http=require('http')//导入内置模块
const custom=require('./custom.js')//导入用户自定义模块
const moment=require('moment')//导入第三方模块
console.log(http,custom,moment)//打印的是他们的module.exports内容

require('./modA');
require('./modB');
console.log('main module:', module);
//module 不会 直接包含 require('./modA') 和 require('./modB') 的详细信息
//但它间接包含子模块的信息 —— 体现在 module.children 属性里。

//modA.js
module.exports = {
   
    myModule: module };
//modB.js
module.exports = {
   
    myModule: module };
//main.js
const modA = require('./modA');
const modB = require('./modB');
console.log('modA module:', modA.myModule);
console.log('modB module:', modB.myModule);
console.log('main module:', module);

//使用moment包  npm安装moment
const moment=require('moment')//导入第三方模块
const dt=moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)

启用ES6模块化支持

  • ES 模块 import/export 在 Node 中的使用(.mjs、type: “module”)
    为啥要转ES模块?与浏览器一致、静态分析优势
    使用:将扩展名从.js改为.mjs;package.json中添加type: "module"
  • 引入模块
    import express from 'express替代之前的require
//app.js
import express from 'express'
import userRouter from './router/user_router.js'
const app = express()
app.use('/api', userRouter)//挂载路由器
app.listen(80, () => {
   
   
  console.log('server running at http://127.0.0.1')
})

//router.js
import express from 'express'
import {
   
    getAllUser } from '../controller/user_ctrl.js'
const router = new express.Router()
router.get('/user', getAllUser)//第二个是回调函数
export default router

//router_handler.js
import db from '../db/index.js'
// 使用 ES6 的按需导出语法,将 getAllUser 方法导出出去
export async function getAllUser(req, res) {
   
   
  try {
   
   
    const [rows] = await db.query('select id, username, nickname, xxx from ev_users')
    res.send({
   
   
      status: 0,
      message: '获取用户列表数据成功!',
      data: rows,
    })
  } catch (err) {
   
   //try...catch捕获异常
    res.send({
   
   
      status: 1,
      message: '获取用户列表数据失败!',
      desc: err.message,
    })
  }
}

//db_index.js
import mysql from 'mysql2'
const pool = mysql.createPool({
   
   
  host: '127.0.0.1',
  port: 3306,
  database: 'my_db_01',
  user: 'root',
  password: 'admin123',
})
export default pool.promise()

路径处理模块 path

Node 路径不能直接用 + 拼,需用 path 保证跨平台正确性。

  • 路径动态拼接的问题
    在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,容易出现路径动态拼接错误的问题。
    原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。
    解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 …/ 开头的相对路径,从而防止路径动态拼接的问题。
    __dirname 是 Node.js 全局变量,表示当前模块(文件)所在目录的绝对路径,用于拼接路径
//__dirname: /Users/yourname/project

fs.readFile(__dirname+'/path.txt','utf8',function(err,datastr){
   
   
	if(err)return console.log('文件读取失败'+err.message)
	//err=null时读取成功,反之读取失败
	console.log(datastr)
})
/*
__dirname 是当前文件夹路径(不是工作目录)
path.resolve():返回绝对路径
path.join():拼路径,自动处理斜杠
*/
const path = require('path');
// 获取绝对路径
const abs = path.resolve(__dirname, 'data', 'test.txt');
console.log(abs); // /xxx/data/test.tx
// 拼接路径
const full = path.join('/a', '/b', 'c.txt'); // /a/b/c.txt

path.join([...paths])//多个路径片段连接

path.basename(path,[ext])
//获取路径的最后一部分,用于获取路径中的文件名
//ext 可选,文件扩展名

const fpath='/a/b/c/index.html'
var filename=path.basename(fpath)//index.html
var name_without_ext=path.basename(fpath,'.html')//index
path.extname(path)//获取扩展名部分
const fext=path.extname(fpath)

文件系统模块 fs

fs.writeFile() 方法只能用来创建文件,不能用来创建路径
重复调用 fs.writeFile() 写入同一个文件,新写入内容会覆盖之前的旧内容

// 同步读写(适合 CLI 脚本)
const fs = require('fs');
fs.writeFileSync('data.txt', 'hello');       // 写文件
const content = fs.readFileSync('data.txt'); // 读文件
console.log(content.toString());             // Buffer 转字符串

// 异步读写(适合服务端)
const fs = require('fs').promises;
async function readData() {
   
   
  const data = await fs.readFile('data.txt', 'utf-8');
  console.log(data);
}
readData();

fs.readFile(path,[options],callback)//callback回调函数
//参数2 可选 编码格式
//参数3 文件读取完成,通过回调函数拿到读取结果

fs.readFile('./path.txt','utf8',function(err,datastr){
   
   
	if(err)return console.log('文件读取失败'+err.message)
	//err=null时读取成功,反之读取失败,datastr=undefined
	console.log(datastr)
})

fs.writeFile(file_path,data.[options],callback)
//data 要写入file的内容
//callback 文件写入完成后的回调函数
//是否写入成功也是判断err
  • 实例:将html文件中的js css部分拆出来成一单独文件
[\s\S] 只匹配 一个字符;
[\s\S]* 才是匹配 任意数量的字符(包括 0 个);
[\s\S]*? 是非贪婪地匹配任意数量字符,直到匹配到后续的模式。
const fs=require('fs')
const path=require('path')
const regStyle=/<style>[\s\S]*<\/style>/
const regScript=/<script>[\s\S]*<\/script>/
fs.readFile(path.join(__dirname,'index.html'),'utf8',(err,datastr)=>{
   
   
	if(err)return console.log('文件读取失败'+err.message)
	
	resolveCSS(datastr)
	resolveJS(datastr)
	resolveHTML(datastr)
})

function resolveCSS(htmlstr){
   
   
	const r1=regStyle.exec(htmlstr)
	const newcss=r1[0].replace('<style>','').replace('</style>','')
	fs.writeFile(path.join(__dirname,'index.css'),newcss,err=>{
   
   
		if(err)return console.log('文件写入失败'+err.message)
		console.log('css写入成功')
	})
}

function resolveJS(htmlstr){
   
   
	const r2=regScript.exec(htmlstr)
	const newjs=r2[0].replace('<script>','').replace('</script>','')
	fs.writeFile(path.join(__dirname,'./index.js'),newjs,err=>{
   
   
		if(err)return console.log('文件写入失败'+err.message)
		console.log('js写入成功')
	})
}

function resolveHTML(htmlstr){
   
   
	const newhtml=htmlstr.replace(regStyle,'<link rel="stylesheet" href="./index.css"/>').replace(regScript,'<script src="./index.js"></script>')
	fs.writeFile(path.join(__dirname,'./index.html'),newhtml,err=>{
   
   
		if(err)return console.log('文件写入失败'+err.message)
		console.log('html写入成功')
	})
}

Promise

nvm是用来切换nodejs版本的

v14之前,由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件,不支持 Promise 的调用方式。需要安装 then-fs 这个第三方包npm install then-fs,支持基于 Promise 方式读取文件的内容
调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容,它的返回值是 Promise 实例对象,可以调用 .then() 方法为每个 Promise 异步操作指定成功和失败之后的回调函数。

import thenFs from 'then-fs'
thenFs.readFile('./1.txt','utf8').then(res=>{
   
   console.log(res)},err=>{
   
   console.log('err',err)})
//err失败回调可选的
thenFs.readFile('./1.txt','utf8').then(res=>{
   
   console.log(res)})

//如果有多个这样的读取,要保证文件的读取顺序,需要用链式调用
/*
如果上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用,就解决了回调地狱的问题。
*/
thenFs.readFile('./1.txt','utf8')//返回promise实例对象
.catch(err=>{
   
    //捕获错误
	console.log(err.message)
})
.then((r1)=>{
   
   //指定成功的回调函数
	console.log(r1)
	return thenFs.readFile('./2.txt','utf8')
})
.then((r2)=>{
   
   
	console.log(r2)
	return thenFs.readFile('./3.txt','utf8')
})
.then((r3)=>{
   
   
	console.log(r3)
})
.catch(err=>{
   
    //捕获错误
	console.log(err.message)
})

Node.js fs.promises API 到 Node.js v14 起正式推荐使用,const fs = require('fs').promises;引入后就可以开始使用了。

const fs = require('fs').promises;

async function readFilesInOrder(files) {
   
   
  const results = [];

  for (const file of files) {
   
   
    try {
   
   
      const content = await fs.readFile(file, 'utf8');
      console.log(`内容 (${
     
     file}):\n`, content);
      results.push(content);
    } catch (err) {
   
   
      console.error(`读取 ${
     
     file} 出错:`, err);
    }
  }
  return results;
}

// 示例文件名数组
const fileList = ['file1.txt', 'file2.txt', 'file3.txt'];
readFilesInOrder(fileList).then(contents => {
   
   
  console.log('\n所有文件读取完成');
});

//更喜欢用 .then() 链式写法(不使用 async/await)
const fs = require('fs').promises;

function readFilesInOrder(files) {
   
   
  let promise = Promise.resolve();
  const results = [];

  files.forEach(file => {
   
   
    promise = promise
      .then(() => fs.readFile(file, 'utf8'))
      .then(content => {
   
   
        console.log(`内容 (${
     
     file}):\n`, content);
        results.push(content);
      })
      .catch(err => console.error(`读取 ${
     
     file} 出错:`, err));
  });

  return promise.then(() => results);
}

readFilesInOrder(fileList).then(contents => {
   
   
  console.log('\n所有文件读取完成');
});

const fs = require('fs');
function initp(fpath){
   
   
    return new Promise(function(resolve, reject){
   
   
        fs.readFile(fpath, 'utf8', (err, dataStr) => {
   
   
            if (err) {
   
   
                reject(err);      // 读取失败,触发 fail 回调
            } else {
   
   
                console.log(dataStr); // 可选:打印内容
                resolve(dataStr);     // 成功,触发 suc 回调
            }
        });
    });
}
function suc(data){
   
   
    console.log("成功回调函数触发:", data);
}
function fail(err){
   
   
    console.error("失败回调函数触发:", err);
}
initp('./1.txt').then(suc, fail);

进程信息:process

console.log(process.argv); // 获取命令行参数
console.log
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七灵微

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值