定义

nodejs是前端工程化的基础,是开源的,基于谷歌v8引擎构建的js运行环境,运行开发者使用js编写的服务器。

三大模块

文件相关

fs模块

封装了与本机文件系统进行交互的方法与属性

fs.readFile()

1
fs.readFile(path[, options], callback)
  • 参数1:必选参数,字符串,表示文件路径

  • 参数2:可选参数,表示以什么编码格式来读取文件。

  • 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果,形如(err,dataStr)=>{}

    读取成功err为null,否则为错误对象

示例:

1
2
3
4
fs.readFile('/path/to/file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);//data是文件内容的buffer数据流
});

fs.writeFile()

1
fs.writeFile(file, data[, options], callback)
  • 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
  • 参数2:必选参数,表示要写入的内容
  • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf-8
  • 参数4:必选参数,文件写入完成后的回调函数

注意:这个方法只能用来创建文件,不能用来创建路径

path模块

path 模块提供了用于处理文件路径的方法,帮助我们在不同操作系统之间处理和标准化路径字符串

  • __dirname:返回当前js文件所在目录的绝对路径

  • path.join()

    path.join() 方法则简单地将所有给定的路径片段连接在一起,并规范化生成的路径。它不会尝试将路径转换为绝对路径,也不会考虑当前工作目录。

    1
    2
    3
    const path = require('path');
    const filePath = path.join('/folder', 'subfolder', 'file.txt');
    console.log(filePath); // 输出:'/folder/subfolder/file.txt'
  • path.resolve()

    path.resolve() 方法会将传入的路径片段解析为绝对路径。它从右向左处理参数,直到构造出一个绝对路径为止。如果所有给定的路径片段都不是绝对路径,则会使用当前工作目录(process.cwd())作为基础来构建绝对路径。无论输入是什么,path.resolve() 总是返回一个绝对路径。

  • path.parse()

    被用来解析路径

    1
    2
    3
    const pathObj = path.parse('/folder/subfolder/file.txt');
    // 输出:{ root: '/', dir: '/folder/subfolder', base: 'file.txt', ext: '.txt', name: 'file' }
    console.log(pathObj);

网络相关-http模块

对标浏览器中的XMLHttptRequest模块,http 模块用于创建 HTTP 服务器或客户端

1
2
3
4
5
6
7
8
9
10
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
//开启服务,占用3000端口
server.listen(3000, () => {
console.log('Server running on port 3000');
});

req

req是请求对象,它包含了与客户端相关的数据和属性,

  • req.url:是客户端请求的 URL地址
  • req.method:是客户端的请求方法

res

res是响应的结果,或者说是响应报文

  • res.statusCode:设置响应状态码

  • res.setHeader():设置响应报文的响应头

    1
    res.setHeader('Content-Type', 'text/plain;charset=utf-8')
  • res.end():结束此次请求与响应,并返回数据,当调用res.end()方法,并向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式。即res.setHeader('Content-Type', 'text/plain;charset=utf-8')

客户端/服务器

在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。

总结

这三个模块的组合使用非常常见,尤其是在构建需要读写文件并提供网络服务的应用程序中。例如,你可能需要从文件系统读取配置文件,然后使用这些配置来启动一个 HTTP 服务器,或者接收 HTTP 请求并将请求的数据写入文件系统。

Nodejs与浏览器的区别与联系

  • 浏览器依靠内核中的V8引擎执行js代码,node.js基于谷歌V8引擎进行封装
  • 都支持ECMAscript基础语法(ES语法)
  • Node.js有独立的api,没有DOM,BOM

模块化

模块化发展流程

函数封装

把功能封装为一个一个函数,把函数和相关的变量放到一个js文件中,需要使用某个函数的时候就引入js文件。引入的函数会被挂载到全局对象上,存在全局变量污染的问题。

1
2
3
4
5
6
7
8
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// 在HTML文件中通过script标签引入math.js后,add和subtract会挂载到全局对象上

将函数挂载到对象

