Node.js | 青训营笔记

121 阅读12分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

Node.js

简介

  • 运行在服务器端的js(JavaScript是运行在浏览器端的js)
  • 用来编写服务器
  • 特点:单线程(指的JavaScript单线程)、异步 I/O、非阻塞(读取数据时)、统一API、跨平台
  • node.js 和 JavaScript的区别:node 有 ECMAScript,没有 DOM 和 BOM ;JavaScript 三者都有。

应用场景

  1. 前端工程化

    • 打包:webpack、vite、esbuild、parcel
    • 代码压缩:uglify
    • 语法转换:bablejs、typescript
  2. Web 服务端应用

    • 学习曲线平缓,开发效率较高
    • 运行效率接近常见的编译语言
    • 社区生态丰富(npm),工具类成熟(V8 inspector)
    • 与前端结合的场景会有优势(服务端渲染 SSR、服务于前端的后端 BFF)
  3. Electron 跨端桌面应用

    • 商业应用:vs code、slack、discord、zoom
    • 大型公司内的效率工具

运行结构

8NX5Z.png

V8:JavaScript Runtime、诊断调试工具(inspector)

libuv:eventloop(事件循环)、syacall(系统调用)

安装与运行

  1. 官网下载安装

    • 官网地址:nodejs.org/en/

    • 版本切换时比较麻烦,需要卸载后再重装。

  2. 通过 nvm 工具安装

    • nvm 是 node.js 的版本管理工具

    • nvm 下载:github.com/coreybutler…

    • 命令

      nvm install latest #下载并安装最新版的node
      nvm install lts #安装稳定版的node
      nvm list #显示已安装的node版本
      nvm install 版本号 #安装指定版本的node
      nvm use 版本号 #指定要使用的node版本
      
    • 配置 nvm 的镜像服务器(国外网站访问不稳定)

      # 将nvm的node镜像服务器修改为国内的阿里云
      nvm node_mirror https://npmmirror.com/mirrors/node/ 
      
  3. 运行(在 js 文件中编写代码后)

    • 打开 vs code 终端,输入 node 文件路径 运行 node
    • 在 vs code 中按下 F5 键,选择 node.js 调试器来运行 node

模块化

CommonJS 规范

CommonJS 是 JS 内置的模块化系统,是 NodeJS 默认的模块化规范。

  • 引入模块

    使用require("模块的路径")函数来引入模块

    引入自定义模块时,模块名要以 ./../ 开头(相对路径)

    模块文件的后缀可以不写,node 会自动补充

    引入文件夹时,默认引入主文件 index.js,该文件中引入了文件夹下的其他所有文件。

  • 自定义模块

    模块扩展名为cjs时,表示这是一个 CommonJS 模块

    // 一个变量一个变量地暴露
    exports 变量1 = v1
    exports 变量2 = v2
    // 暴露多个变量
    module.exports = {
        变量1: v1,
        变量2: v2,
        ...
    }
    
  • 使用模块

    const 模块名 = require("模块的路径");
    console.log(模块名.变量名);
    
  • 原理

    所有的 CommonJS 模块都会被包装到一个函数中

    (function(exports, require, module, __filename, __dirname) {
        // 所有的模块代码会被放在这里
        // __filename:当前模块的绝对路径
        //  __dirname:当前模块所在目录的路径
    });
    

ES 规范

使用 importexport 引入和导出模块,详见 es6 语法。

node 中想使用 ES 规范的两种方法:

(1)使用.mjs作为模块扩展名

(2)在package.json中设置"type": "module"

核心模块

核心模块是node中内置的模块,有的可以直接使用,有的要引用后才能使用。

