Starting again myself 02
1.浏览器的存储方式
cookie:最早的浏览器存储方式,容量小4k以内,可设置过期时间分为会话Cookie(关闭浏览器后删除)持久Cookie(根据设定时间)
localStorage:持久化本地存储,H5标准键值的形式,h5新增容量大5M左右,保存网页离线数据如草稿文章等
sessionStorage:临时会话数据,H5标准键值的形式,当前页面关闭后清理,项目管理系统中跳转多个页面传递临时数据,临时表单的缓存多步骤缓存前序步骤避免刷新后丢失。
indexedDB:存储大量数据,H5标准键值的形式,需要再客户端进行操作的。
2.token的存储以及登录流程
token:验证身份令牌,用户账号密码登录后,服务端加密后返回的字符串。
1.存储于localStorage中,后期每次请求需要当成一个字段传回后端,缺点xss攻击。
2.存储于cookie中,会自动发送,缺点不能跨域以及CSRF攻击。
登录流程处理:
- 账号密码登录后,服务端验证账号密码成功返回token,
- 前端进行存储处理以及拦截器统一处理,
- 后续发送请求数据时再携带token请求。
实际项目中携带token
axios库,通过拦截器统一处理所有Token请求
2.1.配置axios拦截器(全局配置)
// src/utils/request.js
import axios from 'axios'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types' // 假设定义了 Token 键名
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL, // 基础接口地址
timeout: 5000 // 超时时间
})
// 请求拦截器:添加 Token 到请求头
service.interceptors.request.use(
config => {
// 从 Vue.ls 中获取 Token
const token = Vue.ls.get(ACCESS_TOKEN)
if (token) {
// 通常后端要求的格式是 Bearer + 空格 + Token
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
// 请求错误处理
return Promise.reject(error)
}
)
// 响应拦截器:处理 Token 验证失败的情况
service.interceptors.response.use(
response => {
// 正常响应处理
return response.data
},
error => {
// 处理 401/403 错误
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
// 清除无效 Token
Vue.ls.remove(ACCESS_TOKEN)
// 跳转至登录页(携带当前页面路径,方便登录后返回)
window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname)}`
}
return Promise.reject(error)
}
)
export default service
2.2.封装好后进行请求
// src/api/user.js
import request from '@/utils/request'
// 获取用户信息(自动携带 Token)
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
// 在组件中调用
import { getUserInfo } from '@/api/user'
export default {
mounted() {
getUserInfo().then(res => {
console.log('用户信息:', res)
}).catch(err => {
console.error('请求失败:', err)
})
}
}
3.输入URL浏览器渲染页面过程
URL解析于DNS查询:解析输入的url,提取域名,通过DNS域名系统查询,域名转换为对应服务IP
建立TCPl连接:浏览器使用IP与服务器建立连接,通常为三次握手协议
发送HTTP请求服务器并处理返回值
渲染页面:获取htm与css资源,分别解析成对应Dom与cssDom以及js操作Dom节点--》合并渲染布局绘制。
断开连接:请求渲染完成
4.精灵图与base64
精灵图:项目管理系统中按钮自定义icon的使用精灵图(小图合并到大图上通过定位显示)。
base64:img中引入,图片与代码一同加载。
| 对比维度 | 精灵图(CSS Sprite) | Base64 |
|---|---|---|
| HTTP 请求数 | 减少请求,N 个小图合并后仅需 1 次请求 | 无额外请求,图片随代码一同加载 |
| 资源体积 | 合并后体积≈所有小图体积之和(略增) | 编码后体积比原图片大 20%-30% |
| 缓存机制 | 可单独缓存,更新需重新加载整张大图 | 随代码(HTML/CSS)缓存,无法单独缓存 |
| 适用场景 | 多个固定不变的小图标、小按钮 | 极小图片(<2KB)、一次性使用的图片 |
| 维护成本 | 需维护合并工具和坐标,更新较繁琐 | 无需维护额外图片文件,更新更简单 |
5.前端的缓存策略(强缓存与协商缓存)
前端的缓存指的是浏览器或者代理服务器对已请求的资源本地存储机制,核心目的是避免重复请求相同资源。
无缓存:每次请求都获取资源;
强缓存:在有效期内直接使用本地缓存,不发送请求;
协商缓存:通过服务器验证后决定是否使用缓存;
参考具体相关文档
https://juejin.cn/post/7527526971396456483
6.同源策略以及跨域问题解决
6.1.同源策略
浏览器核心的安全机制,防止网页恶意修改数据,协议,域名,端口三者相同为同源。
https://www.example.com:443/index.html
协议 域名 端口 资源
6.2.解决跨域问题
cors(跨域资源共享):后端服务器在响应头设置Access-Control-Allow-Origin 等字段,明确允许哪些源的请求。
SONP:利用 <script> 标签不受同源策略限制的特性,通过加载远程脚本并执行回调函数来获取数据。但仅支持 GET 请求,安全性较低。
代理服务器:在同源服务器端设置一个代理,由代理服务器向目标跨源服务器发起请求,再将结果返回给前端,从而间接实现跨源数据获取。
7.防抖与节流
7.1.防抖
理论:当事件被触发后,不会立即执行函数,而是等待一段时间。如果在这段时间内事件再次被触发,则重新计时。只有当事件停止触发并等待足够时间后,函数才会执行。
实际应用场景:项目管理系统中的输入电话号码查询
7.2.节流
理论:规定一个时间间隔,在这个时间间隔内,无论事件触发多少次,函数只会执行一次。即高频事件触发时,函数会按照固定频率执行。
实际应用场景:滚动加载(每隔一段时间检测滚轮的位置),图片的懒加载。
8.无感登录
8.1.无感登录流程
无感登录核心依赖 "双令牌机制":
返回双令牌:短期令牌过期,刷新令牌有效,自动请求更新短期令牌使用
-
Access Token(访问令牌):短期有效(如 30 分钟),用于接口访问验证
-
Refresh Token(刷新令牌):长期有效(如 30 天),用于在 Access Token 过期时获取新令牌
- 页面加载时候自动检测令牌状态
- 有有效令牌直接登录
- 访问令牌过期但是刷新令牌有效,自动刷新令牌后登录
- 只有当刷新令牌也失效的时候,才需要用户重新输入账号密码
8.2.具体实现流程
首次登录

后续登录

令牌自动刷新
为避免令牌过期时用户正在操作,需要提前自动刷新:
- 计算Access Token的过期时间
- 设置定时器,在过期前5-10分钟触发刷新
- 使用当前有效的Refresh Token获取新的Access Token
- 更新本地存储的Access Token和过期时间
- 重复设置下一次刷新定时器
登出操作

9.大文件的分片上传
项目管理系统中实现文件的上传结合ant
- 上传的文件分割为相同大小的数据块
- 每一个分片生成唯一标识
- 按照一定规则各个数据快进行上传
- 发送完成后后端进行文件合并处理
10.v-if与v-show的区别
v-show控制元素的display值来进行隐藏切换,v-if通过销毁Dom元素节点实现元素的添加与隐藏
v-show在切换时不会触发生命周期,v-if会触发
频繁切换消耗资源使用v-show比如tab切换,v-if权限按钮控制。
11.v-for中key的作用
key:Dom元素的唯一标识
优化渲染性能:列表数据发生变化时,唯一的key避免重新渲染整个列表,变化部分进行更新。
维护元素状态:对于包含表单元素(如输入框),key 能确保元素的状态(如输入内容)不被意外重置。
避免复用问题:Vue 默认会复用 DOM 元素以提高性能,在条件渲染切换时,key 可以强制 Vue 创建新的元素而非复用。
12.created(不可)和mounted(可操作Dom)
| 维度 | created |
mounted |
|---|---|---|
| 执行时机 | 组件实例创建后(未挂载 DOM) | 组件挂载到 DOM 后 |
| DOM 访问 | 不可以 | 可以 |
| 主要用途 | 数据初始化、异步请求 | DOM 操作、第三方库初始化 |
created中确保请求完成数据后操作Dom
核心思路是等待数据请求的 Promise 完成后,再执行依赖该数据的 DOM 操作。
12.1.在created中请求数据,在回调里处理 DOM
由于created中可以发起请求,但此时 DOM 未就绪,因此需在请求成功的回调中,使用this.$nextTick()确保 DOM 更新后再操作
created() {
// 1. 在created中发起数据请求
this.fetchData().then(data => {
// 2. 数据请求成功后,更新组件数据
this.list = data;
// 3. 使用$nextTick确保DOM已渲染
this.$nextTick(() => {
// 此时可安全执行DOM操作(依赖this.list渲染的DOM已生成)
this.initDOMOperation();
});
});
},
methods: {
fetchData() {
// 返回Promise便于链式调用
return axios.get('/api/data').then(res => res.data);
},
initDOMOperation() {
// 示例:操作渲染后的DOM
const items = document.querySelectorAll('.list-item');
console.log('列表项数量:', items.length);
}
}
12.2.在mounted中结合async/await
mounted中 DOM 已基本就绪,但数据请求仍可能未完成,因此可使用async/await等待请求完成后再操作 DOM
async mounted() {
// 1. 等待数据请求完成
const data = await this.fetchData();
// 2. 更新数据(触发DOM重新渲染)
this.list = data;
// 3. 若DOM更新需要时间,仍需$nextTick确保渲染完成
await this.$nextTick();
// 4. 执行DOM操作
this.initDOMOperation();
},
methods: {
fetchData() {
return axios.get('/api/data').then(res => res.data);
},
initDOMOperation() {
// DOM操作逻辑
}
}
13.组件间的传值
父传子:父(引用信息$refs对象上),子(props)
子传父:$emit,子组件绑定自定义事件,触发执行后传父组件,父组件事件监听接收参数
兄弟传值:eventBus,new一个新的实例,用on和emit来对数据进行传输
14..keep-alive的使用
keep-alive 作用:用 <keep-alive> 包裹组件后,组件切换时不会被销毁,而是被缓存起来,再次进入时直接复用缓存的实例,状态保持不变。具体使用项目管理系统中查看器页签的切换。
14.1.缓存动态组件
<!-- 缓存 component 组件 -->
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
14.2.缓存路由组件
切换路由时保留组件状态
<!-- App.vue 中 -->
<keep-alive>
<router-view></router-view> <!-- 所有路由组件都会被缓存 -->
</keep-alive>
15.axios的封装
15.1.封装流程
创建实例基础配置:设置了基础 URL、超时时间和默认请求头
请求拦截:自动添加token到请求头,处理请求发送前的逻辑)
响应拦截:统一处理数据以及后端返回的状态码)
错误处理:针对不同错误类型(网络错误、401、403 404等)进行处理
方法封装:提供了 get、post、put、delete 等常用方法
15.2.常见状态码
| 状态码 | 错误类型 | 责任方 | 常见应对方式 |
|---|---|---|---|
| 401 | 客户端未验证 | 客户端 | 检查是否需要登录,补充身份凭证 |
| 403 | 客户端权限不足 | 客户端 / 服务器 | 确认账号权限,联系管理员解除限制 |
| 404 | 资源不存在 | 客户端 / 服务器 | 检查网址拼写,确认资源是否已迁移或删除 |
| 500 | 服务器故障 | 服务器 | 刷新页面重试,稍后再访问,或联系网站维护方 |
15.3.axios封装
// 引入axios
import axios from 'axios'
// 引入可能需要的工具或配置
import { getToken, removeToken } from './auth' // 假设的token处理工具
import { ElMessage } from 'element-plus' // 假设使用element-plus的消息提示
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量获取基础URL
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
// 例如:添加token
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
// 处理请求错误
console.error('请求拦截器错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data
// 根据后端约定的状态码处理
if (res.code !== 200) {
// 错误提示
ElMessage.error(res.message || '操作失败')
// 特殊错误处理,例如token过期
if (res.code === 401) {
removeToken()
// 可以在这里跳转到登录页
window.location.href = '/login'
}
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
(error) => {
// 处理响应错误
console.error('响应拦截器错误:', error)
// 网络错误处理
if (!error.response) {
ElMessage.error('网络连接异常,请检查网络设置')
return Promise.reject(error)
}
// 根据HTTP状态码处理
const status = error.response.status
switch (status) {
case 401:
ElMessage.error('身份验证失败,请重新登录')
removeToken()
window.location.href = '/login'
break
case 403:
ElMessage.error('没有权限执行该操作')
break
case 404:
ElMessage.error('请求的资源不存在')
break
case 500:
ElMessage.error('服务器内部错误')
break
default:
ElMessage.error(`请求错误: ${status}`)
}
return Promise.reject(error)
}
)
// 封装请求方法
const request = {
/**
* GET请求
* @param {string} url 请求地址
* @param {object} params 请求参数
* @param {object} config 额外配置
* @returns {Promise}
*/
get(url, params = {}, config = {}) {
return service.get(url, { params, ...config })
},
/**
* POST请求
* @param {string} url 请求地址
* @param {object} data 请求数据
* @param {object} config 额外配置
* @returns {Promise}
*/
post(url, data = {}, config = {}) {
return service.post(url, data, config)
},
/**
* PUT请求
* @param {string} url 请求地址
* @param {object} data 请求数据
* @param {object} config 额外配置
* @returns {Promise}
*/
put(url, data = {}, config = {}) {
return service.put(url, data, config)
},
/**
* DELETE请求
* @param {string} url 请求地址
* @param {object} params 请求参数
* @param {object} config 额外配置
* @returns {Promise}
*/
delete(url, params = {}, config = {}) {
return service.delete(url, { params, ...config })
},
/**
* 上传文件
* @param {string} url 请求地址
* @param {object} data 包含文件的表单数据
* @param {function} onUploadProgress 上传进度回调
* @returns {Promise}
*/
upload(url, data, onUploadProgress) {
return service.post(url, data, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress
})
}
}
export default request
15.4.接口封装
// 引入封装好的request
import request from './request'
// 调用GET请求
async function getUserInfo(id) {
try {
const res = await request.get('/user/info', { id })
console.log('用户信息:', res.data)
return res.data
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
// 调用POST请求
async function login(username, password) {
try {
const res = await request.post('/auth/login', { username, password })
console.log('登录成功:', res.data)
return res.data
} catch (error) {
console.error('登录失败:', error)
}
}更多推荐


所有评论(0)