所以就把相关的函数和变量封装到一个对象中,这样即便存在同名的函数也没关系,但是这样不安全,因为对象的属性可以被随意修改

1
2
3
4
5
6
7
8
9
10
// math.js
var MathLib = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
// 在HTML文件中引入math.js后,MathLib.add 和 MathLib.subtract不会直接挂载到全局对象上

立即执行函数

使用立即执行函数私有化变量和函数,并使用return暴露函数。这种写法的,被引入的函数和相关变量不再挂载到全局对象,但必须要求原模块代码先于拓展模块代码被引入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// math.js
var MathLib = (function() {
// 私有变量和函数
var privateVar = 'secret';

function privateFn() {
console.log('This is private:',privateVar);
}

// 返回公开接口
return {
privateFn
};
})();
  • 为了解决先后顺序问题,再传参的时候判断一下如果传入的是undefined则转为传入{},也就是设置默认值。

  • 可以看出模块化实现有多种方式,为了统一,我们必须制定出一个好用统一的模块化标准,比如commonjsesm,他们被打包后都会转换成立即执行函数风格的模块化代码。

模块分类

  • 内置模块

    加载的时候直接写包名,比如上述介绍的三大模块

  • 第三方模块

    加载的时候直接写包名,第三方模块又叫做包,是基于内置模块封装出来的

  • 自定义模块

    加载的时候需要写路径,可以省略.js后缀名,后缀补全规则

    1
    2
    3
    4
    5
    按照确切的文件名进行加载
    补全.js扩展名进行加载
    补全.json扩展名进行加载
    补全.node扩展名进行加载
    加载失败,终端报错

模块作用域

模块内定义的变量和函数无法被外部访问

模块加载机制

模块在第一次加载后会被缓存。这也意味着多次调用require()不会导致模块内的代码被执行多次

  • 内置模块

    加载优先级最高

  • 自定义模块

    加载时必须指定以./../开头的路径标识符,否则则node会把它当作内置模块第三方模块进行加载。

  • 第三方模块

    如果传递给require()模块标识符不是一个内置模块,也没有以./../开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。如果没有找到对应的第三方模块,则再移动到上一层父目录中,进行加载,直到文件系统的根目录

  • 目录作为模块(没有指明文件名)

    在被加载的目录下,查找package.json的文件,并寻找 main属性,这个属性指定了require()加载的入口
    如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会式图加载该目录下的index.js文件。
    如果以上两步都失败了,则Node.js 会在终端打印错误消息,报告模块的缺失: Error: Cannotfind module ‘xxx’

定义

模块其他资料聚合成一个文件夹(通常是js文件package.json文件,用来记录包的清单信息),本质就是个文件夹,一个第三方模块。

package.json

  • package.json 文件是每个 npm 包的核心配置文件,它包含了关于包的元数据信息,如名称、版本、作者等,以及定义了项目的依赖关系、开发依赖关系、脚本命令等。
  • 它列出项目的直接依赖项,并指定这些依赖项的版本范围(例如 ^1.2.3~1.2.3)。这意味着安装时(npm i),npm 可以根据这些范围,从注册表中获取满足条件的最新版本
  • 由于它只指定了版本范围而不是具体的版本号,因此允许在一定范围内自动更新依赖项,这可以确保你总是用上最新的修复和改进,但也可能引入不兼容的变化。

package-lock.json

  • package-lock.json 文件是在 npm 5 引入的一个锁定文件,它记录了所有安装过的依赖及其子依赖的确切版本

  • 不同于 package.json 中的版本范围,package-lock.json 锁定了依赖树中每一个包的具体版本号。这样可以确保无论何时何地重新安装依赖,都能得到完全相同的依赖环境,避免因为不同时间点安装不同版本的依赖而引起的潜在问题,从而提供更为稳定的开发环境

总结

  • npm i 下载包,寻找的是lock文件中指定的具体版本的包
  • package.json文件中指定的包,只会给出包的范围,只参考这个文件可能下载的还是较新的包
  • 删除lock文件,执行npm i ,又会出现新的lock文件
