前端性能优化大总结

本文总结了前端性能优化的各种策略,包括减少HTTP请求,合并脚本与样式文件,使用CDN,利用缓存,以及图像优化等。通过开启gzip,压缩资源,减少cookie大小,优化DOM操作等方式,提高页面加载速度。同时,建议使用恰当的图片格式,预加载和事件委托等技术,提升用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

减少HTTP请求

合并脚本与样式文件

使用雪碧图

图像使用data url

图片被转换成base64编码的字符串形式,内嵌于CSS或HTML中,而不必单独请求。

<img src="data:image/gif;base64,R0lGODlhMwAxAIAAAAAAAP///
yH5BAAAAAAALAAAAAAzADEAAAK8jI+pBr0PowytzotTtbm/DTqQ6C3hGX
ElcraA9jIr66ozVpM3nseUvYP1UEHF0FUUHkNJxhLZfEJNvol06tzwrgd
LbXsFZYmSMPnHLB+zNJFbq15+SOf50+6rG7lKOjwV1ibGdhHYRVYVJ9Wn
k2HWtLdIWMSH9lfyODZoZTb4xdnpxQSEF9oyOWIqp6gaI9pI1Qo7BijbF
ZkoaAtEeiiLeKn72xM7vMZofJy8zJys2UxsCT3kO229LH1tXAAAOw==">

data url适用的场景:

  • 访问外部资源受限
  • 图片是在服务器端动态生成的,每个用户显示的都不同时
  • 图片体积较小,占用一个HTTP回话不是很值得。

但data-url也有一些缺点:

  • Base64编码的数据体积通常是原数据的体积4/3,也就是Data URL形式的图片会比二进制格式的图片体积大。
  • Data URL形式的图片不会被浏览器缓存,这意味着每次访问这样页面时都被下载一次。

划分主域

一是可以使访问静态资源的请求避免每次请求都带上主站的cookie,可以做到cookie free。
二是浏览器可以并发地处理几个域名的请求,一般是6个,这样不同域名下的静态资源可以并行请求了。

例如,动态内容放在 csspod.com 上,静态资源放在 static.csspod.com 上。这样可以禁用静态资源域下的 Cookie,减少数据传输

减小请求带宽

开启gzip

在服务器端进行配置,将要传输给浏览器的文件进行gzip编码。对纯文本内容可压缩到原大小的40%。

压缩脚本与样式文件

减小cookie大小

Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。

  • 去除不必要的 Cookie;
  • 尽量压缩 Cookie 大小;
  • 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
  • 设置合适的过期时间。

图像优化

使用恰当的图片格式

这里写图片描述

响应式图片

根据所在设备的不同,加载不同尺寸的图片。

使用picture元素

HTML <picture> 元素是一个容器,用来为其内部特定的 <img> 元素提供多样的 <source> 元素。
浏览器会根据当前页面的布局以及当前浏览的设备去从中选择最合适的资源。

<picture alt="description">
    <source src="/path/to/medium-image.png" media="(min-width:600px)">
    <source src="/path/to/large-image.png" media="(min-width:800px)">
    <img src="/path/to/mobile-image.png" alt="image description">
</picture>

其中的img元素是默认情况下显示的图片源,在其上面的两个source元素则是在特定媒体查询(media queries)条件下显示的图片。

也可以使用img标签的srcset属性:

<img src="path-to-default-image.jpg" alt=""
  srcset="path-to-default-image.jpg 600w 200h 1x,
          path-to-another-image.jpg 600w 200h 2x,
          path-to-a-third-image.jpg 200w 200h">

或者直接在样式文件中使用媒体查询:

.someElement { background-image: url(sunset.jpg); }

@media only screen and (max-width : 1024px) {
    .someElement { background-image: url(sunset-small.jpg); }
}

图片压缩

JPG和PNG格式的图片生成后,一般还有进一步优化的空间。一般借助一些优化工具来完成。
一般像gulp也有图片压缩的插件,可以在工程中使用,如gulp-imagemin

使用CDN

CDN是在互联网上按离地位置分布计算机网络,通过向地理位置最近的用户传输内容,CDN能极大地减少网络延时。

利用缓存

使用外部js和css

添加 Expires 或 Cache-Control 响应头

将资源的过期时间设置的较长,就可以利用缓存

配置Etag

减少DNS查找

