axios 封装 http 请求详解

news2024/11/24 14:19:31

前言

Axios 是一个基于 Promise 的 HTTP 库,它的概念及使用方法本文不过多赘述,请参考:axios传送门
本文重点讲述下在项目中是如何利用 axios 封装 http 请求。


一、预设全局变量

在 /const/preset.js 中配置预先设置一些全局变量

window.$env = process.env.NODE_ENV === 'development' ? 'DEV' : 'PROD'

// 默认开发环境
let config = {
  baseURL: location.origin,
  httpBaseURL: location.origin + '/api',
  webBaseURL: location.origin + location.pathname,
  vipAddress: '/necp/mapp/sc', // 后端微服务的统一入口
}

// 生产环境
if (window.$env !== 'DEV') {
  if (location.href.indexOf('/ecs/') > -1) {
    config.baseURL = location.href.replace(/\/ecs.+/, '')
    config.httpBaseURL = config.baseURL
  }
}

// 文件资源请求路径
config.fileUrl = config.httpBaseURL + config.vipAddress + 'file/download'

window.$globals = config

在 main.js 中引入

import Vue from 'vue'
import './const/preset'
// ...
// 把 vue 示例挂载到 window 下
window.$vm = new Vue({
  render: h => h(App),
  router
}).$mount('#app')

因为生产环境部署的差异,http 请求的 baseURL 并非都是统一的,所以不单独配置默认的 axios.defaults.baseURL,而是通过此文件预设的变量进行设置。

全局预设变量中的 config.httpBaseURL 将添加到请求的 URL 中,对于代码中的 location.href.indexOf(‘/ecs/’) > -1 判断只是举例,可根据实际需求决定是否需要。

二、http 请求封装

1.配置全局 axios 默认值

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.timeout = 60000
axios.defaults.crossDomain = true

此三条配置分别对应以下作用:

  • 发送POST请求时,设置请求头的 Content-Type 字段为 ‘application/json;charset=UTF-8’ ,以便服务器正确解析请求的数据。
  • 发送请求默认的超时时间为 60s。
  • 允许跨域请求。

提示:覆盖默认超时时间,可在 axios 发送请求的参数 config 对象中设置 timeout 属性即可

2.配置请求拦截器

请求拦截器是在发送请求前执行的函数,它可以用于修改请求的配置或者在请求发送前进行一些操作。最常用的功能就是使用请求拦截器实现身份验证

一个常见的实现是用户登录之后,服务端会响应用户的登录信息,并且把用户的身份认证 token 存储到 cookie 中,然后在请求拦截器中将 cookie 中获取到的 token 设置到请求头中,每次发送请求都会携带上此 token 发送到服务端,服务端再获取请求头的 token 来判断用户是否登录状态或者登录已过期,作出不同的响应。

