一、前端实战方案
1. H5 页面如何进行 "首屏" 优化?
路由懒加载
- 适合与 SPA(不适用 MPA)
- 路由拆分,优先保证首页加载
服务端渲染 SSR
- 传统的前后端分离(SPA)渲染页面的过程复杂
- SSR 渲染页面过程简单,所以性能好
- 如果是纯 H5 页面,SSR 是性能优化的终极方案
- SSR 是一门"古老"的技术
- 刚刚兴起 Web 1.0 时,就是 SSR 技术:PHP ASP JSP 等
- Nuxt.js (Vue)
- Next.js (React)
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
扩展
- 性能优化要配合分析、统计、评分等,做了事情要有结果。
- 性能优化也要配合体验,如骨架屏,loading 动画等
- 不同的形式,有不同的优化方式,要积极和面试官沟通
2. 后端一次性返回 10w 条数据,你该如何渲染?
后端返回 10w 条数据,本身技术方案设计就不合理
主动和面试沟通此事
如果面试官非要这么做,那再继续寻找解决方案
浏览器能否处理 10w 条数据
- JS 没问题
- 渲染到 DOM 会非常卡顿
方案
自定义中间层
- 自定义 nodejs 中间层,获取并拆分这 10w 条数据。
- 前端对接 nodejs 中间层,而不是服务端
- 成本比较高
虚拟列表[低配手机效果不一定好]
- 只渲染可视区域 DOM
- 其他隐藏区域不显示,只用
<div>
撑起高度 - 随着浏览器滚动,创建和销毁 DOM
虚拟列表-第三方 lib
- 虚拟列表实现起来非常复杂,可借用第三方 lib
- Vue-virtual-scroll-list
- React-virtualiszed
3. 前端常用的设计模式有哪些? 并说明使用场景?
23个设计模式
设计原则
- 最重要的思想:开放封闭原则
- 对扩展开放
- 对修改关闭
- 最重要的思想:开放封闭原则
设计模式
工厂模式
- 用一个工厂函数,来创建实例,隐藏 new
- 如 jQuery $ 函数
- 如 React createElement 函数js
class Foo {} // 工厂模式 function factory() { return new Foo() } const f1 = factory()
单例模式
- 全局唯一的实例(无法生成第二个)
- 如 Vuex Redux 的 store
- 如 全局唯一的 dialog modaljs
class SingleTon { public static instance; private constructor() {} public static getInstance() { if (!this.instance) { this.instance = new SingleTon() } return this.instance; } fn1() {} fn2() {} } const s1 = SingleTon.getInstance() s1.fn1() s1.fn2() const s2 = SingleTon.getInstance() console.log(s1 === s2 ) // true
代理模式
- 使用者不能直接访问对象,而是访问一个代理层
- 在代理层可以监听 get set 做很多事情
- 如 ES6 Proxy 实现 Vue3 响应式
观察者模式
js// 一个主题,一个观察者,主题变化之后触发观察者执行 btn.addEventListener('click', () => {})
发布订阅
js// 绑定 event.on('event-key', () => { // 事件1 }) event.on('event-key', () => { // 事件2 }) // 触发执行 event.emit('event-key')
装饰器模式
- 原功能不变,增减一些新功能(AOP 面向切面编程)
- ES 和 Typescript 的 Decorator 语法
- 类装饰器,方法装饰器
- nest.js node.js的框架
连环问:观察者模式和发布订阅模式的区别
观察者模式
- Subject 和 Observer 直接绑定,没有中间媒介
- 如 addEventListener 绑定事件
发布订阅
- Publisher 和 Observer 互不相识,需要中间媒介 Event channel
- 如 EventBus 自定义事件
4. 你在实际工作中,做过哪些 Vue 优化?
v-if 和 v-show
- v-if 彻底销毁组件
- v-show 使用 CSS 隐藏组件
- 大部分情况下使用 v-if 更好,不要过度优化
v-for 使用 key, 不要使用 index
使用 computed 缓存
keep-alive 缓存组件
- 频繁切换的组件,如 tabs
- 不要乱用,缓存太多会占用内存,且不好 debug
异步组件 defineAsyncComponent
- 针对体积较大的组件,如编辑器、复杂表格、复杂表单等
- 拆包,需要时异步加载,不需要时不加载
- 减少主包的体积,首页会加载更快
服务端渲染 SSR
- 可使用 Nuxt.js
- 按需优化,使用 SSR 的成本比较高
连环问:你使用 Vue 遇到过哪些坑?
内存泄漏
- 全局变量、全局事件、全局定时器
- 自定义事件
Vue2 响应式的缺陷(Vue3 不再有)
- data 新增属性用 Vue.set
- data 删除属性用 Vue.delete
- 无法直接修改数据 arr[index] = value
路由切换时 scroll 到顶部
- SPA 的通病,不仅仅是 Vue
- 如:列表页,滚动到第二屏,点击进入详情页
- 再返回到列表页(此时组件重新渲染)就 scroll 到顶部
- 解决方案
- 方案一:
- 在列表页缓存数据和 scrollTop 值
- 当再次返回列表页时,渲染组件,执行 scrollTo(xx)
- 终级方案:
- MPA + App WebView
- 方案一:
5. 你在实际工作中,做过哪些 React 优化?
修改 CSS 模拟 v-show
循环使用 key
使用 Fragment 减少层级
JSX 中不要定义函数
要在构造函数中 bind this
使用 shouldComponentUpdate
- 使用 shouldComponentUpdate 判断组件是否要更新
- 注意1:this.setState() 不可变数据。意思不要修改里面的值,比如:数组 使用 concat 不要使用 push
- 注意2:React 默认会让所有的子组件都更新,无论涉及的数据是否变化。
- componentDidUpdate()
- 子组件可以自己控制是否更新js
shouldComponentUpdate(nextProps, nextProps) { // 默认 return true; // 自己控制 if (this.props.list === nextProps.list) { return false; } return true; }
- 或者使用 React.PureComponent
- 函数组件使用 React.memo
- 使用 shouldComponentUpdate 判断组件是否要更新
异步组件
路由懒加载
SSR - Next.js
拓展:不可变数据第三方插件 immer
连环问:你使用 React 遇到哪些坑?
- 自定义组件的名称首字母要大写
- JSX 关键字的冲突
- JSX 的数据类型
- setState 是异步更新的
6. 如何统一监听 Vue 组件报错?
答案:
errorCaptured 监听下级组件错误,返回 false 阻止向上传播
errorHandler 监听全局 Vue 组件错误
window.onerror 监听其他 JS 错误,如异步
window.onerror
- 全局监听所有 JS 报错
- 但它是 JS 级别的,识别不了 Vue 组件信息
- 捕捉一些 Vue 监听不到的错误
js// 方式一: window.onerror = function (msg, source, line, column, error) { } // 方式二: window.addEventListener('error', event => {})
errorCaptured 生命周期 Vue
- 监听所有下级组件的错误
- 返回 false 会阻止向上传播
errorHandler 配置 跟 window.onerror 冲突
- Vue 全局错误监听,所有组件错误都会汇总到这里
- 但是 errorCaptured 返回 false, 不会传播到这里
异步错误 Vue 监听不到
- 异步回调里的错误,errorHandler 监听不到
- 需要使用 window.onerror
扩展
- Promise 为处理的 catch 需要 onUnhandledRejection
- JS 报错统一(埋点、上报、统计)
7. 如何统一监听 React 报错?
答案:
ErrorBoundary 监听组件渲染报错
事件报错使用 try-catch 或 window.onerror
异步报错使用 window.onerror
ErrorBoundary 组件
- 监听所有下级组件报错,可降级展示 UI
- 只监听组件渲染时报错,不监听 DOM 事件、异步错误
- production 环境生效,dev 会直接抛出错误
事件报错
- ErrorBoundary 不会监听 DOM 事件报错
- 可用 try-catch
- 可用 window.onerror
异步错误
- ErrorBoundary 不会监听异步报错
- 可用 window.onerror
8. 如果一个 H5 很慢,你该如何排查性能问题?
答案:
分析性能指标,找到慢的原因
对症下药,解决问题
持续跟进,持续优化
性能指标,性能检测工具
使用"二分法", 可逐步找出问题根源
要有"监控","持续跟进"的思维。解决了问题,还得保持住
前端性能指标
- First Paint(FP)
- First Contentful Paint(FCP)
- First Meaningful Paint(FMP) 已弃用,改用 LCP
- DomContentLoaded(DCL)
- Largest Contentful Paint(LCP)
- Load(L)
Chrome devTools 【测试最好在无痕模式下查看】
- Performance 可查看上述性能指标,并有网页快照
- Network 可以查看各个资源的加载时间
Lighthouse
- 非常流行的第三方性能评测工具
- 支持移动端 和 PC
- 使用
- npm i lighthouse -g
- lighthouse https://www.imooc.com/ --view --preset=desktop
- npm i lighthouse -g
网页加载慢
- 优化服务端硬件配置,使用 CDN
- 路由懒加载,大组件异步加载——减少主包的体积
- 优化 HTTP 缓存策略
网页渲染慢
- 优化服务端接口
- 继续分析,优化前端组件内部的逻辑
- 服务端渲染 SSR
持续跟进
- 性能优化是一个循序渐进的过程,不像 bug 一次性解决
- 持续跟进统计结果,再逐步分析性能瓶颈,持续优化
- 可使用第三方统计服务,如阿里云 ARMS、百度统计
9. 你工作经历中,遇到过哪些项目难点,如何解决的?
答案模板
- 描述问题:背景 + 现象 + 造成的影响
- 问题如何被解决:分析 + 解决
- 自己的成长:学到了什么 + 以后如何避免