一、首屏优化的方法
路由懒加载
如果是SPA(single page web application,单页面应用)优先保证首页加载。(不适用多页面应用)
路由懒加载指的是打包部署时将资源按照对应的页面进行划分,需要的时候加载对应的页面资源,而不是把所有的页面资源打包部署到一块。避免不必要资源加载。
服务端渲染SSR
传统的spa前后端分离,也就是CSR(Client-Side Rendering),渲染页面的过程十分复杂,浏览器输入url地址后,加载html,css,js代码后,还要向服务器请求数据,浏览器收到数据才可以
SSR指的是服务端渲染(Server-Side Rendering),是在服务器端渲染网页内容,并且将渲染后的HTML发送给浏览器,而不是在浏览器端渲染。渲染过程简单,如果是纯H5页面,SSR是性能优化的终极方案,但是会增大服务器压力。ssr其实是一门古老的前端技术,前后端分离后,项目越来越复杂,又发现ssr的好处
ssr和csr各有优缺点,不再述说
App预取
预取数据一般是标题,首页文本,不包括图片,视频
在列表页就开始预取详情页的数据,进入详情页后即可直接渲染,缺点是可能会造成流量浪费
分页
首屏内容尽量少,分页显示
图片懒加载
先加载内容,再加载图片,窗口滚到哪里图片就加载到哪里。另外,注意提前设置图片容器尺寸,避免重排
离线包hybrid
提前下载html、css、js等等文件,当在 App 内打开页面时,webview 使用 `file://` 协议加载本地的 html css js ,然后再 ajax 请求数据,再渲染
二、渲染 10w 条数据
可以针对hr的问题提出自己的反问、质疑。
要主动沟通,表达观点。
后端的问题要用后端的思维去解决
这是什么应用场景。然后判断这个技术方案是否合理,为什么不能分页
自定义中间层
- 自定义nodejs中间层,获取并拆分10w条数据
- 前端对接nodejs中间层,而不是服务器
- 成本比较高
虚拟列表
- 只渲染可视区域 DOM
- 其他隐藏区域不渲染,只用一个
<div>
撑开高度 - 监听容器滚动,随时创建和销毁 DOM
- 实现起来很复杂,借用第三方lib,如Vue-virtual-scroll-list等等。虚拟列表是一个无奈的选择,能不用就不用,因为实现复杂而效果不一定好(低配手机)
三、文字超出省略
单行文字
#box1 {
border: 1px solid #ccc;
width: 100px;
white-space: nowrap; /* 不换行 */
overflow: hidden;
text-overflow: ellipsis; /* 超出省略 */
}
多行文字
#box2 {
border: 1px solid #ccc;
width: 100px;
overflow: hidden;
display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
-webkit-box-orient: vertical; /* 设置子元素排列方式 */
-webkit-line-clamp: 3; /* 显示几行,超出的省略 */
}
四、前端常用的设计模式?什么场景?
设计原则
- 最重要的思想:开放封闭原则
- 对外扩展开放
- 对修改封闭
设计模式
- 工厂模式,用一个工厂函数,创建一个实例,封装创建的过程。
- 单例模式,提供全局唯一的对象,无论获取多少次
- 代理模式,使用者不能直接访问真实数据,而是通过一个代理层来访问
- 观察者模式,即常说的绑定事件,如addEventListenler
- 发布订阅模式,即常说的自定义事件
- 装饰器模式
五、Vue优化
v-if 和 v-show
一般情况下使用 v-if
即可,普通组件的销毁、渲染不会造成性能问题
如果组件创建时需要大量计算,或者大量渲染(如复杂的编辑器、表单、地图等),可以考虑 v-show
v-for 使用 key
key
可以优化内部的 diff 算法。注意,遍历数组时 key
不要使用 index
computed 缓存
computed
可以缓存计算结果,data
不变则缓存不失效。
keep-alive
<keep-alive>
可以缓存子组件,只创建一次。通过 activated
和 deactivated
生命周期监听是否显示状态
局部频繁切换的组件,如 tabs
不可乱用 <keep-alive>
,缓存太多会占用大量内存,而且出问题不好 debug
异步组件
对于体积大的组件(如编辑器、表单、地图等)可以使用异步组件
拆包,需要时异步加载,不需要时不加载
减少 main 包的体积,页面首次加载更快
路由懒加载
对于一些补偿访问的路由,或者组件提交比较大的路由,可以使用路由懒加载
SSR
SSR 让网页访问速度更快,对 SEO 友好。
但 SSR 使用和调试成本高,不可乱用。
六、如果一个 h5 很慢,你该如何排查问题?
通过工具分析性能参数
PS:建议在 Chrome 隐身模式测试,避免其他缓存的干扰
Performance 可以检测到下述的性能指标,并且有网页快照截图
- First Paint (FP),从开始加载到浏览器首次绘制像素到屏幕上的时间,也就是页面在屏幕上首次发生视觉变化的时间。输入url到页面发生变化
- First Contentful Paint(FCP),浏览器首次绘制来自 DOM 的内容的时间,内容必须是文本、图片(包含背景图)、非白色的 canvas 或 SVG,也包括带有正在加载中的 Web 字体的文本。输入url到文字或图片开始被渲染
- NetWork 可以看到各个资源的加载时间
- First Meaningful Paint(FMP),页面的主要内容绘制到屏幕上的时间。这是一个更好的衡量用户感知加载体验的指标,但无法统一衡量,因为每个页面的主要内容都不太一致。出现有意义的渲染,弃置不用,没有技术手段判断,改用lcp
- DomContentLoaded(DCL),即
DOMContentLoaded
触发时间,DOM 全部解析并渲染完。页面dom渲染完成 - Largest Contentful Paint(LCP),可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。最重要,最大的内容被渲染出来
- Load(L),即
window.onload
触发时间,页面内容(包括图片)全部加载完成。
识别问题
网页慢,到底是加载慢,还是渲染慢?—— 分清楚很重要,因为前后端不同负责。
如果FP加载很慢,就是请求文件加载慢,如果是FP到L很慢,就是页面渲染慢
除了浏览器自带的performance工具,还有lighthouse工具,会生成一份详细明了的性能测试报告
解决问题
加载慢
- 优化服务端接口
- 使用 CDN
- 压缩文件
- 拆包,异步加载
渲染慢(可参考“首屏优化”)
- 根据业务功能,继续打点监控
- 如果是 SPA 异步加载资源,需要特别关注网络请求的时间
持续跟进
分析、解决、测试,都是在你本地进行,网站其他用户的情况你看不到。
所以要增加性能统计,看全局,不只看自己。
七、写一个函数,实现 Array flatten 扁平化,只减少一个嵌套层级
let arr=[1, 2, [3, 4, [100, 200], 5], 6];
let newArr=[];
arr.forEach(item=>{
if(Array.isArray(item)){
item.forEach(i=>{
newArr.push(i)
})
}else{
newArr.push(item)
}
})
console.log(newArr)//[1, 2, 3, 4, Array(2), 5, 6]
let arr=[1, 2, [3, 4, [100, 200], 5], 6];
let newArr=[];
arr.forEach(item=>{
newArr=newArr.concat(item);
})
console.log(newArr)//[1, 2, 3, 4, Array(2), 5, 6]
追问:彻底扁平化数组
let arr=[1, 2, [3, 4, [100, 200], 5], 6];
let newArr=[];
newArr=arr.toString()
newArr=newArr.split(",")
console.log(newArr)//['1', '2', '3', '4', '100', '200', '5', '6']
八、实现一个 `getType` 函数,传入一个变量,能准确的获取它的类型
function getType(data){
const originData=Object.prototype.toString.call(data);
const beforeIdex=originData.indexOf(" ");
const type=originData.slice(beforeIdex,-1).toLowerCase();
return type;
}
console.log(getType("1"))//string
九、手写new过程
- 创建一个空的简单
JavaScript
对象即{}
。 - 链接该对象(即设置该对象的构造函数)到另一个对象。
- 将步骤
1
新创建的对象作为this
的上下文context
。 - 如果该函数没有返回对象,则返回步骤
1
创建的对象。
function _new(base,...args){
var obj = {};
obj.__proto__ = base.prototype;
base.apply(obj, args);
return obj;
}
十、offsetHeight scrollHeight clientHeight 区别
offset是偏移量的意思(border-box)
offsetLeft:距离body或者有定位的父元素的左侧距离,会包含父元素的边框值
offsetTop:距离body或者有定位的父元素的上侧距离,会包含父元素的边框值
offsetHeight:包括border、padding、content的元素高度
offsetWidth: 包括border、padding、content的元素宽度
offsetParent:返回带有定位的父元素,没有则返回body
client是可视区的意思(content-box)
clientWidth:客户端宽度,width+paddding
clientHeight:客户端高度,height+padding
clientLeft:左边框大小,border
clientTop:上边框大小,border
e.clientX:鼠标距离浏览器窗口可视区X坐标
e.clientY:鼠标距离浏览器窗口可视区Y坐标
e.pageX:鼠标距离页面X坐标,滚动条
e.pageY:鼠标距离页面Y坐标,滚动条
e.screenX:鼠标距离电脑屏幕X坐标
e.screenY:鼠标距离电脑屏幕Y坐标
scroll是滚动的意思
scrollHeight:内容的高度,内容有可能超过盒子高度,content+padding
scrollWeight:内容的宽度,内容有可能超过盒子宽度,content+padding
scrollTop:被隐去的内容高度
十一、[10 , 20 , 30].map(parseInt)
结果:[10 , NaN , NaN]
parseInt('1', 0) // 1 ,radix === 0 按 10 进制处理
parseInt('2', 1) // NaN ,radix === 1 非法(不在 2-36 之内)
parseInt('3', 2) // NaN ,2 进制中没有 3
十二、文字超出省略,用哪个 CSS 样式
单行文字
```css
#box1 {
border: 1px solid #ccc;
width: 100px;
white-space: nowrap; /* 不换行 */
overflow: hidden;
text-overflow: ellipsis; /* 超出省略 */
}
```
多行文字
```css
#box2 {
border: 1px solid #ccc;
width: 100px;
overflow: hidden;
display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
-webkit-box-orient: vertical; /* 设置子元素排列方式 */
-webkit-line-clamp: 3; /* 显示几行,超出的省略 */
}
```
十三、如何统一监听 Vue 组件报错?
window.onerror
可以监听当前页面所有的 JS 报错,jQuery 时代经常用。<br>
注意,全局只绑定一次即可。不要放在多次渲染的组件中,这样容易绑定多次。
errorCaptured 生命周期
会监听所有下级组件的错误。可以返回 `false` 阻止向上传播,因为可能会有多个上级节点都监听错误。
errorHandler
全局的错误监听,所有组件的报错都会汇总到这里来
errorHandler 会阻止错误走向 window.onerror
组件内的异步错误 `errorHandler` 监听不到,还是需要 `window.onerror`
十四、定义一个函数,实现数组的旋转。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let arr1 = [0, 1, 2, 3, 4, 5, 6, 7]
let arr2 = [0, 1, 2, 3, 4, 5, 6, 7]
function fn1(arr,key){
if(key==arr.length){
console.log(arr)
return
}
for (let k in arr){
if(k <= key+1){
let n=arr.shift();
arr.push(n)
}
}
console.log(arr)
}
fn1(arr1,3)//[5, 6, 7, 0, 1, 2, 3, 4]
function fn2(arr,key){
if(key==arr.length){
console.log(arr)
return
}
let arr1=arr.slice(0,key+2)
let arr2=arr.slice(key+2,arr.length)
console.log(arr2.concat(arr1))
}
fn2(arr2,3)//[5, 6, 7, 0, 1, 2, 3, 4]
</script>
</body>
</html>
十五、二分法
function binarySearch(arr,target) {
const length = arr.length
if (length === 0) return -1
let startIndex = 0 // 开始位置
let endIndex = length - 1 // 结束位置
while (startIndex <= endIndex) {
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧查找
endIndex = midIndex - 1
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
startIndex = midIndex + 1
} else {
// 相等,返回
return midIndex
}
}
return -1
}
let a=binarySearch([0,1,3,4,5,7,9],5);
console.log(a)
function binarySearch2(arr, target, startIndex, endIndex) {
const length = arr.length
if (length === 0) return -1
// 开始和结束的范围
if (startIndex == null) startIndex = 0
if (endIndex == null) endIndex = length - 1
// 如果 start 和 end 相遇,则结束
if (startIndex > endIndex) return -1
// 中间位置
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧查找
return binarySearch2(arr, target, startIndex, midIndex - 1)
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
return binarySearch2(arr, target, midIndex + 1, endIndex)
} else {
// 相等,返回
return midIndex
}
}
十六、两数之和
let arr=[1, 2, 4, 7, 11, 15];
function fn(arr,sum){
for(let k1 in arr){
for(let k2 in arr){
if(arr[k1]+arr[k2]==sum){
console.log(`第一个数字为${arr[k1]},第一个数字为${arr[k2]}`)
return
}
}
}
}
fn(arr,15)
function findTowNumbers(arr, n){
const res = []
const length = arr.length
if (length === 0) return res
for (let i = 0; i < length - 1; i++) {
const n1 = arr[i]
let flag = false // 是否得到了结果
for (let j = i + 1; j < length; j++) {
const n2 = arr[j]
if (n1 + n2 === n) {
res.push(n1)
res.push(n2)
flag = true
break
}
}
if (flag) break
}
return res
}
十七、定义一个函数,将数组种所有的 `0` 都移动到末尾
let arr=[1, 0, 3, 0, 11, 0]
function fn(arr){
let res1=[]
let res2=[]
for(let k in arr){
if(arr[k]===0){
res1.push(arr[k])
}else{
res2.push(arr[k])
}
}
return res2.concat(res1)
}
console.log(fn(arr))//[1, 3, 11, 0, 0, 0]
十八、统计字符串中庸出现最多的字符
let str='aabbcccddeeee11223';
let obj={};
for(let i=0;i<str.length;i++){
if(str[i] in obj){
obj[str[i]]++;
}else{
obj[str[i]]=1;
}
}
let max=0;
let char="";
for(let k in obj){
if(obj[k]>max){
max=obj[k];
char=k;
}
}
console.log(max,char)//4 'e'