Express 实现原理学习 - 简单模拟

2024-08-23 01:08

本文主要是介绍Express 实现原理学习 - 简单模拟,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本笔记通过模拟实现 Express 的一些功能学习 Express 的实现原理。

初始化应用

mkdir my-express
cd ./my-express
npm init -y
npm i express

添加 app.js

// app.js
const express = require('express')const app = express()app.get('/', (req, res) => {res.end('get /')
})app.get('/about', (req, res) => {res.end('get /about')
})app.listen(3000, () => {console.log('server is running on http://localhost:3000')
})

源码结构

app.js 代码上看,首先会 require('express') 加载 Express 的 npm 包,它会去 node_modules/express/package.json 中寻找 main 字段。

main 字段指定模块的入口文件,如果没有 main 字段,则默认加载 npm 包根目录下的 index.js

node_modules/express/index.js 中只是导入了另一个模块:

'use strict';
module.exports = require('./lib/express');

node_modules/express/lib/express.js 主要导出了一个 createApplication 方法

// 只截取了片段
// 主要导出了一个 `createApplication` 方法
exports = module.exports = createApplication;// 创建并返回 app
function createApplication() {// app 本身是一个函数var app = function(req, res, next) {app.handle(req, res, next);};// 通过 mixin 方法扩展其他成员mixin(app, EventEmitter.prototype, false);// proto --> require('./application')mixin(app, proto, false);// 扩展 requestapp.request = Object.create(req, {app: { configurable: true, enumerable: true, writable: true, value: app }})// 扩展 responseapp.response = Object.create(res, {app: { configurable: true, enumerable: true, writable: true, value: app }})app.init();return app;
}

createApplication 方法创建并返回一个 app 函数,内部通过 mixin 方法扩展了很多其他成员。

成员中最核心的东西在于 node_modules/express/lib/application.js,内部创建并返回了一个 app 对象,然后向这个对象上添加了很多成员,如 userouterlisten 等:

// 只截取了片段
var app = exports = module.exports = {};app.use = function use(fn) {...}
app.route = function route(path) {...}
app.listen = function listen() {...}

mixin 方法就是将 application.js 返回的 app 对象的成员混入到当前 createApplication 方法创建的 app 对象中。

其他还有:

  • 在 node 的 http 模块之上扩展了 requestresponse 对象:lib/request.jslib/response.js
  • 内置了一些工具函数:lib/utils.js
  • 对模板引擎的相关设置处理:lib/view.js
  • 对路由的相关处理:lib/router
  • 内部的中间件:lib/middleware

接下来模仿 Express 的目录结构实现一些内容。

快速体验

在项目下创建 express 目录,实现模拟的 express,在目录下添加文件。