js 的全局对象为 globalThis,在浏览器中为 window ,在 node 中为 global

  • process

    process表示当前的node进程。通过该对象可以获取进程的信息,或者对进程做各种操作。

    // 结束进程。status为可选参数,表示状态码。
    process.exit([status]) 
    // 向tick任务队列(在调用栈之后、微任务队列之前执行)中添加任务
    process.nextTick(callback[,...args])
    
  • path

    path 模块用来获取文件(夹)的路径,使用前需要先将其引入。

    在node中最好把路径写成绝对路径,因为执行方式的不同会造成路径不同。

    const path = require("node:path") //引入path模块
    path.resolve([..paths]) //生成绝对路径(C:\Users\Lenovo\.git、万维网URL都是绝对路径)
    path.resolve() //获取当前工作目录
    path.resolve(__dirname, "相对路径") //将相对路径转化成绝对路径
    
  • fs

    fs 模块用来操作磁盘中的文件(即 I/O),使用前需要先将其引入。

    // 引入fs模块
    const fs = require("node:fs") 
    // 读取文件,读取到的数据会以Buffer对象(临死缓冲区)的形式返回
    fs.readFile()
    // 创建新文件,或将数据添加到已有文件中
    fs.appendFile()
    // 创建目录
    fs.makdir()
    // 删除目录
    fs.rmdir()
    // 删除文件
    fs.rm()
    // 重命名
    fs.rename()
    // 复制文件
    fs.copyFile()
    

包管理器

引用的现成写好的代码称为包,例如 jQuery。

包管理器是一个存储着 JS 开源模块的网络存储库,同时也是一个管理 JS 模块环境的工具,其表现形式是命令行交互工作。

npm

  • 简介

    node 中的包管理器叫做 npm(node package manage),它是世界上最大的包管理库。

    官网:www.npmjs.com/

    可以在官网上查找包,也可以提交和管理自己开发的包。

    安装 node 时会一并安装 npm。

  • package.json

    这是包的描述文件,每个 node 项目必须有该文件。

    该文件中必须含有的两个属性:name(包的名称)和 version(版本号)。

    {
        "name": "包名",
        "version": "x.x.x"
    }
    

    package.json 配置完全解读

  • 命令

    1. 初始化项目,创建 package.json 文件

      npm init #需要回答项目名称等问题
      npm init -y #所有值都采用默认值
      
    2. 安装 npm 网络存储库中的包

      # install 可简写为 i
      
      # 将指定包下载到当前项目的node_modules中
      npm install 包名 #下载项目依赖的包(有该包,项目才能正常运行),并在package.json的dependencies属性中添加包名和版本
      npm i 包名 #简写
      npm i 包名 -g #全局安装,将包安装到计算机中,可以在计算机任何地方的命令行中使用
      npm i 包名 -D #下载开发依赖的包(帮助开发),并在package.json的devDependencies属性中添加包名和版本
      
      # 将package.json的dependencies属性中里的包全部下载下来,该属性表示项目运行所依赖的包
      npm install 
      npm i #简写
      
    3. 引用已安装的包

      const 包名 = require("包名")
      
    4. 卸载已安装的包

      npm uninstall 包名
      
    5. 自定义指令

      // 在package.json文件中
      {
          "scripts": {
          	"test": "较长的指令",
              "自定义指令名": "较长的指令"
          }
      }
      
      npm run 自定义指令名
      
      # 可以简写的指令(省略 run )
      npm start
      npm stop
      npm test
      npm restart
      
  • 配置 npm 镜像服务器

    为解决位于国外的 npm 服务器的访问不稳定的问题,可以在 npm 中配置一个镜像服务器。

    1. 安装 cnpm(不推荐,可能造成语法错误)

      # 安装 cnpm
      npm install -g cnpm --registry=https://registry.npmmirror.com
      

      cnpm 语法与 npm 相同,使用时将 npm 替换为 cnpm 即可。

    2. 临时切换使用淘宝镜像

      npm --registry https://registry.npmmirror.com install 包名
      
    3. 长期切换使用淘宝镜像

      npm set registry https://registry.npmmirror.com #切换淘宝镜像
      npm config delete registry #还原到原版仓库
      npm config get registry #查看当前仓库地址
      

Express

express 是 node 中的服务器软件,通过 express 可以快速地在 node 中搭建一个 web 服务器。