首次访问、没有相应的 DNS 缓存时,域名越多,查询时间越长。所以应尽量减少域名数量。但基于并行下载考虑,把资源分布到 2 个域名上。

页面结构

样式表放在头部

通常情况下 CSS 被认为是阻塞渲染的资源,在CSSOM 构建完成之前,页面不会被渲染,放在顶部让样式表能够尽早开始加载。
但如果把引入样式表的 link 放在文档底部,页面虽然能立刻呈现出来,但是页面加载出来的时候会是没有样式的,是混乱的。当后来样式表加载进来后,页面会立即进行重绘,这也就是通常所说的闪烁了。

js放在底部

如果把script标签放在head中,由于脚本会阻塞页面渲染,因此直到他们全部下载并执行完成后,页面的渲染才会继续。
浏览器在解析到body标签之前,不会渲染页面的任何部分,因此把脚本放到页面顶部将会导致明显的延迟,通常表现为显示空白页面。

大部分浏览器都允许并行下载js文件,因此script标签在下载外部资源时不会阻塞其他script标签。但是仍然会阻塞其他资源的下载,比如图片。

正是由于脚本会阻塞页面其他资源的下载,因此推荐将所有的script标签尽可能放到body标签的底部。

代码逻辑级别

CSS性能优化

避免使用@import

使用@import导入的样式文件不能够被并行下载,也容易造成事件嵌套。

避免CSS表达式

IE5及其以后版本支持在CSS中使用expression,用来把CSS属性和Javascript脚本关联起来,CSS属性根据JavaScript表达式的计算结果来设置。
表达式的问题就在于它的计算频率要比我们想象的多。不仅仅是在页面显示和缩放时,就是在页面滚动、乃至移动鼠标时都会要重新计算一次。

避免通配选择器

首先要了解选择器匹配的机制:从右向左匹配

#header > a {font-weight:blod;}

针对这样的规则,浏览器会遍历页面中所有的a元素并确定其父元素的id是否为header。
如果把例子的子选择器改为后代选择器则会开销更多,在遍历页面中所有a元素后还需向其上级遍历直到根节点:

#header  a {font-weight:blod;}

通配选择器作用于所有的元素,如规则最右边为通配符:

.selected * {color: red;}

浏览器匹配文档中所有的元素后分别向上逐级匹配class为selected的元素,直到文档的根节点,因此其匹配开销是非常大的,通常比开销最小的ID选择器高出1~3个数量级,所以应避免使用关键选择器是通配选择器的规则。

去掉不匹配的样式

渲染性能

主要针对动画,最好开启硬件加速,具体参考CSS动画的性能优化

DOM操作

使用querySelectorAll()

不光速度快,并且querySelectorAll()返回一个NodeList类数组对象而不是动态的HTML集合,不会对应实时的文档结构。

减少DOM 操作

每一次DOM发生了更改都会导致重新渲染,包括构建DOM树、渲染树。重绘和重排等。

重排发生的场景:页面布局和几何属性改变:

  • 添加或删除可见的元素
  • 元素位置改变
  • 元素尺寸改变(包括外边距、内边距、边框厚度、宽度、高度等)
  • 内容改变(比如文本改变,或图片被另一个不同尺寸的图片替代)
  • 页面渲染器初始化
  • 浏览器窗口尺寸改变

减少DOM操作:

  • 缓存已经访问过的元素
  • 将改变合并:一次性改变class而不是样式
  • 批量修改DOM
    • 使元素脱离文档流
      • 隐藏元素
      • 使用document fragment
    • 对其应用改变
    • 将其带回文档中

这个过程中只会触发两次重排:第一步和第三步。如果不按这个步骤来的话,第二步所产生的的任何修改都会触发一次重排。

高频事件防抖动

在每次修改了 DOM 或者其样式之后都要进行 DOM树的构建,CSSOM 的重新计算,进而得到新的渲染树。浏览器会利用新的渲染树对页面进行重排和重绘,以及图层的合并。

多线程

使用Web workers,在后台线程中运行一些处理纯数据,或者与浏览器UI无关的长时间运行脚本,从而达到不阻塞UI线程渲染的目的。

你需要创建一个完全独立的js文件,其中包含了需要在worker中运行的代码。然后调用 Worker() 构造函数,指定一个要在 worker 线程内运行的脚本的 URI

