- 性能优化都可以从哪里入手:
Webpack构建流程优化:
1、loader配置exclude
2、使用DLLPlugin来动态连接第三方库
3、使用Happypack将loader转换由单线程转为多线程
构建结果体积优化:
1、删除冗余代码(tree-shaking)
2、按需加载
3、Gzip压缩(在一个文本文件中找到一些重复出现的字符串临时替换它们,从而使整个文件变小,代码重复率越高,压缩效率越高):在请求头加入一句:accept-encoding:gzip
时间角度优化:
在时间角度优化主要是减少耗时。
1、网络请求优化:
(1)请求链路:DNS缓存、CDN缓存、HTTP缓存、精灵图、Base64
(2)数据大小:对请求资源进行合理的拆分与压缩(使用tree-shaking),代码分割
2、首屏加载优化
(1)尽快展示页面:路由、图片懒加载、使用骨架屏进行预渲染、使用服务端渲染(SSR)
(2)将用户可操作的时间尽量提前:将资源与数据进行离线缓存,用于下一次的快速渲染、通过预览图片的方式将页面内容提供给用户(比如视频封面)
3、渲染流程优化
(1)减少用户的操作等待时间:使用资源预加载、减少/合并DOM操作、使用GPU加载
4、计算逻辑运行提速:
(1)JS结合异步任务的管理,避免长时间页面卡顿
(2)使用更优的代码逻辑
(3)将计算结果进行缓存,减少运算次数
空间角度:
(1)合理使用缓存,进行进行缓存清理
(2)避免存在内存泄露
CSS与JS加载顺序优化
1、CSS的阻塞:link引用css时,在页面载入同时加载,@import引入的css将在页面加载完毕后被加载。css放在head标签里或启用CDN资源加速下载。
2、JS的阻塞:使用async与defer属性
3、避免回流重绘:减少DOM操作,节流防抖,文档碎片
- 项目性能优化:
为项目的开发环境和生产环境配置不同的入口文件:在vue.config.js中配置chainWebpack,config.when为两个环境指定两个不同的入口文件
路由懒加载:将路由相关的组件不再直接导入而是改成异步组件的写法,只有函数被调用时才去加载对应的组件,可以指定webpackChunkName来规定哪些组件打包为一个js文件,如果没有指定则各自打包为一个js文件
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
34
35
36
37//传统写法:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login/index.vue'
import Home from '@/views/home/home.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router
//路由懒加载
import Vue from 'vue'
import VueRouter from 'vue-router'
//const Login = ()=> {
// return import('@/views/login/index.vue')
//}
//const Home = ()=> {
// return import('@/views/home/home.vue')
//}
//有return且函数体只有一行,所以省略后为
const Login = ()=> import('@/views/login/index.vue')
const Home = ()=> import('@/views/home/home.vue')
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router通过webpack的externals加载外部CDN资源:CDN是内容分发网络,网络请求所需要的时间会受到客户端与服务器距离的影响,CDN就是将资源部署在全国各地,类似于缓存服务器,当请求资源时,会向离客户端最近的CDN服务器请求资源,这样就大大减少了请求所需要的时间,同时vue在打包时会将项目中import的资源都打包进一个包,在vue.config.js中配置externals中声明的资源不会被打包,也会减少包的体积。向CDN请求的资源会在第一次获取到之后缓存在本地,刷新页面会从本地直接读取这些资源,因此第二次打开网页用时更短。
开启gzip资源压缩:使用express的compression实例对文本内容进行压缩。 compression 是 一个express的中间件,主要用于进行gzip 压缩。通常用于web性能优化的,主要是能对你的文本内容进行压缩。一般用于html的文件。
- 项目性能优化指标:
1、LCP(Largest content Paint):用于记录视窗内最大(指的是元素的尺寸,不包括元素的margin/padding/border)的元素绘制时间,该时间会随着页面渲染变化而变化,代表网站初始载入速度,我们应该将改制控制在2.5s以内。
一般网页是分批加载的,因此最大的元素也是随时间变化的,浏览器在绘制第一帧时分发一个largest-contentful-paint类型PerformanceEntry对象,随着时间的渲染,当有更大的元素渲染完成时就会有一个PerformanceEntry对象报告。
1 | //提供方法 |
影响因素:服务端响应时间,JS与CSS引起的渲染卡顿、资源加载时间、客户端渲染
2、FID(First input delay):首次输入延迟,代表了页面的交互指标,就是看用户交互事件触发到页面响应中间耗时多少。页面的FID应为100毫秒或者更短。
FID只关注不连续操作对应的输入事件,例如点击、轻触、按键等,一般只测量首次输入的延迟。
与上面一样通过performanceObserver对象监听first-input类型的条目,并获取条目的startTime与processingStart时间戳的差值作为结果
1 | new PerformanceObserver((entryList) => { |
影响因素:减少第三方代码的影响、减少JS的执行时间、最小化主线程工作、减少请求数量与请求文件大小
3、CLS(Cumulative Layout Shift):代表页面的稳定指标,它能衡量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数。每当一个可见元素从一个已渲染帧变更到另一个已渲染帧时,就是发生了布局偏移。CLS应保持在0.1或更少。
具体的计算方式,就是首先偏移前后的两个已渲染帧的总的叠加大小(只算可视区域,重合部分只算一次),占可视区域的百分比,称为影响分数。然后取不稳定元素在一帧中的最大偏移距离(水平或者垂直取最大)占可视区域(取水平/垂直)的比例,称为距离分数。距离分数与影响分数相乘就是偏移分数。
js测量CLS的原理就是创建一个PerformanceObserver对象来侦听意外偏移layout-shift条目。
1 | let clsValue = 0; |
影响因素:图片或视屏元素有大小属性或者给他们保留一个空间大小、不要在一个已存在的元素上面插入内容,除了相应用户输入、使用animation或transition而不是直接出发布局改变。
也可以使用一些官方库比如web-vitals来进行性能测量。
- 组件封装需要注意什么:
组件的封装主要就是用于在多个页面实现复用,所以我们需要对一些动态数据进行抽离,通过props父子组件传参的方式将不同种类的数据传递给子组件进行渲染。如果需要传递的是父组件的一个对象,最好是把需要传递的对象属性一个个进行传递,这样可以使组件的复用性更高。同时如果需要在子组件中对父组件传递过来的数据进行修改,不能直接修改而是应该通过emit方法通知父组件进行修改。
在填写信息的表单中,v-model绑定的数据层数最好是两层以内,不要出现a.b.c.xxx的情况,层数多的话可以将数据单独拆分出来,再提交表单的时候再进行拼接。
- echarts:
基于一个元素,使用echarts.init来初始化一个实例,然后配置options,使用setOption来使用指定的配置项和数据显示图表。
- 开发git的工作流程:
1、新建一个Git仓库,此时默认创建master分支,由于master是用于生产环境的所以必须保证master分支上代码的稳定性,所以要基于master分支创建一个develop分支
2、当需要进行开发时从develop分支中创建新的开发分支,然后编写代码,开发完成后合并到develop分支上
3、新功能合并到develop分支上后如果想要发布到生产环境中需要在develop分支上创建release分支进行测试,测试完成后将该分支分别合并到master分支和develop分支上
4、release分支合并到master分支后需要打上便签用于发布
5、如果在生产环境中出现bug此时需要基于master分支创建一个hotfix分支用于修改bug,bug改完之后在将hotfix分支分别合并到master分支和develop分支上
- 项目还有哪些可以优化的地方:
优化搜索:
由于项目中很多地方出现了搜索框,每一次搜索都需要点击,可以取消点击按钮,在输入之后自动发起请求进行搜索,为了避免短时间内修改搜索框内容多次发起请求,可以使用防抖函数进行优化。
1 | var submit = document.getElementById("input") |
优化token安全性:
在项目中只是单纯的将token存放在sessionStorage中,由于sessionStorage很容易拿到所以存在安全隐患。
- 客户端通过用户名密码登录服务器并获取Token
- 客户端生成时间戳timestamp,并将timestamp作为其中一个参数。
- 客户端将所有的参数,包括Token和timestamp按照自己的算法进行排序加密得到签名sign
- 将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边(http://api.com/users?token=asdsadasd×tamp=123&sign=123123123)
- 服务端写一个过滤器对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效
- 如何平衡token的安全性与用户体验:
使用Access /Refresh Token
Access Token:访问令牌,是客户端向资源服务器换取资源的凭证,使用频繁,且与用户数据相关联,安全性方面比较敏感,因此有效期设置得较短。
Refresh Token:刷新令牌,是客户端向认证服务器换取Access Token的凭证。使用频率低,不与用户数据相关联,过期时间可以设置的长一点,这样可以解决用户反复登录的问题。
1、用户提供身份信息利用客户端向认证服务器换取Access /Refresh Token
2、客户端携带Access Token访问资源服务器,资源服务器识别Access Token并返回资源
3、Access Token过期或失效,客户端再一次访问资源服务器,资源服务器返回无效token报错
4、客户端通过Refresh Token向认证服务器换取Access Token,认证服务器返回新的Access Token
- 使用路由守卫对路由进行优化:
当用户直接跳过登录页面进入网页时,通过在路由守卫中判断是否可以获取到用户的token,如果获取到则允许访问,否则则重定向到登录页。
小程序:
项目难点:
视频页实现视频进度的保存:通过一个数组,以每个视频的独有的id作为键,播放时间作为值,当播放一个视频暂停时将当前视频播放时间存进数组中,当重新点击时,通过视频id获取到播放时间,用视频上下文的API实现自动跳转播放。
实现在每日推荐页面听歌实现歌曲切换:用到了两个消息订阅发布,第一个是当切换歌曲时将切换类型,就是歌曲的下一曲或上一曲,通过消息发布给每日推荐页面,然后在每日推荐页面获取到该歌曲的id,然后再通过消息发布给歌曲播放页面。另一种做法可以是在歌曲播放页面直接发送请求获得每日推荐的数据,然后切换歌曲时在数据中查找对应的歌曲然后进行播放。
项目优化:
可以在视频页渲染的时候使用posterAPI让图片代替视频,只有当点击观看视频时才加载视频,这样就不用在视频页一开始就把所有视频加载好,加快页面渲染时间。
由于项目中的搜索框是在输入之后自动发送请求获取相对应的歌曲名,为了避免短时间内修改搜索框内容多次发起请求,可以使用防抖函数进行优化。
由于视频页的视频数据一次请求是有限的,当视频看完之后如果想要继续更新视频数据,由于后端接口设置是隔一段时间才会更新数据,所以不能实现立即下滑刷新的效果。
- 使用vue的element实现大文件的断点续传:
1、使用element自带的upload组件,关闭文件加载自动上传(auto-upload),然后自己封装网络请求
2、使用组件的@change监听文件加载,在响应函数参数中file可以得到关于文件的信息,可以进行文件校验(指定提交文件的格式)
3、定义一个文件解析函数fileparse,使用内置函数FileReader构造一个实例对象来解析转换用户存储在计算机上的文件为指定的数据格式,用一个变量result进行接收
用到的事件处理:FileReader.onload:该事件在读取操作完成时触发
用到的方法:
FileReader.readAsArrayBuffer():将文件对象转换为ArrayBuffer数据对象
FileReader.readAsDataURL():将文件对象转换为base64的数据对象
4、发送axios请求,将数据对象进行JSON序列化之后再进行传输,服务器接收之后重新转换为数据对象,然后再转换为buffer,最后再转换为文件数据
5、使用spark-md5插件根据buffer生成hash值作为文件名,这样可以避免重复提交相同文件
6、使用fs.writeFileSync将文件写进服务器路径中,给客户端返回响应信息
1 | //服务器 |
断点续传:
1、先把文件解析成buffer数据后,根据切片大小或者是切片数量进行分割
2、对切片命名,可以根据文件生成统一的hash值然后再加上一个序号对切片进行命名
3、在客户端将切片存放在一个数组中然后遍历数组发送上传请求,用一个变量记录上传切片的数量,一个变量记录上传状态,每上传成功一次变量加1,然后移除数组中已上传的切片。
4、可以设置一个暂停上传按钮,当用户点击暂停时更改上传状态停止上传,如果点击继续上传按钮则根据记录上传切片数量的变量继续发送未发送的文件。如果中途刷新,当再次上传同一文件时,当服务器文件夹中存放的切片已经存在则直接跳过,直到没有上传的切片。
5、后端接收到切片文件之后,以文件哈希命名在服务器存放位置生成一个文件夹存放文件切片,然后当所有切片上传完毕之后,按照序列号对切片进行拼接成原来的文件。
- 有在项目中封装过axios吗:
有,在项目中需要多次向后端接口发起http请求,如果不对axios进行封装,每一次都需要设置请求头、错误处理、请求路径,会让代码冗杂且浪费时间,所以在项目中对axios进行了封装。
在main.js文件中,安装引入axios,我将axios添加为vue实例方法,即用Vue.prototype将axios设置为所有组件共享的方法,然后在main.js中配置基本路径baseURL,以及因为在每次发送请求都需要携带登录成功后端返回的token,所以通过请求拦截器在每次请求之前在请求头添加上token。因为想要实现网络请求进度条的效果,我使用一个NProgress插件,然后分别在请求时开启进度条,在拦截请求器中结束进度条实现想要的效果。
- 前端怎么做权限管理:
接口权限:采用jwt形式验证,没有通过则跳转到登录页,通过则拿到token保存起来,通过axios请求拦截器在每次请求时在请求头加上token
路由权限:
1、把所有的页面都放在路由表中,只要在访问的时候判断一下角色权限即可。vue-router在构建路由的时候提供了元信息meta,将能访问到该路由的角色添加到roles中,添加路由守卫,在访问路由前将meta属性与用户角色进行对比,如果用户角色出现在路由中就是可以访问。
缺点:
需要加载所有的路由,如果路由很多对性能有影响;是在全局路由守卫进行配置每次路由跳转都要做权限判断;菜单信息写在前端,任何更改都需要重新编译。
1 | routes:[ |
2、初始化时先挂载不需要权限控制的路由,比如登录页,404错误页等,如果用户强制进行访问则直接进入404。在vuex中添加获取用户权限信息的方法以及根据权限信息生成可访问路由表的方法,最后在全局路由守卫里调用vue-router的addRoutes方法添加路由。
缺点:在全局路由守卫中配置,每次路由跳转都需要做判断,菜单信息写在前端,更改需要重新编译。
菜单权限:
1、菜单与路由分离,菜单由后端返回,前端定义路由信息。前端在路由配置时添加name字段,用于与后端返回的菜单信息进行比较。每次路由跳转时都要判断权限,即判断菜单的name与路由的name,如果根据路由name找不到对应的菜单就表示用户没有权限访问。
缺点:菜单与路由需要做一一对应;全局路由守卫,每次路由跳转都需要判断
2、菜单与路由都由后端返回。在前端定义路由组件,后端返回以下格式的路由组件,通过vue-router的addRoutes动态挂载路由。需要前后端配合度更高。
1 | [ |
按钮权限:
1、用v-if进行判断用户权限role与路由表中的metabtnPermissions
2、通过自定义指令进行按钮权限的判断
- 使用代理服务器解决跨域问题:
1、通过vue-cli搭建的项目可以通过webpack起一个本地服务器作为请求的代理对象,通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不再一起仍会跨域。
1 | //vue.config.js |
2、通过服务端实现代理请求转发
1 | //以express为例 |
3、配置nginx实现代理
- 怎么处理项目中的错误:
后端接口错误:使用axios的响应拦截器先进行一层拦截,如果如果出错则直接报错。
代码逻辑错误:
1、全局设置错误处理:
errorHandle:这个处理函数被调用时可获取错误信息与vue实例。
1 | Vue.config.errorHandler = function(err,vm,info){} |
2、生命周期函数errorCaptured:捕获来自后代组件的错误时被调用,此钩子会受到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串,此构子可以返回false来阻止该错误继续向上传。
聊天应用:
项目描述:一个基于Vue3 + Vite + TypeScript + Naive UI + Socket.io技术栈的实时通讯工具,有登录注册模块,聊天页,用户列表等功能模块。
1、使用简单的store模式进行状态管理,实现各组件之间通信。
2、注册模块使用邮箱进行注册,通过[Nodemailer]实现自动发送邮件验证码进行验证。
- 说说Nodemailer:
Nodemailer是一个npm包,它可以帮助我们快速实现发送邮件的功能,首先通过Nodemailer的createTransportAPI创建一个发送邮件的请求对象,由于在项目中我使用的是自己的QQ邮箱,然后开启POP3/SMTP服务,然后在配置中填入邮箱地址与授权码,以及自定义邮件的内容,在项目中使用的是六位随机验证码,最后使用请求对象的sendMail方法进行发送即可。在生成六位验证码的同时需要将验证码保存下来,以便后面对验证码正确性进行验证。
- 怎么进行状态管理的:
因为应用不是大型应用,使用vuex会比较繁琐冗余,所以使用了一个简单的store模式进行状态管理,将需要多个实例共享的状态用一个响应式对象保存在一个store文件中。这样就可以在应用的任何部分随时更改数据,为了让每一次更改数据都有记录,可以在store文件中定义更改数据的函数,每次调用更改函数对数据进行更改。
- 说说websocket:
在websocket出现之前开发实时web应用的方式是轮询,需要不断向服务器发送http请求询问是否有新数据,当轮询的频率较高时就可以近似实现实时通信的效果。
websocket是一种网络传输协议,位于应用层,可在单个tcp连接上进行全双工通信,客户端与服务器只需要完成一次握手就可以创建持久性连接,并进行双向数据传输。
主要的应用场景有:弹幕、媒体聊天、协同编辑、基于位置的应用、体育实况更新等。
项目中使用到的socket.io是一个websocket库,包括客户端的js以及服务端的node.js,通过在服务器端初始化服务器并将websocket.io绑定到上面然后监听事件将消息发送给客户端。在客户端则是初始化websocket并和服务器建立连接,发送消息,主要使用到的是on函数与emit函数。
socket.emit函数提交一个事件,事件名称可以自定义,紧接着的参数是向该socket发送的内容。
socket.on接收一个事件,紧接着的参数是收到事件调用的回调函数,其中的data是收到的数据。
内置的默认事件名称有:io.on函数接收的connection作为客户端发起连接的实践,当连接成功后,会调用带有socket参数的回调函数;disconnect表示客户端连接断开,message表示接收到消息。
常用的API:
1、向所有客户端发送消息:
1 | io.emit("hi","你好") |
2、进入一个房间向房间里的成员发送消息(包括自己):socket.join,io.to,socket.leave
1 | io.on('connection', function(socket){ |
3、向一个房间广播信息(自己收不到信息):socket.broadcast.to(“some room”).emit(“message”)
1 | io.on('connection', function(socket){ |
- 说说怎么引入富文本编辑器以及相关配置:
- 说说前端埋点:
前端埋点是一种前端监控手段,可以监控用户在应用表现层的行为,可以得到包含页面点击量、用户访问量等重要数据。
1、代码埋点
(1)使用第三方埋点:在APP或者页面初始化的时候初始化第三方数据分析服务商的SDK,然后在某个事件发生时就调用SDK里面相应的数据发送接口发送数据。
(2)基于事件点击埋点
(3)页面访问埋点
(4)css埋点
…
优点:可控性强,灵活性高,可定制各种特殊埋点请求,检测数据准确
缺点:侵入性强,需要开发手动在相应位置进行埋点,增加维护成本
使用场景:使用于埋点量少,定制化程度高的需求
2、可视化埋点
指用户通过设备连接用户行为分析工具的数据接入管理界面,对可交互且交互后有效果的页面元素(如:图片、按钮、链接等),直接在界面上进行操作实现数据埋点,下发采集代码生效回数的埋点方式。这种方式所见即所得,跳过代码部署、测试验证和发版过程,极大提升生产力。
优点:通过集成SDK,运营可自主选择,操作方便满足发多数场景
缺点:通常需要引入第三方,控件有限,技术上推广与实现有难度
适用场景:埋点量多,需要对数据深度整合分析
3、无埋点
与可视化埋点又类似,二者的区别就是可视化埋点先通过界面配置哪些控件的操作数据需要收集;“无埋点”则是先尽可能收集所有的控件的操作数据,然后再通过界面配置哪些数据需要在系统里面进行分析。
优点:数据全民,不需要关注埋点逻辑,前端开发量轻
缺点:流量与采集的数据过于庞大,存在浪费,服务器性能压力大、难以特殊化定制
适用场景:网站需要全埋点监控
- 说说token在项目中的存储:
在登录的时候将后端返回的token保存到localstorage中,同时调用vuex的commit方法将token存进state中,在封装axios的时候从state中取出token在请求拦截器中为请求携带上token。之所以localStorage中的token也要用vuex维护的原因如下:
1、vuex是状态管理,全局的变量放在state中方便查找
2、localStorage的API不友好,每次使用都需要JSON.parse与JSON.stringify进行转化
3、localStorage的数据读取性能极差,远低于vuex的读取性能,因为localStorage的数据都是存储在磁盘文件中,每次读取都相当于一次磁盘读取,像token这样每次请求都需要携带的数据如果每次都从localStorage读取,太消耗性能且太慢了,但是vuex读取就特别快。
- 前端高并发处理方案:
图片方面:
1、通过精灵图(或称为雪碧图、雪花图)将多张小图合并为一张图片,达到减少HTTP请求的一种解决方案。可以通过css的background属性访问图片内容,这种方案同时还可以减少图片总字节数,节省命名词汇量。
2、压缩图片。图片占用资源极大,因此尽量避免使用多余的图片,使用时选择最合适的格式大小,然后使用智图压缩,同时在代码中用Srcset来按需显示。切记不要过分压缩,可能导致图片模糊。
3、图片使用base64编码。减少页面请求数采用base64的编码方式将图片直接嵌入到网页中。但是有一个弊端,base64解码也是需要消耗时间的,所以一般适用于较小图片。
文件方面:
1、合并脚本和样式表。将部分js与css模块合并,多个合并为单个,减少请求次数。
代码压缩:
1、js代码压缩。一般是除去多余的空格与回车,替换长变量名,简化一些代码写法等。
2、css代码压缩。原理与JS压缩原理类似,同样是去除空白符,注释并且优化一些css语义规则。
过滤请求:
1、减少访问API或者是不访问。使用防抖节流
2、利用缓存存放数据。将一些实时性修改但是不必须发送给后端存储的数据放在缓存中,例如修改图像但是还没点确定修改时。
3、避免高频刷新页面获取数据。做一个限定避免高频刷新带给服务器的压力。例如可以几秒内刷新页面中,只后去一次页面样式或者列表等数据。
- 说说节流阀:
节流阀就是在事件多次触发的时候可以通过设置一个true和false来控制,降低事件的执行频率。使用场景比如在下拉刷新或上拉触底加载的时候,会一直发起请求,这样会增加服务器的压力。
使用:在data中定义一个isLoading:false默认关闭状态,在发起请求时把节流阀打开,在请求完的时候节流阀关闭。
1 | //上拉触底获取新的下一页数据 |
- textarea 如何实现高度自适应(不出现滚动条):
1、使用div模拟textarea文本
通过属性设置contenteditable来实现div可编辑。
1 |
|
2、通过js监听文本域输入变化后通过scrollHeight计算出高度,再通过JS修改textarea高度为scrollHeight即可,可以加入过渡动画优化过渡效果
- 多个异步请求同步返回后再执行下一步的解决方案:
1、axios.all与axios.spread搭配实现:
1 | // 获取菜单树(axios请求函数是进行封装了的) |
2、使用Promise.all()实现
3、定义一个全局计数器,当每个请求成功时使计数器自增,当计数器等于所有请求数量时,即表示所有请求成功完成,可以进入下一步。
- 了解分页拉取数据重复的原因跟解决思路吗:
问题:电商系统中,用户查询已收货包裹数据时,向上滑动,新加载的第二页数据与第一页数据有重复内容。
原因:
- 已收货包裹默认按照数据表中主键进行降序排序。
- 第一页的第一条数据为当前该商户已收货包裹,id最大的记录。
- 前端拉取到第一页数据后,该商户此时有新收货记录变动,且该条记录的id大于第一页第一条数据对应的id。
- 拉取第二页数据时,之前第一页的最后一条记录,被排序到第二页的第一条,出现数据重复。
类似其他数据集也会出现这样的问题,共性条件如下:
- 数据集根据某一个或几个字段进行排序。
- 根据排序条件无法确定一个稳定的数据集,新插入的数据,按照排序字段进行排序,可能插在整个数据集的头部或者中间。
- 查询某一页数据时,如果该页之前的数据集存在新插入的数据,该页一定会查询到重复的数据。
解决方案:
1、查询完成的数据集:不进行分页,一次拉取所有的数据,原始数据集无重复,就不会拉取到重复的数据。
缺点:数据规模大的时候查询慢,渲染慢,适用于数据量小的情况
2、固定数据集范围:
- 通过增加数据开始条件、结束条件,取一段固定的数据集。
- 或者调整排序条件,固定一侧数据不会变更。
缺点:使用场景有限,必须能够固定住数据集两侧,或者至少固定住数据集开头。适用于能够固定住数据集的场景。
3、固定某一页的数据集:
- 第一页是固定的,前端保存最后一条数据对应的排序条件的值
- 查询下一页数据时,将当前页的最后一条记录对应的排序条件的值作为过滤参数
缺点:只适用于单条件排序,且排序条件具有唯一性;只适用于滑动分页,不支持页码分页,不根据页码定位数据。
4、前端过滤重复数据集:
- 每拉取一页数据,前端将所有的唯一id缓存
- 对比当前页给出的数据,如果存在则不渲染
- 判断当前页过滤掉的数据,对比阙值,如果超过了阙值,再拉取一页避免用户感知到
缺点:前端需要缓存已拉取到的所有数据,用于判断去重 ,占用内存;缓存数据较多,去重判断次数也多,渲染效率低;未下拉刷新之前,数据集中新插入的数据无法显示。适用于滑动分页,数据量较小,能够容忍未下拉刷新前数据集不展示新插入的数据。

- 为什么webpack的文件名要加hash值:
一般去部署前端资源文件时,会启用服务器的静态资源缓存,对于用户的浏览器而言就可以缓存到我们的静态资源,后续就不需要在请求服务器得到这些静态资源文件,整体应用的相应速度就有大幅度的提升。
不过,开启静态资源的缓存,也有问题,如果说在缓存策略当中,失效时间设置过短的话,效果不是特别明显,如果设置过长,一旦说应用发生了更新,重新部署过后,无法及时更新到客户客户端
所以在生产模式下,文件名使用 Hash 值,一旦资源文件发生改变,文件名称也可以跟着一起改变,对于客户端而言,全新的文件名就是全新的请求,那也就没有缓存的问题。
- git的fetch与pull有什么区别:
git的fetch命令是将远程分支的最新内容拉取到本地,但是fetch之后是看不到变化的,它会在本地新增一个FETCH_HEAD的分支,可以通过切换到master分支之后通过merge去合并这个分支,如果出现冲突则解决冲突。
git pull的作用相当于fetch与merge,自动合并,如果出现冲突的话也是需要手动解决。
- 说说对前端工程化的理解:
前端工程化主要就是从以下几点思考,分别是模块化、组件化、规范化、自动化四个方面。
1、模块化
(1)JS模块化
比如用webpack+babel将所有模块打包成一个模块同步加载或者搭乘多个chunk异步加载;还有ES6的esModule进行js的模块化。
(2)CSS模块化
虽然SASS、less等预处理器实现了css的文件拆分但是没有解决CSS模块化选择器的全局污染问题,为了防止命名冲突,我们可以使用一些命名风格来避免,比如BEM风格,Bootsrap风格等。也可以通过一些工具来解决。
(3)资源的模块化
2、组件化
模块化只是在文件层面上对代码或者是资源的拆分,组价化则是设计层面上,对用户界面的划分。就是将一个大型页面拆成若干个中型组件,然后中型组件还可以再拆分成若干个小型组件,知道拆到DOM元素位置。
3、规范化
项目初期规范的制定的好坏会直接影响到后期的开发质量,比如目录结构的指定,采用一套良好的代码规范,前后端接口的规范、git分支管理、文档规范等等。
4、自动化
比如图标合并、持续集成、自动化构建、自动化部署、自动化测试等等