使用步骤

  1. 创建并初始化项目

    npm init -y
    
  2. 安装express

    npm i express
    
  3. 创建 index.js 并编写代码

    // 引入express
    const express = require("express")
    // 获取服务器的实例(对象),一个服务器里只能有一个app对象
    const app = express()
    // 启动服务器,客户端可以通过端口号来访问服务器
    // 访问服务器的URL:【协议名://ip地址:端口号/路径】,本地服务器ip地址为127.0.0.1或localhost
    app.listen(端口号, () => {
        // 启动服务器后执行的函数
    })
    
    // 路由:METHOD为请求方式,如get、post等,app.all()处理所有类型的HTTP请求
    // 路由路径为"/"时代表访问的是根目录localhost,也是网页的首页
    app.METHOD("路由路径", (req, res, next) => {
        // req 表示的是用户的请求信息,通过req可以获取用户传递的数据
        console.log(req.query) //请求参数
        // res 表示的服务器发送给客户端的响应信息,可以通过res来向客户端返回数据
        res.sendStatus(状态码) //向客户端发送响应状态码
        res.status(状态码) //用来设置响应状态码,但是并不发送
        res.send(响应体) //设置并发送响应体(浏览器呈现的页面)
    	// next() 是一个函数,调用函数后,可以触发下一个的路由(没有next时默认只触发第一个匹配的路由),它不能在响应处理完毕后调用(不能在res.send后调用)
    })
    
    // 中间件:与路由很相似,但中间件会匹配所有请求、路径设置父目录。中间件是请求与匹配路由之间经过的程序,用于多个路由都要实现的功能(例如权限验证)
    app.use((req, res, next) => {
        // next() 是一个函数,调用函数后,可以触发下一个的中间件(没有next时默认只触发第一个中间件),它不能在响应处理完毕后调用(不能在res.send后调用)
    })
    

    node 是自上而下执行的,写在前面的路由/中间件优先级较高,写在前面的中间件可用于进行数据的预处理。若请求匹配了前面的路由,且该路由没有调用next(),则不会再匹配后续的路由/中间件了。

nodemon

服务器代码更改后需要重启才能生效,nodemon 可以监视代码的修改,自动重启服务器。

  1. 全局安装与启动

    # 1.全局安装
    npm i nodemon -g
    # 2.启动
    nodemon #运行index.js
    nodemon js文件路径 #运行指定的js
    
  2. 在项目中安装与启动

    # 1.项目中安装
    npm i nodemon -D
    # 2.启动
    npx nodemon #运行index.js
    npx nodemon js文件路径 #运行指定的js
    

静态资源

服务器中的内容,默认对外部是不可见的。若希望浏览器可以访问某些资源,则将它们设置成静态资源。

浏览器访问服务器时,默认访问静态资源中的 index.html

// 引入express
const express = require("express")
//引入path模块
const path = require("node:path")
// 获取服务器的实例(对象)
const app = express()
// 设置静态资源
app.use(express.static(path.resolve(__dirname, "静态资源的相对路径")))

请求参数

  1. query

    <form method="get" action="服务器文件">  
        <input type="text" name="userName" value="文本"> 
        <input type="password" name="userPassword" value="文本">
        <input type="submit" value="提交">
    </form>
    
    // 浏览器访问的URL:路由路径?userName=...?userPassword=...
    app.get("路由路径", (req, res) => {
        console.log(req.query) //传递的参数对象:{userName: ..., userPassword: ...}
        console.log(req.query.userName) //userName输入框的值
        console.log(req.query.userPassword) //userPassword输入框的值
    })
    
  2. params

    //浏览器访问时的URL:路由路径/:值1/:值2
    app.get("路由路径/:参数名1/:参数名2", (req, res) => {
        console.log(req.params) //传递的参数对象:{参数名1: ..., 参数名2: ...}
        console.log(req.params.参数名1) //浏览器访问时URL中的值1
        console.log(req.params.参数名2) //浏览器访问时URL中的值2
    })
    

    一般不使用 params 传递特别复杂的参数,也不会传递很多参数。

  3. body

    默认情况下,express 不会自动解析请求体 res.body,需要引入解析请求体的中间件。

    <form method="post" action="服务器文件">  
        <input type="text" name="userName" value="文本"> 
        <input type="password" name="userPassword" value="文本">
        <input type="submit" value="提交">
    </form>
    
    // 引入解析请求体的中间件
    app.use(express.urlencoded())
    //用户提交表单后的URL:路由路径
    app.post("路由路径", (req, res) => {
        console.log(req.body) //传递的参数对象:{userName: ..., userPassword: ...}
        console.log(req.body.userName) //userName输入框的值
        console.log(req.body.userPassword) //userPassword输入框的值
    })
    

