首页>>前端>>Node->Node.js——express核心用法和源码解读

Node.js——express核心用法和源码解读

时间:2023-11-29 本站 点击:0

客户端发送请求的方式

客户端传递到服务器参数常见的5种方法

通过get请求中的URLparams

通过get请求中的URL的query

通过post请求中的bodyjson格式

通过post请求中的body的x-www-form-urlencoded格式

通过post请求中的form-data格式

传递参数params和query

params

如果请求路径是http://localhost:3000/user/123132,那么我们就可以通过req.params的方式获取到用户传递过来的参数信息

const express = require('express')const app = express()app.get('/user/:id', (req, res, next) => {  console.log(req.params); // { id: '123132' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})

用户在传递的时候可能也会传递多个参数,但这种情况比较少;请求路径为http://localhost:3000/user/haha/18

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})

query

如果请求路径是http://localhost:3000/user?name=haha&age=18,那么我们就可以通过req.params的方式获取到用户传递过来的参数信息

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})

响应数据

end方法

类似于http中的response.end方法,用法是一致的;但是其只能返回字符串或者是buffer数据

const express = require('express')const app = express()app.post('/login', (req, res, next) => {  // 下面这行代码会报错  res.end({ name: 'chris' }) // The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object})app.listen(3000, () => {  console.log('express框架初体验');})

如果我们想返回一个json数据,则需要先用res.type方法设置响应头中的content-typeapplication/json,然后再利用JSON.stringify将我们要返回的对象转换为json格式的数据之后再使用res.end方法返回

const express = require('express')const app = express()app.post('/login', (req, res, next) => {  // 指定响应数据的数据类型,这样客户端才知道以什么形式的数据进行解析  res.type('application/json')  // 将返回的对象先转为json格式的字符串  res.end(JSON.stringify({ name: 'chris' }))})app.listen(3000, () => {  console.log('express框架初体验');})

json方法

json方法中可以传入很多的类型:objectarraystringbooleannumbernull等,它们最终都会被转换成json格式返回;json方法相当于帮我们做了两件事情,先设置好响应头中的content-type属性,再将数据转化为json格式响应回去

const express = require('express')const app = express()app.post('/login', (req, res, next) => {  res.json({ name: 'chris' })})app.listen(3000, () => {  console.log('express框架初体验');})

status方法

用于设置状态码

const express = require('express')const app = express()app.post('/login', (req, res, next) => {  res.status(204)  res.json([1, 2])})app.listen(3000, () => {  console.log('express框架初体验');})

Express的路由

如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂

一方面完整的Web服务器包含非常多的处理逻辑

另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对用户user相关的处理

获取用户列表

获取某一个用户信息

创建一个新的用户

删除一个用户

更新一个用户

我们可以使用express.Router来创建一个路由处理程序

一个Router实例拥有完整的中间件和路由系统

因此,它也被称为迷你应用程序(mini-app

// app.jsconst express = require('express')const userRouter = require('./routers/user')const app = express()// 注册一个普通中间件来解析请求体中的json数据app.use(express.json())// 将有关用户的请求全部引入到注册好的用户路由中去执行app.use('/user', userRouter)app.listen(3000, () => {  console.log('express框架初体验');})

一个路由实例就相当于是一个小的mini-app,其拥有完整的中间件,所以其也可以像app一样注册其它的中间件

// routers/user.jsconst express = require('express')// 使用express.Router函数注册一个路由const router = express.Router()// 一个路由实例拥有完整的中间件功能,所以其也可以像app一样注册其它的中间件router.get('/:id', (req, res, next) => {  res.json({nickname: 'chris', age: 18})})router.post('/', (req, res, next) => {  console.log(req.body);  res.end('注册了一个新用户')})router.delete('/:id', (req, res, next) => {  res.end(`删除了用户${req.params.id}`)})// 将注册号的路由导出module.exports = router

静态资源服务器

node里面,如果想把某一个文件夹作为静态的服务器让客户端可以进行访问的话是非常简单的

express内置了一个static方法,其返回值是一个函数,可以作为我们的中间件

如果我们通过http来做的话,需要自己读取里面的文件然后再返回回去

但是在express里面,我们只需要注册一个普通中间件就可以了,只指定文件夹作为我们的静态资源文件夹,这样当我们去请求资源的时候就会去该文件夹里面寻找

dist文件夹是之前自己做的后台管理系统项目的打包文件,我们可以通过express来部署项目到本机;express内置了一个static方法,其返回值是一个函数,可以作为我们的中间件,需要传递一个路径作为静态资源文件夹

const express = require('express')const app = express()// express内置了一个static方法,其返回值是一个函数,可以作为我们的中间件app.use(express.static('./dist'))app.listen(3000, () => {  console.log('express框架初体验');})

express的错误处理

next函数中如果传入了参数,执行的并不是下一个匹配上的中间件,而是专门用来匹配错误的中间件;这样我们所有的错误处理就可以集中在一个中间件中进行管理,代码更加整洁且利于维护

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})0

下面是自己用postman进行的测试

源码解读

调用express()到底创建的是什么?

express()创建的是一个名为app的函数,在这个函数上面集成了很多的方法,最经典的就是可以用app来创建中间件

我们从expressindex.js文件中发现其是从lib文件夹下面的express.js文件中导入的

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})1

来到express.js中发现其导出的其实是一个函数createApplication,也就是说const express = require('express')其实就是将createApplication函数的引用赋值给了变量express而已

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})2

