• 尚品汇电商项目总结


    尚品汇项目总结

    项目中提到的知识点需要熟悉,非常大的概率会被问到

    做了什么事情

    vuex的模块化开发

    为什么使用vuex的模块开发
    根据不同的业务,将该业务state、mutations、action、getters 的封装在一个js文件中,让多种数据分类更加明确,代码更好维护。 --> 将store大仓库分为一个一个业务(功能) 的小仓库

    vuex模块化开发的使用
    1.在store大仓库的modules配置下挂载小仓库,小仓库都开启了命名空间

    export default new Vuex.Store({
        modules:{
            home,
            search
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.获取数据的时候写清楚来自哪个小仓库

    对axios进行二次封装

    封装的目的

    • 请求拦截器:设置发送请求前的统一操作
    • 响应拦截器:请求响应后进行统一操作
    • 对不同的需求(请求前缀)创建不同的axios请求实例
      • api请求(从后台获取数据),’/api‘开头的前缀
      • mock请求(mockjs模拟的数据),/mock开头的前缀

    进行了哪些封装?
    请求拦截器

    • 请求头中添加token给服务器
      响应拦截器
    • 请求成功直接获取res.data,请求失败终止promise链

    使用mockjs模拟数据

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    分页器 封装通用组件

    分页器需要哪些数据?
    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 };
        },
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    登录与注册 token-路由守卫

    可以提到JWT(JSON Web Token) 对比 session认证机制

    JSON Web Token:https://blog.csdn.net/qq_41370833/article/details/125052440

    路由守卫 - 控制访问权限

    1.在每次路由跳转时,根据token和用户信息判断用户是否登录,从而判断该路由是否可以跳转。
    用户信息是因为页面需要使用,token来判断用户是否登录。如果token失效了,需要重新登录,也就是可以执行退出登录的流程。

    2.没有登录时,有些路由不能进入(通过全局路由守卫实现),登录之后应该跳转到想要访问的路由。所以可以先把登录的路由作为query参数,点击登录的时候根据url有无query参数再决定往哪里跳转(在登陆组件中点击登录事件触发的回调中实现)。

    路由守卫笔记:https://blog.csdn.net/qq_41370833/article/details/124300162

    性能优化

    事件委托

    将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上。
    好处
    ①减少内存占用(事件监听的回调变少)
    ②动态添加的内部元素也能响应

    节流和防抖 --遇见的问题也可以回答:卡顿现象

    问题描述
    给一级菜单绑定了鼠标的事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。

    问题解决

    • 节流(throttle):控制事件执行的时间间隔,在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
      适合多次事件按时间做平均分配触发:窗口调整(resize)+ 页面滚动(scroll)等
    • 防抖(debounce):在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
      适合多次事件一次响应的情况:输入框实时搜索联想(keyup/input)

    节流函数实现

    节流(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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    防抖函数实现

    防抖(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)
    				
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    导航条 只发送一次请求

    当组件之间进行切换时,会销毁旧组件,创建新组件。所以组件中的子组件导航也会重新创建实例、重新挂载,重新发送数据请求。

    如何优化
    对于导航组件来说,一般都是不变的,所以我们希望只发送一次数据请求。所以可以把数据请求放在根组件,根组件只会实例化一次

    图片懒加载和路由懒加载

    图片懒加载:https://blog.csdn.net/qq_41370833/article/details/125284975 重点
    路由懒加载:https://blog.csdn.net/qq_41370833/article/details/125299151

    遇见的问题

    1.编程式路由跳转的NavigationDuplicated警告报错 --重写push和replace方法

    • 声明式导航router-link(需要有to属性),可以进行路由的跳转
    • 编程式导航利用组件实例的$router.push/replace,可以进行路由跳转

    问题描述
    编程式路由重复点击(参数不变),多次执行会抛出NavigationDuplicated警告报错

    问题分析,为什么会报错?
    Vue router3.1之后,$router.push()返回Promise,返回的promise没有设置失败的回调,没有对错误进行处理

    解决办法
    1.对每个router.push()进行错误捕获

    router.push('xxxx').catch(err => {err})
    
    • 1

    push方法还可以传入成功和失败的回调

    this.$router.push({
    		name:'search',//路由记得命名
    		params:{keyword:this.keyword},
    		query:{keyword:this.keyword.toUpperCase()}
    },()=>{},(err)=>{if(如果是NavigationDuplicated错误)console.log(err)})
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.利用swiper插件实现轮播图时,不生效 --引出$nextTick原理的知识点

    原因
    在new Swiper实例之前,页面中的结构必须存在
    刚开始将数据请求放在mounted里,但是ajax请求是异步的,数据是动态获取的,new Swiper时可能数据还没有获取到,页面还没有根据数据重新渲染,结构还不完整。

     mounted() {
          //派发action,通过vuex发起aja请求
          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"
            }
          })
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    解决办法
    watch: 数据监听,监听已有数据变化。 此时只能保证数据已经获取到了,不能保证v-fordom渲染完毕了
    $nextTick:将回调延迟到下次DOM更新周期之后执行,下一次DOM更新周期之后指下次微任务执行更新DOM操作之后,此时保证dom渲染完毕

    更新DOM的回调vm.$nextTick注册的回调,都是添加到微队列中。所以DOM会先更新完毕,然后再执行$nextTick的回调

    $nextTick原理:https://blog.csdn.net/qq_41370833/article/details/124830714

    滚动条保持原有位置

    问题描述
    当从页面跳转到新路由时,滚动条保持原有位置

    原因
    路由切换时没有重新刷新页面

    解决办法
    使用前端路由,当切换到新路由时,想要页面滚动到顶部或者保持原先的滚动位置,vue-router可以实现,只支持在history.pushState的浏览器使用。 —引出H5接口的新方法pushStatereplaceState,或者hash模式和history模式的区别

    //配置路由
    export default new VueRouter({
        routes,
        scrollBehavior (to, from, savedPosition) {
            return {x:0,y:0} //每次路由切换时的滚动条位置
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    从源码的角度回答“mybatis的#{} 和${}有什么区别”?
    等保测评三级等保—安全设计思路
    C语言|图解指针变量
    项目十二认识指针
    pytest测试框架使用基础07 fixture—parametrize获取参数的几种常用形式
    执行上下文和闭包
    深入了解 Java 中的时间信息定义、转换、比较和操作
    Kafka常见面试题
    以detectron2了解maskrcnn实现源码(1)--双线性插值
    .net 温故知新:【6】Linq是什么
  • 原文地址:https://blog.csdn.net/qq_41370833/article/details/125530176