var worker = new Worker("code.js");

此代码一旦执行,将会为这个文件创建一个新的线程和一个新的worker运行环境。
该文件会被异步下载,直到文件下载并执行完成后才会启动此worker。

worker与网页代码通过事件接口进行通信:

  • 网页通过postMessage()方法给worker传递数据。它接收一个参数,即需要传递给worker的数据。
  • worker通过onmessage事件处理器来接收信息
var worker = new Worker("code.js");
//接收woker传回的事件
worker.onmessage = function(event){
    alert(event.data);
}
worker.postMessage("hello")

worker:

self.onmessage = function(event){
    self.postMessage("hi"+event.data+ "!")
}

worker也可以通过她自己的PostMessage方法把信息回传给页面.

message系统是网页和worker通信的唯一途径。

延迟加载

页面初始加载时哪些内容是绝对必需的?不在答案之列的资源都可以延迟加载。比如:

  • 非首屏使用的数据、样式、脚本、图片等;
  • 用户交互时才会显示的内容。

无阻塞js加载

在页面加载完成后、或者在某一时机才加载js代码:

  • 使用defer属性
    带有defer和async属性的脚本,采用并行下载的方式,即下载过程中不会阻塞页面加载。 这两者的区别在于执行时机,async是下载完成后自动执行,而defer需要等待页面加载完成后执行。

  • 动态脚本
    可以利用DOM动态创建脚本,脚本文件在该元素被添加到页面时开始下载。
    这种技术的重点在于,无论何时启动下载,文件的下载和执行过程不会阻塞页面其他进程。

var script = document.createElement("script");

script.onload = function(){
    ……
}

script.src = "file1.js";
document.head.appendChild(script);
  • XMLHttpRequest脚本注入
    另一种无阻塞脚本的方法是使用XMLHttpRequest对象获取脚本并注入页面中。
    也就是通过ajax来获取外部脚本文件,然后通过动态创建script元素将该脚本添加到页面中。
var xhr = new XMLHttpRequest();
xhr.open("get","file.js",true);
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        if(xhr.status >= 200 && xhr.status <= 300 || xhr.status == 304){
            var script = document.createElement("script");
            script.text= xhr.responseText;
            document.head.appendChild(script);
        }
    }
}

xhr.send(null);

这种方法的主要优点是,可以下载js代码但不立即执行。由于代码是在script标签之外返回的,因此它下载后不会自动执行。

这种方法的主要局限性是所请求的js文件必须与所请求的页面处于相同的域。这意味着js文件不能从CDN下载。

预加载

把将要使用到的资源预先加载,以便用户访问时更快地响应。

事件委托

当页面中存在大量元素,而且每一个都要一次或多次绑定事件处理器时,这种情况可能会影响性能。

解决这种问题的技术就是事件委托。事件能逐层冒泡并被父级元素捕获。使用事件委托,只需给外层元素绑定一个处理器,就可以处理其子元素上触发的所有事件。
同时新添加的子元素也不必重新绑定事件。

避免图片src为空

图片 src 属性值为空字符串可能以下面两种形式出现:

<img src="" />  
var img = new Image();  
img.src = ""; 

虽然 src 属性为空字符串,但浏览器仍然会向服务器发起一个 HTTP 请求,不同的浏览器有不同的处理方式,但带来的坏处是显而易见的:

  • 给服务器造成意外的流量负担
  • 浪费服务器计算资源
  • 可能产生报错

数据存取

每个执行上下文都有自己的作用域链,用于解析标识符。 在函数执行过程中,每遇到一个变量都会经历一次标识符解析的过程,以决定从哪里获取或存储数据。该过程搜索执行上下文的作用域链,查找同名标识符。

搜索过程从作用域链头部开始,也就是当前运行函数的活动对象。如果找到,就使用这个标识符对应的变量;如果没找到就继续搜索作用域链中的下一个对象。

正是这种搜索过程影响了性能,因此最好使用本地变量,减少搜索时间。

闭包、作用域和内存

通常来说函数的活动对象会随着执行上下文一同销毁,但由于闭包的scope属性包含了与执行上下文作用域链相同的对象的引用,由于引用还存在于闭包的scope中,因此变量对象无法被销毁。这意味着闭包需要更多的内存开销。

原型链

属性或方法在原型链中的位置越深,访问它的速度越慢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值