// express\index.js
module.exports = require('./lib/express')
// express\lib\express.js
const http = require('http')
const url = require('url')// 路由表
const routes = [// { path: '', method: '', handler: () => { } }
]function createApplication() {return {get(path, handler) {// 收集路由routes.push({path,method: 'get',handler,})},listen(...args) {const server = http.createServer((req, res) => {/* 接收到请求的时候处理路由 */const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const route = routes.find(route => route.path === pathname && route.method === method)if (route) {return route.handler(req, res)}res.end('404 Not Found.')})server.listen(...args)}}
}module.exports = createApplication

抽取创建 App 的模块

为了方便扩展 app,现在将内部的方法提取到一个构造函数中,通过 new 的方式创建实例,而将这些方法定义为 App 的实例方法。

// express\lib\application.js
const http = require('http')
const url = require('url')function App() {// 路由表this.routes = []
}App.prototype.get = function (path, handler) {// 收集路由this.routes.push({path,method: 'get',handler,})
}App.prototype.listen = function (...args) {const server = http.createServer((req, res) => {/* 接收到请求的时候处理路由 */const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const route = this.routes.find(route => route.path === pathname && route.method === method)if (route) {return route.handler(req, res)}res.end('404 Not Found.')})server.listen(...args)
}module.exports = App
// express\lib\express.js
const App = require('./application')function createApplication() {return new App()
}module.exports = createApplication

提取路由模块

源码:node_modules\express\lib\router\index.js

为了更方便的在 app 中对路由进行开发和管理,建议将路由相关处理封装到单独的模块中。

同样的,创建一个 Router 构造函数,所有和路由相关的都通过 Router 的路由实例进行。

// router\index.js
const url = require('url')function Router() {// 路由记录栈this.stack = []
}Router.prototype.get = function (path, handler) {this.stack.push({path,method: 'get',handler,})
}Router.prototype.handle = function (req, res) {const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const route = this.stack.find(route => route.path === pathname && route.method === method)if (route) {return route.handler(req, res)}res.end('404 Not Found.')
}module.exports = Router

路由表 routes 改为 express 中的命名 stack

// express\lib\application.js
const http = require('http')
const Router = require('./router')function App() {// 路由表this._router = new Router()
}App.prototype.get = function (path, handler) {this._router.get(path, handler)
}App.prototype.listen = function (...args) {const server = http.createServer((req, res) => {this._router.handle(req, res)})server.listen(...args)
}module.exports = App

处理不同的请求方法

路由示例

// app.js
app.get('/', (req, res) => {res.end('get /')
})app.get('/about', (req, res) => {res.end('get /about')
})app.post('/about', (req, res) => {res.end('post /about')
})app.patch('/about', (req, res) => {res.end('patch /about')
})app.delete('/about', (req, res) => {res.end('delete /about')
})

Express 源码

每个 method 的请求处理逻辑是一样的,只是 method 名不一样,可以遍历常用请求方法,批量的添加处理逻辑。

express 内部使用 methods 包,它导出了 HTTP 常用的请求方法。

它只有一个 js 文件(node_modules\methods\index.js):

'use strict';
// 导入 nodejs 原生模块 http
var http = require('http');// 优先 http 中支持的 methods,如果没有则导出手动整理的 methods
module.exports = getCurrentNodeMethods() || getBasicNodeMethods();// 获取 nodejs 原生模块 http 的 METHODS 属性
// 它标识 node 环境中支持的 HTTP 请求方法
// 将它们遍历小写处理并返回
function getCurrentNodeMethods() {return http.METHODS && http.METHODS.map(function lowerCaseMethod(method) {return method.toLowerCase();});
}// 手动整理 methods
function getBasicNodeMethods() {return ['get','post','put','head','delete','options','trace','copy','lock','mkcol','move','purge','propfind','proppatch','unlock','report','mkactivity','checkout','merge','m-search','notify','subscribe','unsubscribe','patch','search','connect'];
}

模拟实现

可以直接使用 methods 模块:

// express\lib\application.js
const http = require('http')
const Router = require('./router')
const methods = require('methods')function App() {// 路由表this._router = new Router()
}// 为每个请求方法添加处理函数
methods.forEach(method => {App.prototype[method] = function (path, handler) {this._router[method](path, handler)}
})App.prototype.listen = function (...args) {const server = http.createServer((req, res) => {this._router.handle(req, res)})server.listen(...args)
}module.exports = App
// router\index.js
const url = require('url')
const methods = require('methods')function Router() {// 路由记录栈this.stack = []
}// 为每个请求方法添加处理函数
methods.forEach(method => {Router.prototype[method] = function (path, handler) {this.stack.push({path,method,handler,})}
})Router.prototype.handle = function (req, res) {const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const route = this.stack.find(route => route.path === pathname && route.method === method)if (route) {return route.handler(req, res)}res.end('404 Not Found.')
}module.exports = Router

注意:当前可以直接使用这些第三方包是因为本项目安装了 express,所以安装了它的依赖。如果要单独发布自己实现的 express,应该在这个包的目录下安装依赖:

# 进入到模拟 express 的模块目录下
cd express
npm init -y
# 安装依赖
npm i methods
# 建议安装和 Express 一样的版本
# npm i methods@1.1.2

现在,在 express(模拟的)中加载 methods 的时候就会优先加载当前目录(/express)下的 node_modules/methods 了。

实现更强大的路由路径匹配模式

源码:node_modules\express\lib\router\layer.js

Express 内部支持路由路径的多种匹配模式:Route paths

路由示例

// app.js
app.get('/', function (req, res) {res.end('root')
})app.get('/about', function (req, res) {res.end('about')
})app.get('/random.text', function (req, res) {res.end('random.text')
})app.get('/ab?cd', function (req, res) {res.end('ab?cd')
})app.get('/ab+cd', function (req, res) {res.end('ab+cd')
})app.get('/ab*cd', function (req, res) {res.end('ab*cd')
})app.get('/ab(cd)?e', function (req, res) {res.end('ab(cd)?e')
})app.get(/a/, function (req, res) {res.end('/a/')
})app.get(/.*fly$/, function (req, res) {res.end('/.*fly$/')
})app.get('/users/:userId/books/:bookId', function (req, res) {res.end(req.params)
})

PS:response.send() 方法是 Express 在 nodejs http.ServerResponse 对象的基础上扩展的方法,当前还没有定义,http.ServerResponse 本身没有 .send() 方法,所以这里使用的 res.end()

Express 源码

Express 内部使用的 path-to-regexp 模块实现路由匹配,并且可以解析动态参数。

// node_modules\express\lib\router\layer.js
var pathRegexp = require('path-to-regexp');this.regexp = pathRegexp(path, this.keys = [], opts);

模拟实现

安装依赖

# 在 /express 目录下执行
# 注意:path-to-regexp 最新版已经不支持 Express 4 了,要安装老版本,版本号参考 Express 包下的 package.json
npm i path-to-regexp@0.1.7

处理路径

// router\index.js
const url = require('url')
const methods = require('methods')
const pathRegexp = require('path-to-regexp')function Router() {// 路由记录栈this.stack = []
}// 为每个请求方法添加处理函数
methods.forEach(method => {Router.prototype[method] = function (path, handler) {this.stack.push({path,method,handler,})}
})Router.prototype.handle = function (req, res) {const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const route = this.stack.find(route => {const keys = []// 基于配置的路由匹配规则,生成一个正则表达式const regexp = pathRegexp(route.path, keys, {})// 匹配实际请求路径的结果const match = regexp.exec(pathname)return match && route.method === method})if (route) {return route.handler(req, res)}res.end('404 Not Found.')
}module.exports = Router

处理动态路径参数

假设访问 '/users/:userId/books/:bookId' 路由地址: http://localhost:3000/users/tom/books/123

path-to-regexp 会解析动态参数,将解析的参数名填充到传入的 keys 数组,参数值可以通过正则匹配后的结果中查看:

const keys = []
const regexp = pathRegexp(route.path, keys, {})
const match = regexp.exec(pathname)
console.log('keys =>',keys)
console.log('match =>',match)

打印结果:

keys => [{ name: 'userId', optional: false, offset: 8 },{ name: 'bookId', optional: false, offset: 30 }
]
match => ['/users/tom/books/123','tom','123',index: 0,input: '/users/tom/books/123',groups: undefined
]

所以可以将参数名和参数值映射在一起,挂载到 req.params 上:

// router\index.js
const route = this.stack.find(route => {const keys = []// 基于配置的路由匹配规则,生成一个正则表达式const regexp = pathRegexp(route.path, keys, {})// 匹配实际请求路径的结果const match = regexp.exec(pathname)// 解析动态参数if (match) {req.params = req.params || {}keys.forEach((key, index) => {req.params[key.name] = match[index + 1]})}return match && route.method === method
})

提取 Layer 处理模块

Express 源码

源码:node_modules\express\lib\router\layer.js

Express 中是将路由路径匹配单独封装到了一个模块中管理的。

// node_modules\express\lib\router\index.js
var layer = new Layer(path, {sensitive: this.caseSensitive,strict: this.strict,end: true
}, route.dispatch.bind(route));layer.route = route;// 路由栈里存储的 Layer 实例
this.stack.push(layer);
// 调用的 layer 的 match 方法进行匹配
layer = stack[idx++];
match = matchLayer(layer, path);
function matchLayer(layer, path) {try {return layer.match(path);} catch (err) {return err;}
}
// node_modules\express\lib\router\layer.js
module.exports = Layer;function Layer(path, options, fn) {if (!(this instanceof Layer)) {return new Layer(path, options, fn);}debug('new %o', path)var opts = options || {};this.handle = fn;this.name = fn.name || '<anonymous>';this.params = undefined;this.path = undefined;// 实例化时就生成了路由的正则表达式this.regexp = pathRegexp(path, this.keys = [], opts);// set fast path flagsthis.regexp.fast_star = path === '*'this.regexp.fast_slash = path === '/' && opts.end === false
}Layer.prototype.match = function match(path) {...}

模拟实现

// express\lib\router\layer.js
const pathRegexp = require('path-to-regexp')function Layer(path, handler) {this.path = paththis.handler = handlerthis.keys = []this.regexp = pathRegexp(path, this.keys, {})this.params = {}
}Layer.prototype.match = function (pathname) {const match = this.regexp.exec(pathname)if (match) {this.keys.forEach((key, index) => {this.params[key.name] = match[index + 1]})return true}return false
}module.exports = Layer
// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')function Router() {// 路由记录栈this.stack = []
}// 为每个请求方法添加处理函数
methods.forEach(method => {Router.prototype[method] = function (path, handler) {const layer = new Layer(path, handler)layer.method = methodthis.stack.push(layer)}
})Router.prototype.handle = function (req, res) {const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()const layer = this.stack.find(layer => {const match = layer.match(pathname)if (match) {req.params = req.params || {}Object.assign(req.params, layer.params)}return match && layer.method === method})if (layer) {return layer.handler(req, res)}res.end('404 Not Found.')
}module.exports = Router

实现单个处理函数的中间件功能

路由示例

app.get('/', (req, res, next) => {console.log(1)next()
}, (req, res, next) => {console.log(2)next()
}, (req, res, next) => {console.log(3)next()
})app.get('/', (req, res) => {// 响应res.end('get /')
})app.get('/foo', (req, res, next) => {console.log('foo 1')next()
})app.get('/foo', (req, res, next) => {console.log('foo 2')next()
})app.get('/foo', (req, res) => {// 响应res.end('get /foo')
})

模拟实现

不能直接遍历匹配到的每个路由的 handler,考虑到处理函数中可能有异步操作,可以自定义一个 next 方法(内部处理下一个路由),将其作为参数传递给 handler,由 handler 自己决定处理下一个路由(调用 next)的时机。

// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')function Router() {// 路由记录栈this.stack = []
}// 为每个请求方法添加处理函数
methods.forEach(method => {Router.prototype[method] = function (path, handler) {const layer = new Layer(path, handler)layer.method = methodthis.stack.push(layer)}
})Router.prototype.handle = function (req, res) {const { pathname } = url.parse(req.url)const method = req.method.toLowerCase()let index = 0const next = () => {if (index >= this.stack.length) {return res.end(`Cannot GET ${pathname}`)}const layer = this.stack[index++]const match = layer.match(pathname)if (match) {req.params = req.params || {}Object.assign(req.params, layer.params)}if (match && layer.method === method) {return layer.handler(req, res, next)}// 如果当前请求没有匹配到路由,继续遍历下一个路由next()}// 调用执行next()
}module.exports = Router
// 访问 `/foo` 打印结果:
foo 1
foo 2// 访问 `/ 打印结果(只执行了每个路由的第一个处理函数):
1

实现多个处理函数的中间件

思路分析

当前路由表 stack 中每项存储的是单独的 layer,分别包含一个处理函数 handler。

实际上 Express 在路由表(Router)中还存储了一份路由表(Route),表中存放的是为每个处理函数创建的 layer,这样就可以遍历每个路由的多个处理函数。

数据结构类似:

Routerstack [Layer 1 {path: 'xx', // 请求路径dispatch, // 处理方法,内部调用 Route 下的 dispatchRoute {stack [Layer { path: 'xx', method: 'xxx', handler: 'xxx' },Layer { path: 'xx', method: 'xxx', handler: 'xxx' },Layer { path: 'xx', method: 'xxx', handler: 'xxx' }],dispatch // 遍历调用 stack 中的 handler}},Layer 2 {path: 'xx', // 请求路径dispatch, // 处理方法Route {stack [Layer { path: 'xx', method: 'xxx', handler: 'xxx' },Layer { path: 'xx', method: 'xxx', handler: 'xxx' },Layer { path: 'xx', method: 'xxx', handler: 'xxx' }],dispatch // 遍历调用 stack 中的 handler}},]

组织数据结构

// express\lib\router\route.js
const methods = require('methods')
const Layer = require('./layer')function Route() {this.stack = [// { path, method, handler }]
}// 遍历执行当前路由对象中所有的处理函数
Route.prototype.dispatch = function () { }methods.forEach(method => {Route.prototype[method] = function (path, handlers) {handlers.forEach(handler => {const layer = new Layer(path, handler)layer.method = methodthis.stack.push(layer)})}
})module.exports = Route
// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')
const Route = require('./route')function Router() {// 路由记录栈this.stack = []
}// 为每个请求方法添加处理函数
methods.forEach(method => {Router.prototype[method] = function (path, handlers) {const route = new Route()const layer = new Layer(path, route.dispatch.bind(route))layer.route = routethis.stack.push(layer)route[method](path, handlers)}
})Router.prototype.handle = function (req, res) {...}module.exports = Router
// express\lib\application.js
...methods.forEach(method => {// 接受全部处理函数App.prototype[method] = function (path, ...handlers) {this._router[method](path, handlers)}
})...

可以打印路由看看结果:

// app.js
app.get('/foo', (req, res) => {// 响应res.end('get /foo')
})console.log(app._router)

执行处理函数

Express 可以通过下面的 API 配置路由:

app.route('/foo').get((req, res) => {}).post((req, res) => {})

在执行处理函数的时候,在外层路由匹配路径,在内层路由匹配请求方法,所以这里将匹配 method 去掉:

// router\index.js
Router.prototype.handle = function (req, res) {...const next = () => {...// 顶层只匹配请求路径,内层匹配请求方法// if (match && layer.method === method) {if (match) {// 顶层调用的 handler 就是 route.dispatch 函数return layer.handler(req, res, next)}next()}next()
}
// express\lib\router\route.js
const methods = require('methods')
const Layer = require('./layer')...// 遍历执行当前路由对象中所有的处理函数
// 遍历内层的 stack,内层遍历结束就要返回到外层,所以第三个参数名取名 out 而不是 next
Route.prototype.dispatch = function (req, res, out) {const method = req.method.toLowerCase()let index = 0const next = () => {if (index >= this.stack.length) {return out()}const layer = this.stack[index++]if (layer.method === method) {return layer.handler(req, res, next)}next()}next()
}...module.exports = Route

再次访问 / 打印结果:

1
2
3

实现 use 方法

回顾使用规则

// 不验证请求方法和请求路径,任何请求都会处理
// app.use((req, res, next) => {
//   res.end('hello')
// })// 匹配的不是 `/foo`,而是以 `/foo` 作为前置路径的路由
// app.use('/foo', (req, res, next) => {
//   res.end('foo')
// })// 与不传第一个路径参数等效
// app.use('/', (req, res, next) => {
//   res.end('hello')
// })// 可以挂载多个请求处理函数
app.use('/', (req, res, next) => {console.log(1)next()
}, (req, res, next) => {console.log(2)next()
}, (req, res, next) => {res.end('hello')
})

模拟实现

// express\lib\application.js
App.prototype.use = function (path, ...handlers) {this._router.use(path, handlers)
}
// router\index.js
Router.prototype.use = function (path, handlers) {// 没有传路径的时候if (typeof path === 'function') {handlers.unshift(path)path = '/' // 任何路径都以它开头}handlers.forEach(handler => {const layer = new Layer(path, handler)// 标记是否是 use 类型的中间件layer.isUseMiddleware = truethis.stack.push(layer)})
}
// express\lib\router\layer.js
const pathRegexp = require('path-to-regexp')function Layer(path, handler) {this.path = paththis.handler = handlerthis.keys = []this.regexp = pathRegexp(path, this.keys, {})this.params = {}
}Layer.prototype.match = function (pathname) {const match = this.regexp.exec(pathname)if (match) {this.keys.forEach((key, index) => {this.params[key.name] = match[index + 1]})return true}// 匹配 use 中间件的路径if (this.isUseMiddleware) {if (this.path === '/') {return true}if (pathname.startsWith(`${this.path}/`)) {return true}}return false
}module.exports = Layer

这篇关于Express 实现原理学习 - 简单模拟的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097920

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi