前端八股之性能优化

Web前端八股文性能优化
2025-03-12 - 13:59
四季橙子
2025-03-12 - 13:59

CDN

1. CDN的概念

CDN(Content Delivery Network,内容分发网络)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将资源发送给用户,来提供高性能、可扩展性及低成本的网络内容传递。 典型的CDN系统由下面三个部分组成:

  • 分发服务系统:最基本的工作单元就是Cache设备,cache(边缘cache)负责直接响应最终用户的访问请求,把缓存在本地的内容快速地提供给用户。同时cache还负责与源站点进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保存在本地。Cache设备的数量、规模、总服务能力是衡量一个CDN系统服务能力的最基本的指标。
  • 负载均衡系统:主要功能是负责对所有发起服务请求的用户进行访问调度,确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡(GSLB)和本地负载均衡(SLB)。全局负载均衡主要根据用户就近性原则,通过对每个服务节点进行“最优”判断,确定向用户提供服务的cache的物理位置。本地负载均衡主要负责节点内部的设备负载均衡
  • 运营管理系统:运营管理系统分为运营管理和网络管理子系统,负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作,包含客户管理、产品管理、计费管理、统计分析等功能。

2. CDN的作用

CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问

  1. 在性能方面,引入CDN的作用在于
    • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
    • 部分资源请求分配给了CDN,减少了服务器的负载
  2. 在安全方面,CDN有助于防御DDoS、MITM等网络攻击
    • 针对DDoS:通过监控分析异常流量,限制其请求频率
    • 针对MITM:从源服务器到CDN节点到 ISP,全链路 HTTPS 通信

3. CDN的原理

  1. 用户未使用CDN缓存资源的过程
    1. 浏览器通过DNS对域名进行解析,依次得到此域名对应的IP地址
    2. 浏览器根据得到的IP地址,向域名的服务主机发送数据请求
    3. 服务器向浏览器返回响应数据
  2. 用户使用CDN缓存资源的过程
    1. 对于点击的数据的URL,经过本地DNS系统的解析,发现该URL对应的是一个CDN专用的DNS服务器,DNS系统就会将域名解析权交给CDN专用的DNS服务器
    2. CND专用DNS服务器将CND的全局负载均衡设备IP地址返回给用户
    3. 用户向CDN的全局负载均衡设备发起数据请求
    4. CDN的全局负载均衡设备根据用户的IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
    5. 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的IP地址返回给全局负载均衡设备
    6. 全局负载均衡设备把服务器的IP地址返回给用户
    7. 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端

懒加载

1. 懒加载的概念

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。

如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

2. 懒加载的特点

  • 减少无用资源的加载:使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担
  • 提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验
  • 防止加载过多图片而影响其他资源文件的加载:会影响网站应用的正常使用

3. 懒加载的实现原理

图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,我们使用HTML5的data-xxx属性来储存图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。

使用原生JavaScript实现懒加载

知识点:

  1. window.innerHeight || document.documentElement.clientHeight 是浏览器可视区的高度
  2. document.body.scrollTop || document.documentElement.scrollTop是浏览器滚动的过的距离
  3. HTMLElement.prototype.offsetTop是元素顶部距离文档顶部的高度
  4. 图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop

代码:

<div class="container">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
</div>
<script>
const imgs = document.querySelectorAll('img');
function lozyLoad(){
        const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        const winHeight = window.innerHeight;
        for(let i = 0;i < imgs.length;i++){
            if(imgs[i].offsetTop < scrollTop + winHeight ){
                imgs[i].src = imgs[i].getAttribute('data-src');
            }
        }
    }
window.addEventListener('scroll', lazyLoad);
</script>

使用新的IntersectionObserver API实现懒加载

实现原理:​

通过IntersectionObserver观察图片与视口的交叉状态,当图片进入视口时触发回调,动态加载图片。该API自动处理位置计算,无需手动监听滚动事件,性能更优。

关键配置项:​

  • root: 默认为视口,可指定滚动容器。
  • rootMargin: 扩展/缩小触发范围(如提前 100px 加载)。
  • threshold: 交叉比例阈值(0 表示刚进入视口即触发)。

实现步骤:​

  1. 创建观察器实例,定义交叉回调。
  2. 遍历所有图片,注册观察器监听。
  3. 图片加载后解除观察,避免重复触发。
<img data-src="image.jpg" class="lazy"> 
<img data-src="image2.jpg" class="lazy">

<script>
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {  // 判断是否进入视口
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);   // 停止观察已加载图片
    }
  });
}, { 
  rootMargin: '0px 0px 100px 0px' // 提前 100px 触发加载
});

// 注册所有懒加载图片
document.querySelectorAll('img.lazy').forEach(img => {
  observer.observe(img);
});
</script>

回流与重绘

1. 回流与重绘的概念及触发条件

回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流

下面这些操作会导致回流:

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活CSS伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的DOM元素

在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种

  • 全局范围:从根节点开始,对整个渲染树进行重新布局
  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。 下面这些操作会导致重绘:

  • colorbackground相关属性:background-colorbackground-image
  • outline相关属性:outline-coloroutline-width text-decoration
  • border-radiusvisibilitybox-shadow

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流

2. 如何避免回流与重绘?

  • 操作DOM时,尽量在低层级的DOM节点进行操作
  • 使用CSS的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  • 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作DOM
  • 将元素先设置​display: none​,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。

