摘要:最近,看了下慕课2周刷完n道面试题,记录并添加部分可参考的文档,如下...
1. H5页面如何进行首屏优化?
路由懒加载
适用于SPA(不适用MPA)
本质就是路由拆分,有限保证首页加载
服务端渲染SSR
传统的前后端分离(SPA)渲染页面的过程复杂
SSR渲染页面过程简单,所以性能好
如果是纯H5页面,SSR是性能优化的终极方案
APP预取
如果H5在APP WebView中展示,可使用APP预取
用户访问列表页时,APP预加载页面首屏内容
用户进入H5页面,直接从APP中获取内容,瞬间展示首屏
分页
针对列表页
默认值展示第一页内容
上划加载更多
图片懒加载lazyLoad
针对详情页
默认只展示文本内容,然后触发图片懒加载
注意:提前设置图片尺寸,尽量只重绘不重排
Hybrid
提前将HTML JS CSS下载到APP内部
在APP webview中使用file://协议加载页面文件
再用Ajax获取内容并展示(也可结合APP预 取)
总结:服务端SSR是H5的终极优化方案(但成本也高)
移动端H5要结合APP能力去优化
严格来说,hybrid不是H5,但这里回答也没有问题
扩展:SSR
SSR(Server-Side Rendering,服务器端渲染)是一种将页面内容在服务器端生成并直接返回给客户端的技术。相比于传统的客户端渲染(CSR,Client-Side Rendering),SSR 在首次加载页面时可以直接呈现出完整的 HTML 内容,而不需要等待 JavaScript 文件下载和执行完成后再进行页面渲染,从而提高了首屏加载速度和搜索引擎优化(SEO)效果,适用于内容密集型页面。
SSR是一门"古老"的技术,刚刚兴起Web1.0时,就是SSR技术:PHP ASP JSP等
Nuxt.js(Vue) 安装 - NuxtJS | Nuxt.js 中文网Nuxt.js 十分简单易用。一个简单的项目只需将 `nuxt` 添加为依赖组件即可。https://www.nuxtjs.cn/guide/installation
Next.js(React) Next.jsNext.js 是一个轻量级的 React 服务端渲染应用框架。https://nextjs.frontendx.cn/
2. 后端一次性返回10w条数据,你该如何渲染?
后端返回10W条数据,本身技术方案设计不合理;
JS可以处理10w条数据,但是渲染的DO会非常卡顿;
自定义中间层
自定义node.js中间层,获取并拆分这10W条数据
前端对接node.js中间层(后端的问题优先考虑用后端的思维解决,中间件),而不是服务端
成本比较高
虚拟列表
只渲染可视区域DOM
其他区域不显示,只用<div>撑起高度
随着浏览器滚动,创建和销毁DOM
虚拟列表实现起来非常复杂(且效果不一定好,例如低配手机),可借用第三方lib,例如Vue-virtual-scroll-list、 React-virtualiszed
3. 前端常用的设计模式有哪些?并说明使用场景
设计原则
最重要的思想:开放封闭原则
对扩展开放
对修改封闭
工厂模式
用一个工厂那个函数,来创建实例,隐藏new
如jQuery $函数,React 的createElement函数
class Foo{}
// 工厂模式
function factory(a, b, c) {// if elsereturn new Foo()
}
const f = factory(1, 2, 3)
单例模式
全局唯一的实例(无法生成第二个)
如Vuex Redux的store ;如全局唯一的dialog modal
class SingleTon {private static instance: SingleTon | null = nullprivate constructor() {}public static getInstance():SingleTon { // 静态方法,属于类不属于实例if (this.instance = null) {this.instance = new SingleTon()}return this.instance}fn1() {}fn2() {} //实例方法
}const s = SingleTon.getInsance()
s.fn1()
const s1 = SingleTon.getInsance()
**s===s1 //true**
扩展:JS是单线程的,创建单例很简单;
Java是支持多线程的,创建单例要考虑锁死线程,否则多个线程同时创建,单例就重复了(多线程共享进程内存)
代理模式
使用者不能直接访问对象,而是访问一个代理层
在代理层可以监听get set 做很多事情,如ES6的Proxy 实现Vue3的响应式
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
ES6 入门教程https://es6.ruanyifeng.com/#docs/proxy
观察者模式(Observer Pattern)
当一个对象的改变需要同时改变其他对象,并且不知道有多少对象需要改变时,可以使用观察者模式。例如,在事件监听、数据订阅等场景下可以使用观察者模式
// 一个主题,一个观察者,主题变化后触发观察者执行
btn.addEventListener('click', () => {...})
发布订阅模式(Publish-Subscribe Pattern)
主要用于解耦组件之间的通信,实现松耦合的架构。在事件总线、消息队列、数据订阅等场景广泛使用,例如事件总线在非父子组件之间传递数据或触发特定操作
// 绑定
event.on('event-key', () => {//事件1
})
event.on('event-key', () => {//事件2
})
// 触发执行
event.emit('event-key')
装饰器模式
是一种结构型设计模式,它允许向一个对象动态地添加新的功能,同时又不改变其结构。
原功能不变,增加一些新功能(AOP面向切面编程),例如 Node.js 框架Nest.js
ES和TypeScript的Decorator语法
@testable就是一个类装饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身。
@testable
class MyTestableClass {// ...
}function testable(target) {target.isTestable = true;
}MyTestableClass.isTestable // true
扩展:观察者模式和发布订阅模式的区别?
观察者模式:Subject和Observer直接关联,没有中间媒介,如addEventListener绑定事件
发布订阅模式:Publisher和Observer是松散耦合的,需要中间媒Event channel,如EventBus;发布者不直接与订阅者通信,而是将消息发布到消息代理,然后由代理负责将消息传递给所有订阅者
4. 实际工作中,做过那些Vue的优化?
v-if 和v-show
v-if 是条件渲染指令, 条件改变时会彻底销毁和创建组件,最初渲染消耗较小;
v-show使用CSS display 属性来控制元素的显示与隐藏,切换性能开销较小,适用于频繁切换场景;
大部分时候使用v-if更好,不要过度优化
v-for使用key,且避免同时和v-if同时使用
严格意义已经不能算优化项了,v-for不是key会报警;
v-for的优先级比v-if高(vue2),因为这样会导致每次渲染都进行条件判断,影响性能;Vue3中优先级相反,建议使用计算属性替代实现兼容。
<ul><!--而且,key不要用index--><li v-for="(id,name) in list" :key="id">{{name}}</li>
</ul>
使用computed缓存
使用computed属性缓存计算属性,避免重复计算
export default {data() {return {msgList: [...] // 消息列表}},computed: {// 未读消息列表unreadCount() {return this.msglist.filter(m => m.read === false).length}}
}
keep-alive缓存组件
缓存大型列表或者表格,频繁切换的组件,如tabs;
不要乱用,缓存太多会占用内存,且不好debug
异步组件
针对体积较大的组件,如编辑器、复杂表格、复杂表单等
拆包,需要时异步加载,不需要则不加载
减少主包体积,首页加载会更快
// 使用Vue.js中的defineAsyncComponent方法来实现异步组件加载
import {defineAsyncComponent} from 'vue'export default {name: 'AsyncComponent', // 定义了一个名为AsyncComponent的组件/*** 通过components属性注册了一个名为 Child 的子组件,并使用defineAsyncComponent方法来定义异步加载这个子组件* 在defineAsyncComponent方法传入了一个函数,在需要渲染这个异步组件时被调用;使用动态import的方式来异步加载 './Child.vue' 组件,Webpack 会将这个异步加载的组件作为一个单独的chunk进行打包,这个chunk 的名称被指定为'async-child'。*/components: {Child: defineAsyncComponent(() => import(**/* webpackChunkeName: "async-child"*/** './Child.vue'))}
}
路由懒加载
项目比较大,拆分路由,保证首页先加载
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'const routes = [{path: '/',name: 'Home',component: Home},{path: '/about',name: 'About',// 通过动态import异步加载的About组件,并使用Webpack进行代码分割成名为"about"的chunkcomponent: () => import(/* webpackChunkeName: "about"*/ '../views/About.vue')}
]const router = createRouter({history: createWebHistory(),routes
})export default router
服务端渲染SSR
可使用Nuxt.js, 但按需优化,使用SSR的成本比较高
扩展: 使用Vue遇到那些坑?
内存泄漏
全局变量,全局事件,全局定时器和自定义事件等绑定后未销毁
Vue2响应式的缺陷(Vue3不再有)
data新增属性用Vue.set
data删除属性用Vue.delete
无法直接修改数据arr[index] = value
路由切换时scroll到顶部
SPA的通病,不仅仅是Vue
如列表页、滚动到第二屏,点击进入详情页,再返回列表页(此时组件重新渲染)就scroll到顶部
解决方案:在列表页缓存数据和scrollTop的值;当再次返回列表页时,渲染组件,执行scrollTo(scrollTop的值)
终极方案:MPA + APP webView
5. 实际工作中,对React做过的优化?(没实际用过)
模拟v-show
循环使用key
Fragment减少层级
JSX不要定义函数
构造函数bind this
使用shouldComponentUpdate
使用shouldComponentUpdata判断组件是否要更新
或者使用React.PureComponent
函数组件使用React.memo
Hooks缓存数据和函数
useMemo useCallBack
异步组件
import React, { Suspense } from 'react';const AsyncComponent = React.lazy(/* webpackChunkName: 'AsyncComponent'*/() => import('./AsyncComponent')
);function App() {return (<div><h1>My React App</h1><Suspense fallback={<div>Loading...</div>}><AsyncComponent /></Suspense></div>);
}export default App;
路由懒加载
import React, { lazy, Suspense } from 'react';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';const Home = lazy(() => import('./Home'));
const List = React.lazy(() => import(/* webpackChunkName: 'List'*/ './List'));const = App() => {return (<Router><Suspense fallback={<div>Loading...</div>}><Switch><Route exact path="/" component={Home}/><Route path="./list" component={List}/></Switch></Suspense></Router>);
}
SSR - Next.js
扩展: 使用React遇到过那些坑?
自定义组件的名称首字母大写
{/* 原生HTML组件*/}
<input/>{/* 自定义组件 */}
<Input/>
JS关键字冲突
{/* for 改成 htmlFor, class要改成className*/}
<label htmlFor="input-name" className="xxx">姓名 <input id="input-name"/>
</label>
JSX数据类型
setState是异步更新的
const curNum = this.state.num
this.setState({num: curNum + 1}, () => {console.log('newNum', this.state.num) // 正确
})
console.log('newNum', this.state.num) // 错误,直接打印值尚未变化
6. 如果H5很慢,该如何排查性能问题?
前端性能指标
First Paint(FP):指浏览器从响应用户输入网址地址到浏览器开始显示内容的时间。
First Contentful Paint(FCP):指浏览器从响应用户输入网址地址到页面首次绘制文本、图片、非白色的 canvas 或 SVG 的时间。
First Meaningful Paint(FMP):以前是测量用户加载体验的主要指标,但已被 Largest Contentful Paint 取代。
DomContentLoaded(DCL):DOM完全加载及解析事件触发
Largest Contentful Paint( LCP):指可视区“内容”最大的可见元素开始出现在屏幕上的时间点。
Load(L): Load 页面加载完成
使用Chrome devTools可以监控上述指标识别那部分执行的慢:
Performance可产看上述指标,并有网页快照(最好使用无痕模式避免缓存的影响)
Network可以查看各资源的加载时间(开启Show overview)
使用Lighthouse分析性能指标:
lighthouse是非常流行的第三方性能评测工具
支持移动端和PC端,可生成如下测试报告和优化建议
## lighthouse 安装
C:\Users\zhchen>**npm i lighthouse -g**
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'lighthouse@11.7.0',
npm WARN EBADENGINE required: { node: '>=18.16' },
npm WARN EBADENGINE current: { node: 'v18.14.2', npm: '8.19.1' }
npm WARN EBADENGINE }added 163 packages, and audited 164 packages in 7m12 packages are looking for fundingrun `npm fund` for detailsfound 0 vulnerabilities
## lighthouse 测试网页地址 --view表示查看测试报告 --preset=desktop
C:\Users\zhchen>**lighthouse https://www.imooc.com/ --view --preset=desktop使用PC端访问(默认移动端)**
? We're constantly trying to improve Lighthouse and its reliability.Learn more: https://github.com/GoogleChrome/lighthouse/blob/main/docs/error-reporting.mdMay we anonymously report runtime exceptions to improve the tool over time?We'll remember your choice, but you can also use the flag --[no-]enable-error-reporting (y/N) » falseLH:CLI:warn No response to error logging preference, errors will not be reported. +0m
lighthouse生成国内访问github的报告:
识别问题:哪里慢?加载慢 OR渲染慢
加载慢的优化方法
优化服务端硬件配置,使用CDN
路由懒加载,大组件异步加载—减少主包体积
优化HTTP缓存
渲染慢的优化方法
优化服务端接口(如Ajax获取数据慢)
继续分析,优化前端组件内部的逻辑(参考Vue React优化)
服务端渲染SSR
总结,排查性能问题的步骤:
分析性能指标,找到慢的原因;
对症下药,解决问题
持续跟进,持续优化
7. 工作中遇到过那些项目难点,是如何解决的?
描述问题:背景+现象+造成的影响
问题如何被解决:分析+解决
自己的成长:学到了什么 + 以后如何避免