处理访问错误

// 在所有路由后面配置错误路由
app.use((req, res) => {
    // 只要这个中间件一执行,说明上面的路由都没有匹配
    res.status(404)
    res.send("您访问的地址已被外星人劫持!")
})

模板引擎 ejs

  • 模板引擎

    1. 模板是一个文字档,定义了一个输出档的结构或者排版,使用定位符号表示。
    2. 当模板被绘制时,解析对应类型模板文件然后动态生成由嵌入的变量和静态页面组成的视图文件。
    3. 模板通过标签(tag)来响应各种解析动作,通过变量占位的方式动态地将对应数据展示到指定位置。
    4. 模板引擎需要在服务器渲染后才能被浏览器访问,不能直接访问。
    5. ejs 模板在服务器端渲染,而 ReactVue 在客户端渲染。
  • ejs 的安装与配置

    # 安装ejs
    npm i ejs
    
    // 配置express模板引擎为ejs
    app.set("view engine", ejs)
    // 配置模板的路径(专门存放模板的文件夹,文件夹名一般为views)
    app.set("views", path.resolve(__dirname, "views"))
    
  • ejs 的使用

    1. 编写模板文件(文件后缀为 .ejs,语法与html相似)

      <!-- 插入动态数据 -->
      <h3><%=动态数据名1 %></h3> <!-- 会对传入的特殊数字字符(如html标签)进行转义,防止XSS攻击 -->
      <h3><%-动态数据名2 %></h3><!-- 不会对传入的特殊数字字符进行转义(会渲染html标签)可以在res.render前先判断请求参数是否合法,以避免XSS的攻击 -->
      
      <% 
          //这里可以直接写js代码,可用于条件渲染和列表渲染
      %>
      <!-- 条件渲染 -->
      <% if(动态数据名 === value) { %>
          <div></div>
      <% } else { %>
          <div></div>
      <% } %>
      <!-- 列表渲染 -->
      <% for(const item of 动态数组名) { %>
          <div><%= item %></div>
      <% } %>
      

    XSS 攻击:利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。

    1. 渲染模板文件

      app.METHOD("路由路径", (req, res) => {
          // 渲染模板
          res.render("目标模板 相对于配置的模板路径 的路径", {动态数据名1: value1, 动态数据名2: value2})
          // 发起重定向请求,转发回到模板页面(不会停留在路由路径)
          res.redirect("目标模板 相对于配置的模板路径 的路径")
      })
      

数据持久化

服务器刷新后,直接写在 node.js 中的数据会恢复为初始值。为持久化数据,可以将数据写在单独的文件中(例如 json 文件),通过 fs 模块来修改文件中的数据。

// 引入fs模块
const fs = require("fs/promises")
// 读取文件中的数据
let 数据名 = require("存储数据的文件的路径")
// 将数据写入存储数据的文件中
fs.writeFile("存储数据的文件的路径", 新数据
).then(() => {
    // 写入成功后的回调
}).catch(() => {
    // 写入失败后的回调
})

Router 对象

Express 专门提供了路由功能用来封装请求,将路由单独写在一个或多个文件中,需要时再在 index.js 中调用。

  1. 封装路由

    // 封装路由的js文件
    const express = require("express")
    const router = express.Router() //创建router对象
    router.get("/xxx", (req, res) => {
        
    })
    module.exports = router //将router暴露到模块外
    
  2. 调用路由

    // index.js文件
    const express = require("express")
    const app = express()
    const 路由名 = require("封装路由的js文件的路径")
    app.listen(端口号, () => {
        
    })
    app.use("/yyy", 路由名) //调用路由,/yyy代表路由前缀,完整URL为localhost:端口号/yyy/xxx