节流与防抖

1. 对节流与防抖的理解

节流

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在scroll函数的事件监听上,通过事件节流来降低事件调用的频率。

适用场景:

  • 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
  • 缩放场景:监控浏览器resize
  • 动画场景:避免短时间内多次触发动画引起性能问题

防抖

函数防抖是指在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

适用场景:

  • 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
  • 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次

Webpack优化

如何提⾼webpack的打包速度?

Loader深度优化

  1. 限定文件范围
    使用 include/exclude 缩小Babel处理范围,避免扫描无关文件(如第三方库):
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      include: path.resolve(__dirname, 'src'), // 仅处理项目代码
      exclude: /node_modules/ // 排除第三方库
    }]
    
  2. 启用缓存与多线程
    • 通过 cacheDirectory 缓存Babel编译结果,减少重复编译
    options: { cacheDirectory: true } // 缓存到node_modules/.cache
    
    • 使用thread-loader并行处理任务(Webpack5推荐方案,替代已弃用的HappyPack)
    use: ['thread-loader', 'babel-loader'] // 多线程处理JS
    

构建流程加速策略

  1. 持久化缓存
    启用文件系统缓存:
    module.exports = {
      cache: { type: 'filesystem' } // 缓存到本地磁盘
    };
    
  2. 代码压缩并行化
    • 启用 TerserPlugin 多核压缩(Webpack5默认集成):
    optimization: {
      minimize: true,
      minimizer: [new TerserPlugin({ parallel: true })]
    }
    
    • CSS压缩使用 CssMinimizerPlugin,替代过时的OptimizeCSSAssetsPlugin
  3. DLL替代方案
    对于长期不变的第三方库,使用 externals+CDN引入,替代旧版DLL方案:
    externals: { react: 'React', lodash: '_' } // 配合HTML中CDN脚本引入
    

代码组织与加载优化

  1. 动态导入(Lazy Loading)
    通过 import() 语法实现按需加载,减少首屏体积:
    // 路由配置中动态加载组件
    const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue');
    

模块解析优化

  1. 路径解析加速
    • 使用 resolve.alias缩短常用路径:
    resolve: { 
      alias: { '@': path.resolve(__dirname, 'src') }
    }
    
    • 精简resolve.extensions后缀列表:
    extensions: ['.js', '.vue'] // 按使用频率排序
    
  2. 跳过无依赖解析
    使用 module.noParse 忽略独立库文件(如jQuery):
    module: { 
      noParse: /jquery|lodash/ // 无依赖的大型库
    }
    

特别注意事项

  1. Tree Shaking生效条件
    • 使用ES6模块语法(import/export
    • 生产模式(mode: 'production'
    • 避免副作用代码(可通过 sideEffects 配置)
  2. SSD硬件加速
    机械硬盘用户建议升级SSD,文件读写速度可提升5-10倍

2.如何减少 Webpack 打包体积

按需加载

在开发SPA项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个JS文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于loadash这种大型类库同样可以使用这个功能。

按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个Promise,当Promise成功以后去执行回调。

Tree Shaking

Tree Shaking可以实现删除项目中未被引用的代码,如果使用Webpack 4的话,开启生产环境就会自动启动这个优化功能。

3. 如何⽤webpack来优化前端性能?

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin和ParallelUglifyPlugin来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
  • 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于output参数和各loader的publicPath参数来修改资源路径
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

Vue的性能优化

1. 代码层面的优化

v-ifv-show区分使用场景

v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show就简单得多,不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS 的display属性进行切换。

所以,v-if适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show则适用于需要非常频繁切换条件的场景。

computedwatch区分使用场景

computed: 是计算属性,依赖其它属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取computed的值时才会重新计算computed的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用computed,因为可以利用 computed的缓存特性,避免每次获取值时,都要重新计算;

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用watch,使用watch选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

v-for遍历必须为item添加key,且避免同时使用v-if

v-for遍历必须为item添加key

在列表数据进行遍历渲染时,需要为每一项item设置唯一·值,方便Vue内部机制精准找到该条列表数据。当state更新时,新的状态值和旧的状态值对比,较快地定位到diff。

避免同时使用v-if

v-forv-if优先级,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性

事件的销毁

Vue组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。

onMounted(() => {
    addEventListener('click', handleClick)
})
onUnmounted(() => {
    removeEventListener('click', handleClick)
})

图片资源懒加载

路由懒加载

Vue是单页面应用,可能会有很多的路由引入 ,这样使用打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。

在Vue-Router中,可以使用() => import xx.vue来实现

第三方插件、组件库的按需引入

详见第三方插件文档操作。

优化无限列表性能(虚拟列表)

服务端渲染

2. 基础的Web技术优化

开启gzip压缩

gzip是GNUzip的缩写,最早用于UNIX系统的文件压缩。HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术,web服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如Apache,Nginx,IIS 同样支持,gzip压缩效率非常高,通常可以达到 70% 的压缩率。

浏览器缓存

为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,协商缓存)

CDN 的使用

浏览器从服务器上下载 CSS、js和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而CDN可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN具有更好的可用性,更低的网络延迟和丢包率。

使用Devtool Performance查找性能瓶颈

各大浏览器的Performance面板可以录制一段时间内的js执行细节及时间。

话题状态:正常
20 × 2025-03-12 - 18:55