axios.interceptors.request.use(
  config => {
    const token = cookie.get(TOKEN_COOKIE_KEY)
    if (token) {
      config.headers[TOKEN_REQ_KEY] = token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

3.配置响应拦截器

响应拦截器是在接收到响应后执行的函数,它可以用于修改响应的数据或者在接收到响应后进行一些操作。
响应拦截器主要作用包括修改响应数据、错误处理、统一处理响应等功能,因把响应数据及错误的处理都放在了发送请求的回调中,所以只定义了最简单的响应拦截器。

axios.interceptors.response.use(response => {
  return response
}, error => {
  return Promise.reject(error)
})

4.发送请求的 request 函数

此函数接收四个参数:请求方法,请求的 api 接口,请求参数,请求的 config 配置项,返回一个 Promise 的实例。此函数完成了正常响应处理、异常处理、重复请求取消等功能。

4.1 拼接完整的请求 url

const apiInterceptor = api => {
  if (api.startsWith('http')) { // 自定义请求路径
    return api.slice(4)
  }
  if (api.startsWith('_SC_')) { // 项目统一的api前缀
    api = $globals.vipAddress + api.slice(4)
  }
  return $globals.httpBaseURL + api
}

const request = async (method = 'post', api, params = {}, config = {}) => {
  // 省略...
  let url = apiInterceptor(api)
  let opts = {
    method,
    url,
    headers: config.headers || {},
    withCredentials: config.withCredentials || true // 跨域请求时是否需要使用凭证
  }
  // 省略...
}

调用 apiInterceptor 函数来拼接完整的请求 url,如果 api 是以 http 开头,则表示自定义 api 的请求路径,否则请求路径使用 preset.js 中预设的全局变量来拼接完整的 url。

4.2 参数处理

const jsonObj2FormData = jsonObj => {
  let formData = new FormData()
  Object.keys(jsonObj).forEach(key => {
    if (jsonObj[key] instanceof Array) {
      formData.append(key, JSON.stringify(jsonObj[key]))
    } else {
      formData.append(key, jsonObj[key])
    }
  })
  return formData
}
// 省略...

if (config.formDataFormat) {
  opts.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
  params = jsonObj2FormData(params)
}
if (method == 'post') {
  opts.data = params
} else {
  opts.params = params
}
  • 服务端有部分接口接收的参数要求 FormData 格式,这时候需要将参数序列化,并且修改请求头的 Content-Type。
  • 发送 get/post 请求时,接收参数的对象的 key 不一样。

4.3 正常响应处理

使用 axios(opts) 发起请求,得到的是一个 Promise,在 then 的第一个参数中传入一个正常的响应处理函数,这个函数接收响应拦截器中返回的 response 作为参数。

return new Promise((resolve, reject) => {
  axios(opts).then(response => {
     let res = response.data
     if (config.customHandler) { // 自定义响应处理
       if (config.responseAll) return resolve(response)
       return resolve(res)
     }
     if (res) {
       if (res.code === 000) { // 登录超时
          $vm.$toast.error(res.message)
          $vm.$store.dispatch('REMOVE_USER') // 移除 cookie、session、storage 存储的信息
          reject(res.message)
          if (window.self === window.top) {
            $vm.$router.push('/login') // 跳转登录页
          }
        } else if (res.code === 200) {
          resolve(res.data)
        } else {
          $vm.$toast.error(res.message || '接口异常, 请稍后重试')
          reject(res)
        }
     } else {
        $vm.$toast.error('接口无返回内容')
     }
   })
})

提示:$vm 指向全局的 Vue 实例,$toast 则是将 element 的 Message 组件实例挂载到了 Vue 的原型上

  • 如果调用 request 函数传入了 config.customHandler = true,表示自定义响应处理,并且 config.responseAll = true 时,会把响应拦截器中得到的 response 直接返回,这个参数主要用于调用服务端响应字节流的接口时使用。
  • 后端响应的数据结构如下图,并且登录过期接口的 http 响应状态码是 200,但是响应的数据格式中的 code 值为特定值,所以要特殊处理此类情况,清空存储在客户端的客户信息,跳转到登录页。
  • 当响应的数据中与服务端约定响应正常的 code 为 200,此时把 data 作为 Promise.resolve 的值
    response响应结构

4.4 异常处理

异常处理在 axios(opts).then() 的第二个参数中传入处理函数,这个函数接收响应拦截器中返回的 Promise.reject(error) 作为参数。

异常处理主要针对 http 响应状态码不等于 200 的情况,包括常见的请求超时,404请求资源不存在,50X 服务器异常等情况。

axios(opts).then(response => {
  // 省略...
}, error => {
      // 如果自定义处理
      if (config.customHandler) {
        reject(error)
        return
      }
      // 请求超时
      if (error.code == 'ECONNABORTED' && error.message.indexOf('timeout') > -1) {
        $vm.$toast.error(`请求超时,接口地址:${url}`)
        reject(error)
        return
      }
      if (error.response) {
        // 401未登录或登录失效
        if (error.response.status === 401) {
          reject(error)
          if (window.self === window.top) {
            $vm.$router.push('/login')
          }
          return
        }
        switch (error.response.status) {
          case 404:
            $vm.$toast.error(`请求的资源不存在,异常服务接口地址:${url}`)
            break
          case 408:
            $vm.$toast.error('请求超时')
            break
          case 500:
            $vm.$toast.error('服务异常')
            break
          case 502:
            $vm.$toast.error(error.message || '服务未响应')
            break
          case 503:
            $vm.$toast.error(error.message || '服务暂不可访问')
            break
          default:
            $vm.$toast.error(error.response.statusText || '服务异常, 请稍后重试')
        }
      } else {
        $vm.$toast.error(error.response.statusText || '未知错误, 请稍后重试')
      }
      reject(error)
    })

4.5 取消请求

在一些特定情况下,比如用户快速点击提交表单,短时间内同时触发同一个请求多次,我们可以借助 axios.cancelToken 来取消前几次请求,只保留最后一次请求。

主要实现的原理如下:

  1. 每次调用 request 函数时,根据传入的 method + api + JSON.stringify(config) 作为当前请求的标识 key,如果配置了 config.cancelTokenWidthParams = true,时,在 key 后面拼接 JSON.stringify(params) 作为 key。
  2. HTTP_CANCEL_MAP 每一项的 key 为每个请求的 ‘唯一标识 + _ + 时间戳’,每一项 value 设置为 axios.CancelToken 构造函数传入的 executor 函数的参数,也就是 cancel 函数,调用 checkHttpCancel 函数传入 key 判断是否为重复请求,是重复请求则调用 cancel() 取消请求。
  3. 调用 request 函数时,配置 opts.cancelToken,使用 new 调用 CancelToken 的构造函数来创建 cancel token
  4. 请求响应成功和失败时都需要从 HTTP_CANCEL_MAP 中删除 reqUniqueKey 对应的 cancelToken
const CANCEL_TOKEN = axios.CancelToken
const HTTP_CANCEL_MAP = $globals.httpCancelMap = new Map()
const IS_CANCELED_MSG = 'canceled'

const checkHttpCancel = reqKey => {
  HTTP_CANCEL_MAP.forEach((v, k) => {
    if (k.slice(0, -14) === reqKey) {
      v()
      HTTP_CANCEL_MAP.delete(k)
    }
  })
}

const request = async (method = 'post', api, params = {}, config = {}) => {
  let reqKey = method + api + JSON.stringify(config)
  if (config.cancelTokenWidthParams) reqKey += JSON.stringify(params)
  let reqUniqueKey = reqKey + '_' + new Date().getTime()
  checkHttpCancel(reqKey)
  // 省略...
  opts.cancelToken = new CANCEL_TOKEN(c => HTTP_CANCEL_MAP.set(reqUniqueKey, c))
  // ...
  axios(opts).then(response => {
    HTTP_CANCEL_MAP.delete(reqUniqueKey)
    // ...
  }, error => {
    HTTP_CANCEL_MAP.delete(reqUniqueKey)
    if (axios.isCancel(error)) {
      reject(new Error(IS_CANCELED_MSG))
      return
    }
    // ...
  })
})

注意

  1. 此项目使用的 axios 版本为 0.21.1,从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求,CancelToken API被弃用
  2. 可以使用同一个 cancel token 取消多个请求

三、完整的 http.js

import axios from 'axios'
import { TOKEN_REQ_KEY, TOKEN_COOKIE_KEY } from '@/const/common'
import { session, cookie, jsonObj2FormData } from '@/util/common'

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.timeout = 120000
axios.defaults.crossDomain = true

axios.interceptors.request.use(
  config => {
    const token = cookie.get(TOKEN_COOKIE_KEY)
    if (token) {
      config.headers[TOKEN_REQ_KEY] = token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

axios.interceptors.response.use(response => {
  return response
}, error => {
  return Promise.reject(error)
})

const CANCEL_TOKEN = axios.CancelToken
const HTTP_CANCEL_MAP = $globals.httpCancelMap = new Map()
const IS_CANCELED_MSG = 'canceled'

const checkHttpCancel = reqKey => {
  HTTP_CANCEL_MAP.forEach((v, k) => {
    if (k.slice(0, -14) === reqKey) {
      v()
      HTTP_CANCEL_MAP.delete(k)
    }
  })
}

const apiInterceptor = api => {
  if (api.startsWith('http')) { // 自定义请求路径
    return api.slice(4)
  }
  if (api.startsWith('_SC_')) { // 项目统一的api前缀
    api = $globals.vipAddress + api.slice(4)
  }
  return $globals.httpBaseURL + api
}

const request = async (method = 'post', api, params = {}, config = {}) => {
  let reqKey = method + api + JSON.stringify(config)
  if (config.cancelTokenWidthParams) reqKey += JSON.stringify(params)
  let reqUniqueKey = reqKey + '_' + new Date().getTime()
  checkHttpCancel(reqKey)

  return new Promise((resolve, reject) => {
    if (config.loading) $vm.$loading.show()
    let url = apiInterceptor(api)
    let opts = {
      method,
      url,
      headers: config.headers || {},
      withCredentials: config.withCredentials || true // 跨域请求时是否需要使用凭证
    }
    if (config.formDataFormat) {
      opts.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
      params = jsonObj2FormData(params)
    }
    if (config.timeout) opts.timeout = config.timeout
    if (config.extends) opts = Object.assign(opts, config.extends) // 如果有并列层级的参数扩展
    if (method == 'post') {
      opts.data = params
    } else {
      opts.params = params
    }
    opts.cancelToken = new CANCEL_TOKEN(c => HTTP_CANCEL_MAP.set(reqUniqueKey, c))
    if (config.responseType) opts.responseType = config.responseType
    
    // 发起 axios 请求
    axios(opts).then(response => {
      HTTP_CANCEL_MAP.delete(reqUniqueKey)
      if (config.loading) $vm.$loading.close()
      let res = response.data
      if (config.customHandler) { // 自定义响应处理
        if (config.responseAll) return resolve(response)
        return resolve(res)
      }
      if (res) {
        if (res.code === 000) { // 登录超时
          $vm.$toast.error(res.message)
          $vm.$store.dispatch('REMOVE_USER') // 移除 cookie、session、storage 存储的信息
          reject(res.message)
          if (window.self === window.top) {
            $vm.$router.push('/login') // 跳转登录页
          }
        } else if (res.code === 200) {
          resolve(res.data)
        } else {
          $vm.$toast.error(res.message || '接口异常, 请稍后重试')
          reject(res)
        }
      } else {
        $vm.$toast.error('接口无返回内容')
      }
    }, error => {
      HTTP_CANCEL_MAP.delete(reqUniqueKey)
      if (axios.isCancel(error)) {
        reject(new Error(IS_CANCELED_MSG))
        return
      }
      if (config.loading) $vm.$loading.close()
      // 如果自定义处理
      if (config.customHandler) {
        reject(error)
        return
      }
      // 请求超时
      if (error.code == 'ECONNABORTED' && error.message.indexOf('timeout') > -1) {
        $vm.$toast.error(`请求超时,接口地址:${url}`)
        reject(error)
        return
      }
      if (error.response) {
        // 401未登录或登录失效
        if (error.response.status === 401) {
          reject(error)
          if (window.self === window.top) {
            $vm.$router.push('/login')
          }
          return
        }
        switch (error.response.status) {
          case 404:
            $vm.$toast.error(`请求的资源不存在,异常服务接口地址:${url}`)
            break
          case 408:
            $vm.$toast.error('请求超时')
            break
          case 413:
            $vm.$toast.error('请求实体大小超过服务器最大限制')
            break
          case 500:
            $vm.$toast.error('服务异常')
            break
          case 502:
            $vm.$toast.error(error.message || '服务未响应')
            break
          case 503:
            $vm.$toast.error(error.message || '服务暂不可访问')
            break
          default:
            $vm.$toast.error(error.response.statusText || '服务异常, 请稍后重试')
        }
      } else {
        $vm.$toast.error(error.response.statusText || '未知错误, 请稍后重试')
      }
      reject(error)
    })
  })
}

export default {
  get: (api, params = {}, config = {}) => {
    return request('get', api, params, config)
  },
  post: (api, params = {}, config = {}) => {
    return request('post', api, params, config)
  },
  image: id => {
    return `${$globals.fileUrl}?fileId=${id}`
  },
  isCanceled: error => {
    if (error && error.message === IS_CANCELED_MSG) return true
    return false
  }
}
  1. http.image 方法仅用于返回文件的请求完整 url,使用场景为比如 <img> 标签中的 src 的值
  2. http.isCanceled 方法用于判断当前请求是否取消,如果有请求未取消并且出现全局 loading 加载未关闭的情况,可根据此标志来判断是否关闭

四、封装成插件并挂载到原型

/plugins/http/install.js

import httpService from '@/service/http'

export default {
  install: Vue => {
    Vue.prototype.$http = httpService
  }
}

五、管理 api

例如,根据业务可划分为文档,评论等模块,在 service 目录下分别创建对应的模块存放 api 的 js 文件,对 api 进行统一管理。

强烈建议给每个 api 备注功能,提高可维护性

service模块划分
/service/comment.js

/**
 * @name 获取评论列表
 * @param {Object} params 请求参数对象
 */
export const getCommentListPromise = params => {
  params = Object.assign({
    page: 0, // 页码
    pageSize: 5, // 每页数量
  }, params)
  return $vm.$http.get('_SC_/comment/findCommentList', params)
}

在 Comment.vue 页面中使用

import { getCommentListPromise } from '@/service/comment'
async findCommentList() {
  const data = await getCommentListPromise()
  console.log(data)
}

总结

本文主要讲述了如何使用 axios 进行 http 封装的详细过程,及在项目中如何使用封装的 http 请求,请求拦截器和响应拦截器都是比较简单,没有处理很多的逻辑,逻辑处理基本是集中在 request 函数中。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Qt 学习笔记】Qt 背景介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt 背景介绍 文章编号&#xff1a;Qt 学习笔记 / 01 文章目录 Qt 背景…

vue 条件渲染、列表循环渲染、事件绑定 初探第三天

条件渲染 <script>const app Vue.createApp({data(){return {show:true,conditionOne: false,conditionTwo: true,}},template:<div v-if"show"> hello word </div><div v-if"conditionOne"> if </div><div v-else…

[lesson02]C到C++的升级

C到C的升级 C与C的关系 C继承了所有的C特性C在C的基础上提供了更多的语法和特性C的设计目标是运行效率与开发效率的统一 C到C的升级 C更强调语言的实用性 所有的变量都可以在需要使用时再定义 int c 0; for (int i 1; i < 3; i) {for(int j 1; j < 3; j){c i * …

Kubernetes(k8s):部署、使用 metrics-server

Kubernetes&#xff08;k8s&#xff09;&#xff1a;部署、使用 metrics-server 一、metrics-server简介二、部署metrics-server2.1、 下载 Metrics Server 部署文件2.2、修改metrics-server.yaml 文件2.3、 部署 Metrics Server2.4、 检查 Metrics Server 三、使用 Metrics Se…

国资委确定首批起航企业,重点布局人工智能、量子信息等新兴领域

国务院国资委近日按照“四新”&#xff08;新赛道、新技术、新平台、新机制&#xff09;标准&#xff0c;遴选确定了首批启航企业&#xff0c;加快新领域新赛道布局、培育发展新质生产力。 据了解&#xff0c;去年以来&#xff0c;国务院国资委围绕加快培育创新型国有企业&…

hadoop 查询hdfs资源信息的方式

hdfs dfsadmin -report &#xff3b;-live&#xff3d;&#xff3b;-dead&#xff3d;&#xff3b;-decommissioning&#xff3d;

用动态规划求解多段图的最短路径问题

题目描述 对如下图所示的一个5段图&#xff0c;图上的数字代表该段路径的成本。写出求最短路径的计算过程&#xff0c;给出最短路径和距离。 思路分析 创建一个边权数组edgeWeigth&#xff0c;存储顶点和边的信息&#xff0c;用来表示图创建一个cost数组&#xff0c;索引in…

【MySQL系列】使用 ALTER TABLE 语句修改表结构的方法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

STM32 DWT数据观察触发器作为延时函数的使用

STM32 DWT数据观察触发器作为延时函数的使用 &#x1f4d1;DWT(Data Watchpoint and Trace数据观察触发器&#xff09;描述 &#x1f4dd;DWT是属于处理器内核单元中的调试组件之一&#xff0c;由四个比较器组成。它们可配置为&#xff1a;硬件监视点或对ETM或PC采样器或数据地…

智慧工厂建设全面指南 从规划到实施

随着工业4.0和工业互联网的发展&#xff0c;智慧工厂已成为制造业的热门话题。智慧工厂通过运用先进的信息技术、自动化技术和人工智能技术&#xff0c;实现生产过程的智能化、数字化和网络化&#xff0c;提高生产效率、降低生产成本、提升产品质量&#xff0c;为企业创造更大的…

【C++第二阶段】文件操作

以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 文件操作文件写入流程简单的demo写操作 文件读流程二进制写文件二进制读文件 文件操作 文件写入流程 写文件包括以下几个步骤 1.包含头文件 2.创建流对象 3.打开文件&#xff0…

CSS面试题---基础

1、css选择器及优先级 选择器优先级&#xff1a;内联样式>id选择器>类选择器、属性选择器、伪类选择器>标签选择器、微元素选择器 注意&#xff1a; !important优先级最高&#xff1b; 如果优先级相同&#xff0c;则最后出现的样式生效&#xff1b; 继承得到的样式优先…

商城网站-礼品网站首页html+css+js+说明文档

网页设计与网站建设作业htmlcssjs 预览 说明 单页面&#xff0c;轮播图 获取&#xff1a;https://hpc.baicaitang.cn/2077.html

MyBatis-Plus04(条件构造器)

条件构造器和常用接口 wrapper介绍 Wrapper &#xff1a; 条件构造抽象类&#xff0c;最顶端父类 AbstractWrapper &#xff1a; 用于查询条件封装&#xff0c;生成 sql 的 where 条件 QueryWrapper &#xff1a; 查询条件封装 UpdateWrapper &#xff1a; Update 条件封装 A…

【stm32】USART编码部分--串口数据包

USART串口数据包【源码放在最后】 关于数据包的分类 关于数据包的发送 如果想要发送Hex数据包&#xff0c;定义一个数组填充数据&#xff0c;然后使用串口模块函数SendArray进行发送 如果想要发送文本数据包&#xff0c;写一个字符串然后调用SendString进行发送 对于发送数据…

BEV的多传感器融合方案

多模态融合 早期融合&#xff08;Early Fusion&#xff09;晚期融合&#xff08;Late Fusion&#xff09;深度融合&#xff08;Deep Fusion&#xff09; BEVFusion - MITBEVFusion - 阿里&北大UniTR 感知技术中相机和雷达数据融合方法的概述&#xff0c;这些方法分别是早期…

[原创] MOS管选型

一、选型背景 MOS管是常用元器件之一&#xff0c;学校出来后&#xff0c;很多人都对BJT感兴趣和熟悉&#xff0c;对MOS的熟悉度相对BJT要少一些&#xff0c;即使工作了两三年&#xff0c;对MOS的理解也还是不多&#xff0c;下面通过对MOS管的一些参数解读&#xff0c;加深对MO…

第二届安徽省中小学科技竞赛经验交流会暨NOC省赛解读会在肥顺利举办

阳春三月&#xff0c;喜泰开来。3月30日&#xff0c;第二届安徽省中小学科技竞赛经验交流会暨NOC省赛解读会在合肥顺利举办&#xff01;本次会议由安徽省人工智能学会主办&#xff0c;赛哆哆承办&#xff0c;会议吸引了近两百名来自全省的中小学教师和科技培训教师&#xff0c;…

【浅尝C++】STL第三弹=>list常用接口使用示例/list底层结构探索/list模拟实现代码详解

&#x1f3e0;专栏介绍&#xff1a;浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 &#x1f3af;每日格言&#xff1a;每日努力一点点&#xff0c;技术变化看得见。 文章目录 list介绍list常用接口使用示例构造类函数迭代器属性与元素获取增删改操作 list底层结构探索list模…

【保姆级讲解下Docker容器】

&#x1f308;个人主页:程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…