token
和refresh token
。token
是登录凭证,有效使其非常短。refresh token
是刷新凭证时间比token
长,当token
过期时请求新的token。保存在localstorage和vuex里面。退出登录的时候清除。beforeEach
做登录鉴权操作,利用token判断该路径是否可以跳转。页面展示主要实现的是性能优化
如何保证安全?加密+短时间刷新token
在传输中,不允许明文传输用户隐私数据
在本地,不允许明文保存用于隐私数据
在服务器,不允许明文保存用户隐私数据
如何防止token泄露?
类似数字签名机制,利用时间戳和token加密算法 = token签名?
token在前台表示已登陆,在后台表示有权限访问接口。
如果有更需要保护的可以将加入其他验证,如手机号、邮箱等。
token主动刷新
如果token过期,主动刷新token,利用与token相关联的refresh token,refresh token作用是获取新的token,过期时间比token的时间长。
将token过期的处理放在响应拦截器中,当返回的响应码为401时,说明token过期了,需要利用refresh token重新获取新的token。重新获取token之后,重发请求。
Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Token 时才会验证
实现介绍
登录时后端为了区分用户,会返回给前端token,token是用户的唯一标识符。
vuex存储数据不是持久化,刷新之后数据会消失
Authorization
字段(手动设置),将Token发送给服务器//请求拦截器
requestAxios.interceptors.request.use((config)=>{
if(store.state.detail.uuid_token) {
config.headers.userTempId = store.state.detail.uuid_token;
}
let token = localStorage.getItem('TOKEN');
if(token){
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
})
封装的目的
/mock
开头的前缀进行了哪些封装?
请求拦截器
res.data
,请求失败终止promise链mockjs 生成随机数据,当前端使用mock模拟的数据接口时,mockjs进行数据返回,并拦截ajax请求不发送给后台。
封装一个mock请求的axios
const mockRequests = axios.create({
baseURL:"/mock",
timeout:5000, //请求超时的时间5s
})
//请求拦截器
mockRequests.interceptors.request.use((config)=>{
//config:配置对象,对象里面有一个属性很重要,header请求头
nprogress.start();//进度条开始
return config;
})
//响应拦截器
//参数1成功的回调,参数2失败的回调
mockRequests.interceptors.response.use((res)=>{
nprogress.done();//进度条结束
return res.data;//返回服务器返回的数据
},(error)=>{
return Promise.reject(new Error('fail')) //终止promise链
})
export default mockRequests;
//采用mock发送请求
import mockRequests from "./request";
//获取Home首页轮播图banner的结构
export const getBannerList = () => mockRequests.get('/banner');
分页器需要哪些数据?
1.当前是第几页 pageNo
2.每一页需要展示多少数据 pagesize
3.分页器一共有多少条数据 total
4.分页器显示的连续页码个数:5 | 7
对于分页器,很重要的点是计算出连续显示页面号起始数字和结束数字。 当前页在连续页的正中间
Math.ceil(total/pagesize)
computed: {
//总共多少页
totalPage() {
return Math.ceil(this.total / this.pageSize);
},
//计算出连续的页码的起始数字与结束数字[连续页码的数字:至少是5]
startNumAndEndNum() {
const { continues, pageNo, totalPage } = this;
//先定义两个变量存储起始数字与结束数字
let start = 0,
end = 0;
//不正常现象【总页数没有连续页码多】
if (continues > totalPage) {
start = 1;
end = totalPage;
} else {
//正常现象【连续页码5,但是你的总页数一定是大于5的】
start = pageNo - parseInt(continues / 2);
end = pageNo + parseInt(continues / 2);
//把出现不正常的现象【start数字出现0|负数】纠正
if (start < 1) {
start = 1;
end = continues;
}
//把出现不正常的现象[end数字大于总页码]纠正
if (end > totalPage) {
end = totalPage;
start = totalPage - continues + 1;
}
}
return { start, end };
},
}
将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上。
好处
①减少内存占用(事件监听的回调变少)
②动态添加的内部元素也能响应
问题描述
给一级菜单绑定了鼠标的事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。
问题解决
节流(throttle)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的时间间隔
注意点
1.返回函数使用了闭包,闭包会永远在内存中保存所以这个pre都是记录的上一次的结果
2.修改this的目的是让函数的指向绑定事件的DOM
//使用形式,绑定时候throttle函数就会执行,所以this是window
window.addEventListener('scroll',throttle(()=>{},500))
//自定义
function throttle(callback,wait){
let pre=0;
//console.log(this);window
//节流函数/真正的事件回调函数
return function(...args){
const now = Date.now();
if(now-pre>wait){
//callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源,
//console.log('this2',this); //DOM
callback.apply(this,args);
pre = now;
}
}
}
防抖(debounce)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的规定时间
思路
1.返回一个函数,在函数中设置定时器,在定时器中执行回调函数,注意this指向的改变
2.当频繁点击的时候,如果此时已经开启定时器了说明之前触发了回调,我们需要删除定时器
3. 注意定时器的timeId不能在返回函数中定义,如果在返回函数中定义,那么每次触发回调的时候,都会重新定义。而我们的需求是对于当前触发的回调,timeId需要记录之前的结果,通过timeId来判断之前是不是已经开启了定时器。所以这里需要利用闭包实现。
//使用形式:绑定时debounce立即执行
window.addEventListener('scroll',debounce(()=>{},500));
//防抖函数
function debounce(callback, wait){
let timeId=null;
return funtion(...args){
if(timeId){//之前已经有一个定时器了,这里再一次触发事件,重新开始即使
clearTimeout(timer);
}
timeId = setTimeout(()=>{
callback.apply(this,args);
//执行成功之后,重置timeId,所以这里可以起作用
timeId = null;
},wait)
}
}
当组件之间进行切换时,会销毁旧组件,创建新组件。所以组件中的子组件导航也会重新创建实例、重新挂载,重新发送数据请求。
如何优化
对于导航组件来说,一般都是不变的,所以我们希望只发送一次数据请求。所以可以把数据请求放在根组件,根组件只会实例化一次。
图片懒加载:https://blog.csdn.net/qq_41370833/article/details/125284975 重点
路由懒加载:https://blog.csdn.net/qq_41370833/article/details/125299151
$router.push/replace
,可以进行路由跳转问题描述
编程式路由重复点击(参数不变),多次执行会抛出NavigationDuplicated警告报错
问题分析,为什么会报错?
Vue router3.1之后,$router.push()
返回Promise,返回的promise没有设置失败的回调,没有对错误进行处理
解决办法
1.对每个router.push()
进行错误捕获
router.push('xxxx').catch(err => {err})
push方法还可以传入成功和失败的回调
this.$router.push({
name:'search',//路由记得命名
params:{keyword:this.keyword},
query:{keyword:this.keyword.toUpperCase()}
},()=>{},(err)=>{if(如果是NavigationDuplicated错误)console.log(err)})
2.重写push()方法
①先保存VueRouter原型上的push方法
②重写push|repalce
import VueRouter from 'vue-router' //引入插件
Vue.use(VueRouter) //使用插件
let origiPush = VueRouter.prototype.push
VueRouter.prototype.push = function(location,onResolved=()=>{},onRejected=(err )=>err){
return origiPush.call(this,location,onResolved,onRejected)
}
原因
在new Swiper实例之前,页面中的结构必须存在
刚开始将数据请求放在mounted里,但是ajax请求是异步的,数据是动态获取的,new Swiper时可能数据还没有获取到,或者说页面还没有根据数据重新渲染,结构还不完整。
mounted() {
//派发action,通过vuex发起ajax请求
this.$store.dispatch('home/reqBannerList');
new Swiper(document.querySelector('.swiper-container'),{
loop:true,
pagination:{
el:".swiper-pagination"
},
navigator:{
nextEl:".swiper-button-next",
prevEl:".swiper-button-prev"
}
})
}
解决办法
watch: 数据监听,监听已有数据变化。 此时只能保证数据已经获取到了,不能保证v-for
dom渲染完毕了
$nextTick:将回调延迟到下次DOM更新之后执行
本质:将回调添加到任务队列中延迟执行
更新DOM的回调
和vm.$nextTick
注册的回调,都是添加到微队列中。所以DOM会先更新完毕,然后再执行$nextTick
的回调
$nextTick原理:https://blog.csdn.net/qq_41370833/article/details/124830714
问题描述
当从页面跳转到新路由时,滚动条保持原有位置
原因
路由切换时没有重新刷新页面
解决办法
使用前端路由,当切换到新路由时,想要页面滚动到顶部或者保持原先的滚动位置,vue-router可以实现,只支持在history.pushState
的浏览器使用。 —引出H5接口的新方法pushState
和replaceState
,或者hash模式和history模式的区别
//配置路由
export default new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
return {x:0,y:0} //每次路由切换时的滚动条位置
}
})