我们来到createApplication函数后发现,在其里面定义了一个名为app的函数并导出。我们现在应该明白const app = express()的时候函数的返回值是什么了吧?其实就是在createApplication函数里面声明的一个名为app的函数而已

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})3

app.listen()是如何启动服务器的?

app.listen()其实本质上用的还是http模块搭建服务器的方式,相当于在外面封装了一层而已

仔细查看createApplication函数之后,没有发现有给app函数添加过listen属性,那我们为什么可以直接调用app.listen方法呢?

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})4

其实基础比较好的同学肯定已经想到了,一个对象上面没有对应的属性就会到其原型中寻找,我们能调用listen方法就说明该方法肯定是在app函数的原型上的,而mixin(app, proto, false)这行代码做的就是将proto对象上的属性混合到app上

我们去proto对象中寻找之后发现,其身上的确具有listen方法,所以我们调用app.listen时实际上调用的是下面的代码;不难看出app.listen实际上是使用了原生http模块的createServer方法来进行搭建服务器的,监听端口号的方法用的也是server.listen而已,相当于是做了一层封装。所以本质上express就是http模块的封装这句话一点也不假

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})5

传入http.createServer函数的this就是app;这个函数会在客户端发送请求的时候执行,所以在express框架中,用户发送请求的时候执行的函数其实是app

app.use(中间件)内部到底发生了什么?

其实app.use的本质是调用了router.use方法,在内部将我们传递进去的中间件函数都放入到了一个stack数组中,以这种方法帮我们注册了中间件

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})6

Layer构造函数中可以知道,其在创建实例的时候将对应的中间件函数的引用赋值给了layer.handle属性

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})7

下面是lazyrouter函数,主要做的事情就是注册了一个路由并赋值给了app._router

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})8

下面是router.use函数,前面的操作跟app.use的操作很类似,所以现在大家应该可以理解之前在创建路由当中为什么可以用router.use来创建中间件了吧,并不是router.use模仿了app.use,而是app.use基于router.use

app.get('/user/:name/:age', (req, res, next) => {  console.log(req.params); // { name: 'haha', age: '18' }  res.end('Hello')})9

用户发送了网络请求,中间件是如何被回调的?

刚刚我们探索了app.use帮助我们注册中间件的本质其实就是把中间件函数和layer绑定到了一起放到了stack数组中去。其实中间件是如何被执行的也很简单,express会在用户发送请求的时候遍历stack数组,找到第一个匹配的layer之后,就去执行绑定在它身上的中间件函数layer.handle,这样就实现了用户发送请求回调中间件的功能

首先我们要知道,当用户发送了网络请求之后,传入http.createServer的参数会被执行,我们从app.listen的源码中可以看到,我们其实是把app传入给了http.createServer函数,所以当用户发送请求的时候,http模块会帮助我们执行app函数

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})0

app函数里面到底有什么内容呢?不难发现,其本质上执行的是app.handle函数

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})1

于是我们又找到了app.handle函数,发现其调用的又是router实例上的handle方法

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})2

沿着代码的轨迹,我们来到了router文件夹的index.js文件中寻找router.handle方法;为了让代码看起来更加简洁,我删除掉我们这次不讨论的源码

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})3

为了知道最终执行的layer.handle_request函数到底执行了哪些操作,我们来到了router文件夹下面的layer.js文件;仔细阅读后发现,最终调用的就是我们绑定在layer上面的中间件

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})4

至此,用户发送网络请求是怎么执行中间件的这个过程我们就已经探索完了

调用next为什么会执行下一个中间件?

从上面的代码中,可以看到在执行fn函数的时候将reqres以及next作为参数传递了进去,传递进去的next其实就是router.handle中的next函数,所以我们在中间件函数中调用next,实际上调用的是router.handle中的next函数,这里涉及到了js的闭包——可以访问到其他函数作用域中的变量的函数

因为闭包特性,所以next在执行的时候是可以访问到作用域链中的idx变量的,这个变量记录了上一次执行的中间件函数在数组中的索引,所以当我们在中间件函数里面执行next函数的时候,会从idx开始寻找下一个匹配的中间件并执行

const express = require('express')const app = express()app.get('/user', (req, res, next) => {  console.log(req.query); // { name: 'haha', age: '18' }  res.end('Hello')})app.listen(3000, () => {  console.log('express框架初体验');})5
原文:https://juejin.cn/post/7103806298029817892


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Node/930.html