场景依赖版本选择依据是否更新 package-lock.json
首次安装(npm i )(无 package-lock.json根据 package.json 的版本范围是,生成 package-lock.json
常规安装(有 package-lock.json严格按 package-lock.json
手动升级依赖(如 npm install package@x.y.z强制覆盖为指定版本是,更新 package-lock.json
使用 npm update根据 package.json 的版本范围更新是,更新 package-lock.json

规范的包结构

  • 包必须以单独的目录而存在
  • 包的顶级目录下要必须包含package.json这个包管理配置文件
  • package.json中必须包含nameversionmain这三个属性,分别代表包的名字版本号包的入口

NPM

定义

npm(node packages manager)是node.js的标准软件包管理器,下载好node这个就能用了,通常用来在项目中下载,引入其他已发布的包,类似java中的maven

常用指令

  • npm init -y

    给一个包初始化package.json文件,配置清单信息(记录这个包引入的其他包的信息),-y的意思是:后续所有配置都选择yes,也就是使用默认配置

    只能在英文目录下运行,不能包含空格,不能包含中文

  • npm i

    安装所有依赖,包括开发依赖dependencies 和 生产依赖devDependencies

  • npm i 软件包名 -D

    下载软件包到本地开发环境(devDependencies),软件包名和-D的顺序不重要,下方同理

  • npm i 软件包名 -s

    下载安装包到本地的生产环境(dependencies),不添加任何符号,比如npm i 软件包名将默认下载到开发依赖中。

  • npm i 软件包名 -g

    下载软件包到全局。全局安装的软件包不会被添加到任何package.json文件中,因为它们不属于特定的项目,全局安装的软件包会被放置在系统的全局 Node.js 安装目录下(C:\Users\35194\AppData\Roaming\npm\node_modules)

  • npm i 包1 包2….

    一次性安装多个包(用空格隔开)

  • npm uninstall 包名

    卸载包,package.json文件中内容也会改变

推荐的包

nrm

一个方便切换下载源的工具包,选择合适的下载源,能显著提高我们下载包的速度。

安装指令:

1
npm i -g nrm

不借助nrm切换下载源:

1
2
npm config get registry //获取下包的服务器地址
npm config set registry = https://registry.npm.taobao.org //修改包的下载地址

明显比较麻烦,需要记住指令下载源的网址

使用nrm

1
2
nrm ls //获取所有可用镜像源
nrm use 镜像源名 //切换下载源

i5ting_toc

一个可以把md文档转换成html的包

安装:

1
npm install -g i5ting toc

使用:

1
i5ting_toc -f 要转换的md文件路径 -o

nodemon

安装:

1
npm install -g nodemon //安装到全局

或者

1
npm install --save-dev nodemon //安装到本地开发环境

安装完成后,你可以直接用 nodemon 来代替 node 命令来启动你的应用程序。例如,如果你的应用入口文件是 app.js,你可以这样做:

1
nodemon app.js

当你修改app.js文件并保存时,nodemon会检测到这个文件变化,并自动重新运行这个文件。

mysql

mysql模块是托管于npm 上的第三方模块。它提供了在 Node.js项目中连接和操作 MySQL数据库的能力。

express

是什么

express 是一个极简且灵活的基于Node.js 的Web 应用框架,它为构建 Web 应用和 API 提供了一组强大的功能。Express 通过提供路由、中间件、模板引擎等功能简化了 HTTP 服务器的创建过程,并支持快速开发可扩展的应用程序。

安装

1
npm i express

导入

1
2
3
4
// 引入 express 模块,导入的是一个函数
const express = require('express');
// 创建 express 应用实例
const app = express();

根据语法写业务函数

1
2
3
4
5
6
app.get("/api/complex",(req,res)=>{
// 一个字母都不能写错
// 解决复杂跨域问题
// res.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500")
res.send(data)//不需要转化成json
})
  • req

    • req.query:获取url中的查询参数,默认是个空对象

    • req.body:获取请求的请求体

    • req.params:

      1
      2
      3
      4
      5
      app.get( '/user/:id', (req,res) =>{ 
      //req.params 默认是一个空对象
      //里面存放着通过︰动态匹配到的参数值
      console.log(req.params)
      })

      可以传入多个动态参数,比如/user/:id/:name

  • res

    • res.send('数据'):在响应体中携带数据,并返回响应
    • res.sendFile(''文件路径'')res.sendFile 方法自动处理文件的读取发送,减少了手动读取文件设置响应头的工作,它会根据文件扩展名自动设置响应头中的 Content-Type 字段。
    • res.endres.end 是 Node.js 的http模块的原生方法,Express 继承了它。主要用于快速结束请求-响应周期,通常不携带数据或者只携带非常少量的数据。与res.send不同的是,它不会自动设置响应头(如 Content-Type)。

开启服务

1
2
3
app.listen(8081,()=>{
console.log("服务器启动")
})//服务器对应的域名是http://127.0.0.1:8081

然后执行,node express.js启动服务器(上述代码都写在express.js文件中),每次修改服务器内容后都需要重新启动,所以建议使用nodemon express.js

配置静态资源

1
app.use(express.static('./public'))

上述代码指定了,服务器的静态资源根目录,为与express.js文件同一级别的public文件

1
2
3
4
5
6
7
8
9
/your-project
/public
/images
logo.png
/css
style.css
/js
script.js
app.js

这意味着如果客户端请求 http://127.0.0.1:8081/images/logo.pngExpress 会尝试从 public/images/logo.png 提供该文件。

指定虚拟路径前缀

果你想为静态资源设置一个虚拟路径前缀(例如 /static),可以这样做:

1
app.use('/static', express.static('public'));

这行代码意味着所有的静态资源都将通过 /static 开头的 URL 路径访问。比如,要获取 public/images/logo.png 文件,客户端应该请求 http://127.0.0.1:8081/static/images/logo.png。这样的效果就是能够配置多个静态资源文件,且能够根据前缀区分开来。

配置多个静态资源

如果你有多个静态资源目录,可以多次调用 express.static

1
2
app.use(express.static('public'));
app.use(express.static('assets'));

这样,Express 会按照定义的顺序查找静态文件。首先会在 public 目录下查找,如果没有找到,再尝试在 assets 目录中查找。

也许推荐的做法是写成:

1
2
app.use('/public',express.static('public'));
app.use('/assets',express.static('assets'));

这样就能精确的控制具体在哪个目录下查找静态资源。

路由

在Express 中,路由指的是客户端的请求服务器处理函数之间的映射关系,也可以说是后端路由

和匹配静态资源类似,在匹配时,会按照路由的声明/书写顺序进行匹配。

路由模块化

  • 创建路由文件

    首先,在你的项目中创建一个 routes 目录,并为每个资源创建单独的路由文件。例如,如果你有一个用户资源和一个产品资源,可以创建如下结构:

    1
    2
    3
    4
    5
    /your-project
    /routes
    user.js
    product.js
    app.js
  • 定义路由

    在每个路由文件中,使用 express.Router() 来创建一个新的路由器对象,并定义与该资源相关的路由。然后导出这个路由器对象。以用户资源为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // routes/user.js
    // 可以看出,导出的express函数上还挂载了一个Router方法,用来创建一个路由器
    const express = require('express');
    const router = express.Router();

    // 定义用户的 GET 路由
    router.get('/info', (req, res) => {
    res.send('GET request to the user route');
    });

    // 定义用户的 POST 路由
    router.post('/info', (req, res) => {
    res.send('POST request to the user route');
    });

    // 导出路由器
    module.exports = router;
  • 加载路由模块

    接下来,在主应用文件(通常是 app.js)中引入这些路由模块,并使用 app.use() 方法挂载它们。你还可以为每个路由模块指定一个基础路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // app.js
    const express = require('express');
    const app = express();
    const userRouter = require('./routes/user');
    const productRouter = require('./routes/product');

    // 挂载用户路由,基础路径为 /users
    // 这样就能通过 /users/info访问到users对应的资源
    app.use('/users', userRouter);

    // 挂载产品路由,基础路径为 /products
    app.use('/products', productRouter);

    // 启动服务器
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
    });

中间件

定义

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// routes/user.js
const express = require('express');
const router = express.Router();

// 自定义中间件,用于所有用户路由
router.use((req, res, next) => {
console.log('Time: ', Date.now());
next();//放行
});

// 定义用户的 GET 路由
router.get('/', (req, res) => {
res.send('GET request to the user route');
});

// 导出路由器
module.exports = router;
  • 和路由的区别

    中间件函数的形参列表中,必须包含next 参数。而路由处理函数中只包含reqres

  • next()

    next()是实现多个中间件连续调用的关键,它表示把控制权转交给下一个中间件或路由

  • 共享

    多个中间件之间,共享同一份reqres。基于这样的特性,我们可以在上游的中间件中,统一为 reqres对象添加自定义的属性或方法,供下游的中间件路由进行使用。

  • 执行顺序

    可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行

按作用范围分类

  • 全局中间件

    所有请求到达都会经过的中间件,通过调用app.use(中间件函数),即可定义一个全局生效的中间件

  • 局部中间件

    在定义路由的时候传入中间件

    1
    app.get('/ ', mw1,(req,res) =>{res.send(' Home page. ')})

    定义多个局部中间件

    1
    app.get('/ ', mw1, mw2, (req,res) =>{res.send(' Home page. ')})

    或者在一个单独的模块中定义,通过router.use(中间件函数)实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // routes/user.js
    const express = require('express');
    const router = express.Router();

    // 自定义中间件,用于所有用户路由
    router.use((req, res, next) => {
    console.log('Time: ', Date.now());
    next();
    });

其他分类

  • 应用级别:绑定到app上的

  • 路由级别:绑定路由上的

  • 异常捕获中间件:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。区别于其他中间件,必须放在所有路由之后,可以观察到,错误级别的中间件有四个参数,(err, req, res, next),err错误对象是第一个参数,因为它对于异常捕获中间件是非常重要的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.use((err,req,res,next)=>{
    if(err.inner.message = "No authorization token was found"){
    res.send({
    code:1,
    msg:"token未携带"
    })
    }
    next('token未携带')//它最终会被传递到最后一个中间件,通常是日志记录中间件,然后打印到控制台
    })
  • 内置中间件

    • express.static()

      快速托管静态资源的内置中间件,所以说,express.static()返回一个新的中间件函数

      1
      app.use(express.static('public'));
    • express.json()

      这个中间件用于解析JSON格式的请求体。它会将接收到的 JSON 数据解析成 JavaScript 对象,并将其存储在 req.body 中,以便后续的路由处理器可以访问。

    • express.urlencoded()

      此中间件用于解析 URL 编码格式的请求体,通常出现在 HTML 表单提交时。与 express.json() 类似,它也会将解析后的数据附加到 req.body

  • 自定义中间件

    定义一个模块,编写中间件函数,然后导出,使用的时候导入,再挂载。

    1
    2
    3
    4
    5
    6
    function myMiddleware(req, res, next) {
    // 执行一些操作...

    // 调用 next() 将控制权传递给下一个中间件或路由处理程序
    next();
    }
    1
    2
    3
    4
    5
    const express = require('express');
    const app = express();

    // 定义并使用自定义中间件
    app.use(myMiddleware);
  • 第三方中间件

    下载然后使用,比如cors

注意

  • 一定要在注册路由注册中间价,特别是全局中间价,局部中间件也要先声明在使用。

  • 中间件类别的区分在于参数的个数

  • 如果在中间件中发生错误,应该传递给 next(err),这样可以让错误处理中间件有机会处理该错误

  • 不要忘记调用 next():如果你不调用 next(),请求-响应周期将会停止,导致客户端永远等待响应。