【前端性能】性能优化手段-高频面试题
持续更新.............................最近更新2023/10/24
1. 讲一下png8、png16、png32的区别,并简单讲讲 png 的压缩原理
PNG8、PNG16、PNG32 是 PNG 图像格式的不同变种,它们主要区别在于颜色深度和透明度支持的不同。
区别
PNG8: PNG8 是一种 8 位颜色深度的 PNG 图像格式,它最多支持 256(2^8) 种颜色。对于颜色相对较简单、不需要透明度的图像,使用 PNG8 可以获得较小的文件大小。PNG8 图像使用一种叫做调色板(Palette)的技术来存储颜色信息,它会创建一个包含所有使用的颜色的列表,并在图像中通过索引来引用这些颜色。
PNG16: PNG16 是一种 16 位颜色深度的 PNG 图像格式,它支持更多的颜色,最多可达 655369(2^16)种颜色。PNG16 适用于一些颜色较丰富的图像,但同样不支持透明度。
PNG32: PNG32 是一种 32 位颜色深度的 PNG 图像格式,它支持上千万种颜色,并且支持完全透明度(alpha 通道)。PNG32 适用于需要精确透明度控制的图像,例如图标、Logo 等。
原理
PNG 图像使用无损压缩技术来减小文件大小。它主要通过以下两种压缩方式来实现:
-
使用 DEFLATE 算法:PNG 使用 DEFLATE 算法来对图像数据进行压缩,这是一种无损的压缩算法,可以将相邻的相似数据块识别并用更短的编码进行替代,从而减小文件大小。
1.1 扩展: DEFLATE算法是一种用于数据压缩的无损压缩算法。它是由Phil Katz于1993年创建的,最初用于PKZIP压缩工具中。DEFLATE算法结合了两种压缩技术:LZ77(Lempel-Ziv 77)和哈夫曼编码。
LZ77是一种基于字典的压缩技术,它通过查找和替换重复的数据来实现压缩。它使用滑动窗口和查找缓冲区来识别重复的数据,并用指向先前出现的相同数据的指针来替换它们。
哈夫曼编码是一种变长编码技术,它使用较短的编码表示频率较高的字符,而使用较长的编码表示频率较低的字符。这种编码方式可以有效地减少数据的存储空间。
DEFLATE算法首先使用LZ77压缩数据,然后使用哈夫曼编码对压缩后的数据进行进一步压缩。这种组合技术使得DEFLATE算法能够在保持数据完整性的同时,显著减小数据的大小。因此,DEFLATE算法在许多应用中被广泛使用,如ZIP文件压缩、HTTP协议中的gzip压缩等。 -
使用索引颜色和调色板:对于 PNG8 图像,它使用调色板技术来存储颜色信息。调色板是一个包含所有使用的颜色的列表,然后图像中使用颜色的索引来引用调色板中的颜色。这样可以大大减小文件大小,特别适用于颜色较简单的图像。
**注意:**由于 PNG 使用无损压缩,所以图像的质量不会因为压缩而损失,但这也导致 PNG 文件相对于其他有损压缩格式(如 JPEG)通常会更大。因此,在选择使用 PNG 还是其他格式时,需要根据图像类型、透明度需求和文件大小要求进行权衡。
2. 页面加载的过程中,JS 文件是不是一定会阻塞 DOM 和 CSSOM 的构建?
不一定。
JavaScript阻塞DOM和CSSOM的构建的情况主要集中在以下两个方面:
-
JavaScript文件被放置在head标签内部
当JavaScript文件被放置在head标签内部时,浏览器会先加载JavaScript文件并执行它,然后才会继续解析HTML文档。因此,如果JavaScript文件过大或服务器响应时间过长,就会导致页面一直处于等待状态,进而影响DOM和CSSOM的构建。 -
JavaScript代码修改了DOM结构
在JavaScript代码执行时,如果对DOM结构进行了修改,那么浏览器需要重新计算布局(reflow)和重绘(repaint),这个过程会较为耗时,并且会阻塞DOM和CSSOM的构建。
除此之外,还有一些情况下JavaScript并不会阻塞DOM和CSSOM的构建
:
- 通过设置 script 标签的
async 、defer
属性避免阻塞DOM和CSSOM的构建
- async:异步加载JavaScript文件,脚本的下载和执行将与其他工作同时进行(例如从服务器请求其他资源、渲染页面等),而不必等到脚本下载完成才开始这些操作。因此,在使用 async 属性时,脚本的加载和执行是异步的,并且不保证脚本在页面中的顺序。
- defer属性 :属性告诉浏览器立即下载脚本文件,但有一个重要的区别:当文档解析时,脚本不会执行,直到文档解析完成后才执行。这意味着脚本将按照它们在页面上出现的顺序执行,并且在执行之前,整个文档已经被解析完毕了。
- Web Workers :Web Workers 是一种运行在后台线程的JavaScript脚本,它不会阻塞DOM和CSSOM的构建,并且可以利用多核CPU提高JavaScript代码执行速度。
总结
在一定情况下,JavaScript的执行会阻塞DOM和CSSOM的构建。
但是,在实际应用中,我们可以通过设置 script 标签的 async、defer 属性、使用Web Workers等方式来避免这个问题。
3. 导致页面加载白屏时间长的原因有哪些,怎么进行优化?
一、白屏时间 (FCP(First Contentful Paint))
白屏时间:即用户点击一个链接或打开浏览器输入URL地址后,从屏幕空白到显示第一个画面的时间。
二、白屏时间的重要性
当用户点开一个链接或者是直接在浏览器中输入URL开始进行访问时,就开始等待页面的展示。页面渲染的时间越短,用户等待的时间就越短,用户感知到页面的速度就越快。这样可以极大的提升用户的体验,减少用户的跳出,提升页面的留存率。
三、白屏的过程
从输入url,到页面的画面展示的过程
1、首先,在浏览器地址栏中输入url
2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。
3、在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。
5、握手成功后,浏览器向服务器发送http请求,请求数据包。
6、服务器处理收到的请求,将数据返回至浏览器
7、浏览器收到HTTP响应
8、读取页面内容,浏览器渲染,解析html源码
9、生成Dom树、解析css样式、js交互,渲染显示页面
浏览器下载HTML后,首先解析头部代码,进行样式表下载,然后继续向下解析HTML代码,构建DOM树,同时进行样式下载。当DOM树构建完成后,立即开始构造CSSOM树。理想情况下,样式表下载速度够快,DOM树和CSSOM树进入一个并行的过程,当两棵树构建完毕,构建渲染树,然后进行绘制。
Tips:浏览器安全解析策略对解析HTML造成的影响:
当解析HTML时遇到内联JS代码,会阻塞DOM树的构建,会先执行完JS代码;当CSS样式文件没有下载完成时,浏览器解析HTML遇到了内联JS代码,此时,浏览器暂停JS脚本执行,暂停HTML解析。直到CSS文件下载完成,完成CSSOM树构建,重新恢复原来的解析。
JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。
四、白屏-性能优化
1. DNS解析优化
针对DNS Lookup环节,我们可以针对性的进行DNS解析优化。
- DNS缓存优化
- DNS预加载策略
- 稳定可靠的DNS服务器
DNS缓存优化
是一种提高网络性能和减少延迟的技术。DNS(Domain Name System)是将域名转换为IP地址的系统,它在互联网中起到了重要的作用。当我们访问一个网站时,计算机需要向DNS服务器查询该域名对应的IP地址,然后才能建立连接。
DNS缓存优化的目标是减少DNS查询的次数,从而加快网页加载速度。以下是一些常见的DNS缓存优化方法:
- 本地DNS缓存:操作系统和浏览器通常会在本地缓存最近查询的DNS记录。这样,当再次访问相同的域名时,就可以直接从本地缓存中获取IP地址,而无需进行DNS查询。
DNS服务器缓存:DNS服务器也会缓存最近查询的DNS记录。当多个用户查询相同的域名时,DNS服务器可以直接返回缓存的结果,而无需再次查询。- TTL设置:每个DNS记录都有一个TTL(Time-to-Live)值,表示该记录在缓存中的有效时间。通过设置适当的TTL值,可以控制DNS记录在缓存中的存储时间。较短的TTL值可以使DNS记录更频繁地更新,从而及时获取最新的IP地址。
- DNS负载均衡:一些网站使用多个IP地址来提供服务,并通过DNS负载均衡将流量分散到不同的服务器上。这样可以减轻单个服务器的负载,并提高响应速度。
- CDN(Content Delivery Network):CDN是一种分布式网络架构,它将网站的内容缓存在全球各地的服务器上。当用户访问网站时,CDN会根据用户的地理位置选择最近的服务器提供内容,从而减少网络延迟。
通过使用这些DNS缓存优化方法,可以显著提高网页加载速度和用户体验。
DNS预加载
是一种优化网页加载速度的策略,它通过在浏览器中预先解析域名的IP地址,以减少DNS查询的时间延迟。以下是几种常见的DNS预加载策略:
<link rel="dns-prefetch" href="...">
:在HTML文档的头部添加标签,使用rel属性设置为dns-prefetch,并在href属性中指定要预加载的域名。浏览器会在加载页面时提前解析这些域名的IP地址。
示例:<head> <link rel="dns-prefetch" href="https://example.com"> <link rel="dns-prefetch" href="https://cdn.example.com"> </head>
HTTP响应头
:服务器可以在HTTP响应头中添加Link字段,指示浏览器预加载特定域名的DNS信息。
示例:Link: </assets/styles.css>; rel=preload; as=style, </assets/script.js>; rel=preload; as=script, </api/data>; rel=preload; as=fetch
DNS预解析
:在CSS样式表中使用@import或url()函数引用外部资源时,浏览器会自动进行DNS预解析。@import url("https://fonts.googleapis.com/css?family=Open+Sans");
需要注意的是,DNS预加载并不适用于所有情况,它主要适用于那些在页面加载过程中需要进行大量DNS查询的情况。在使用DNS预加载时,应谨慎选择需要预加载的域名,避免过度预加载导致不必要的网络请求。此外,不同浏览器对DNS预加载的支持程度可能有所不同,因此在使用时需要进行兼容性测试。
2. TCP网络链路优化
- 调整TCP窗口大小:TCP窗口大小决定了在发送数据之后需要等待确认的时间。通过增大TCP窗口大小,可以提高网络吞吐量和传输速度。但是,过大的窗口大小可能导致网络拥塞,因此需要根据网络状况进行调整。
- 使用拥塞控制算法:TCP拥塞控制算法用于检测网络拥塞并调整发送速率。常见的拥塞控制算法包括TCP Reno、TCP Cubic等。选择适合网络环境的拥塞控制算法可以提高网络的稳定性和吞吐量。
- 启用快速重传和快速恢复:快速重传和快速恢复是TCP的一种机制,用于快速恢复丢失的数据包而不必等待超时。通过启用这些机制,可以减少数据传输的延迟和丢包的影响。
- 启用Selective Acknowledgment(SACK):SACK是一种TCP扩展,允许接收方向发送方报告丢失的数据段,从而避免不必要的重传。启用SACK可以提高网络的吞吐量和传输效率。
- 使用TCP加速器或优化器:TCP加速器或优化器是一种网络设备或软件,用于优化TCP连接的性能。它们可以通过压缩数据、缓存重复数据、优化传输路径等方式来提高网络性能。
使用带宽管理工具:带宽管理工具可以帮助控制网络流量,优化带宽分配和流量控制。通过合理管理带宽,可以提高网络的稳定性和性能。
这些是一些常见的TCP网络链路优化策略,可以根据具体的网络环境和需求选择适合的优化方法。同时,网络优化是一个复杂的过程,需要综合考虑多个因素,并进行实验和测试来评估优化效果。
3. 服务端处理优化
- 代码优化:对服务端代码进行优化,包括减少不必要的计算、避免重复操作、使用高效的算法和数据结构等。优化代码可以减少CPU和内存的使用,提高代码执行效率。
- 并发处理:使用多线程、多进程或异步处理等技术,充分利用服务器的多核处理能力,提高并发处理能力和吞吐量。合理地划分任务和资源,避免竞争条件和阻塞,提高系统的响应速度。
- 数据库优化:对数据库进行优化,包括合理设计数据库结构、使用索引、优化查询语句、缓存查询结果等。减少数据库的访问次数和查询时间,提高数据库的读写性能。
- 缓存优化:使用缓存技术将频繁访问的数据存储在内存中,减少对后端存储系统的访问。可以使用内存缓存(如Redis、Memcached)或分布式缓存(如CDN、分布式缓存系统)来提高数据访问速度。
- 负载均衡:使用负载均衡技术将请求分发到多个服务器上,避免单个服务器过载。可以使用硬件负载均衡器或软件负载均衡器来实现负载均衡,提高系统的可扩展性和稳定性。
- 异步处理:将耗时的操作(如文件读写、网络请求)转为异步处理,避免阻塞主线程。可以使用异步框架、消息队列等技术来实现异步处理,提高系统的并发能力和响应速度。
- 定期优化和监控:定期对服务端进行性能分析和监控,识别瓶颈和性能问题,并进行相应的优化。可以使用性能分析工具、日志分析工具等来帮助定位和解决问题。
这些是一些常见的服务端处理优化策略,可以根据具体的应用场景和需求选择适合的优化方法。同时,优化是一个持续的过程,需要不断地进行测试、评估和调整,以达到最佳的性能和用户体验。
4. 浏览器下载、解析、渲染页面优化
根据浏览器对页面的下载、解析、渲染过程,可以考虑一下的优化处理:
- 尽可能的精简HTML的代码和结构
- 尽可能的优化CSS文件和结构
- 一定要合理的放置JS代码,尽量不要使用内联的JS代码
- 将渲染首屏内容所需的关键CSS内联到HTML中,能使CSS更快速地下载。在HTML下载完成之后就能渲染了,页面渲染的时间提前,从而缩短首屏渲染时间;
- 延迟首屏不需要的图片加载,而优先加载首屏所需图片(offsetTop<clientHeight)
document.documentElement.clientHeight//获取屏幕可视区域的高度
element.offsetTop//获取元素相对于文档顶部的高度
因为JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。
实际开发中优化首屏时间可用的技术手段
1. SSR渲染(服务端渲染)
SSR渲染是指服务器端渲染(Server-Side Rendering)的过程。传统的Web应用通常使用客户端渲染(Client-Side Rendering)的方式,即在浏览器中使用JavaScript动态生成页面内容。而SSR渲染则是将页面的初始渲染过程从客户端转移到服务器端。
在SSR渲染中,服务器端会在接收到客户端请求时,动态生成完整的HTML页面,并将其发送给客户端。这样,客户端在接收到HTML页面后,可以直接显示内容,而不需要等待客户端渲染完成。这种方式可以减少页面加载时间和白屏时间,提高用户体验。
SSR渲染的过程通常包括以下步骤:
客户端发送请求到服务器端。
服务器端接收到请求,并根据请求的路由信息确定要渲染的页面。
服务器端获取页面所需的数据,可以通过调用API、查询数据库等方式获取数据。
服务器端将获取到的数据注入到页面模板中,生成完整的HTML页面。
服务器端将生成的HTML页面发送给客户端。
客户端接收到HTML页面后,可以直接显示内容,而不需要等待客户端渲染完成。
客户端在接收到HTML页面后,可以继续执行JavaScript代码,处理交互逻辑和动态更新页面。
通过使用SSR渲染,可以提高页面的加载速度和用户体验,特别是对于首次访问页面的用户。然而,SSR渲染也会增加服务器的负载和响应时间,因此在实施SSR时需要进行适当的性能优化和资源管理。
2. 骨骼图,减少白屏时间,提高用户体感和留存
前端骨骼图(Skeleton Screen)是一种在页面加载过程中展示页面结构的技术。它通过在页面加载时先展示一个简单的骨架结构,然后逐渐填充内容,给用户一种页面正在加载的感觉。
前端骨骼图的主要作用是改善用户体验,减少页面加载时的白屏时间。以下是前端骨骼图的几个作用:
提示页面正在加载:前端骨骼图可以向用户传达页面正在加载的信息,让用户知道他们的操作已经被响应,并且页面即将显示。
- 减少白屏时间:通过展示页面的大致结构,前端骨骼图可以减少页面加载时的白屏时间。即使页面内容尚未完全加载,用户也可以看到页面的大致布局,给予他们一种页面正在加载的感觉。
- 提高用户满意度:前端骨骼图可以提高用户的满意度,因为他们可以立即看到页面的结构,而不需要等待页面完全加载。
- 提示用户交互元素位置:通过展示页面的骨架结构,前端骨骼图可以帮助用户快速定位和识别页面中的交互元素,提高用户的操作效率。
总的来说,前端骨骼图可以改善用户体验,减少页面加载时的不适感,提高用户满意度和页面的可用性。它是一种简单而有效的技术,可以在页面加载过程中给用户提供更好的反馈和体验。
3. 流式渲染(Streaming Rendering)
流式渲染(Streaming Rendering)是一种渲染页面的技术,它允许在服务器端逐步生成和发送页面内容给客户端,而不需要等待整个页面完全渲染完成。
传统的页面渲染方式是等待服务器端生成完整的HTML页面,然后一次性将整个页面发送给客户端。这种方式在页面内容较多或复杂时,会导致较长的白屏时间,用户需要等待较长时间才能看到页面内容。
而流式渲染则是在服务器端逐步生成页面内容,并将其逐段发送给客户端。这样,客户端可以在接收到部分内容后就开始渲染和显示页面,而不需要等待整个页面完全加载。
流式渲染的优势包括:
1.减少白屏时间:通过逐步发送页面内容,流式渲染可以减少白屏时间,让用户更快地看到页面内容。
2. 提高用户体验:用户可以在页面渲染的过程中进行交互,而不需要等待整个页面加载完成。这可以提高用户的满意度和页面的可用性。
2. 节省带宽和资源:流式渲染只需要发送页面的部分内容,可以减少网络传输的数据量,节省带宽和服务器资源。
3. 适应性更强:流式渲染可以根据网络状况和设备性能动态调整发送的内容,以提供更好的用户体验。
流式渲染的实现通常涉及使用适当的技术和框架,如服务器端渲染(SSR)、HTTP分块传输、WebSockets等。通过合理地划分页面内容和使用适当的渲染策略,可以实现流式渲染并提供更好的用户体验。
4. 如果一个列表有 100000 个数据,这个该怎么进行展示?
1. 将数据分页,利用分页的原理,每次服务器端只返回一定数目的数据,浏览器每次只对一部分进行加载。
2. 使用懒加载的方法,每次加载一部分数据,其余数据当需要使用时再去加载。
3. 虚拟列表,每次只渲染需要视口的部分
什么是虚拟列表?
虚拟列表(Virtual List)是一种优化大型列表渲染性能的技术。它通过只渲染可见区域内的列表项,而不是渲染整个列表,来减少页面渲染的开销。
虚拟列表的作用是提高列表的性能和用户体验,特别是在处理大量数据时。传统的列表渲染方式会将所有列表项都渲染到DOM中,无论其是否可见。这会导致页面加载缓慢、滚动卡顿等问题。而虚拟列表只渲染可见区域内的列表项,可以大大减少DOM操作和渲染开销,提高页面的响应速度和流畅度。
虚拟列表的用法通常涉及以下几个方面:
… 列表项的高度或平均高度,以便计算可见区域的范围。
… 列表项的数量,以便根据可见区域的范围计算需要渲染的列表项。
… 列表项的渲染方式,通常是通过动态创建和销毁DOM元素来实现。
… 列表项的滚动事件,以便在滚动时更新可见区域的范围,并触发列表项的渲染。
虚拟列表的具体实现方式可以根据使用的前端框架或库而有所不同。一些流行的前端框架,如React、Vue等,提供了相应的虚拟列表组件或插件,可以方便地实现虚拟列表的功能。
总的来说,虚拟列表是一种优化大型列表渲染性能的技术,通过只渲染可见区域内的列表项来提高页面的响应速度和流畅度。它在处理大量数据时非常有用,并且可以与各种前端框架和库结合使用。
虚拟列表原理
虚拟列表的原理是通过动态渲染可见区域内的列表项,而不是渲染整个列表。它基于以下两个核心概念:
- 可见区域范围计算:虚拟列表需要计算出当前可见区域的起始索引和结束索引。这可以通过列表容器的高度、列表项的高度(或平均高度)以及滚动位置来确定。根据这些参数,可以计算出当前可见区域内的列表项数量和索引范围。
- 动态渲染:根据可见区域的索引范围,虚拟列表只渲染可见区域内的列表项。当用户滚动列表时,虚拟列表会根据滚动位置和可见区域范围的变化,动态地创建或销毁列表项的DOM元素。这样,只有当前可见区域内的列表项才会被渲染到页面上,而不会渲染整个列表。
虚拟列表的实现通常涉及以下步骤:
- 初始化:计算可见区域的范围,并渲染初始的可见区域内的列表项。
- 滚动事件监听:监听列表容器的滚动事件,当滚动发生时,触发更新可见区域的操作。
- 更新可见区域:根据滚动位置和列表项的高度(或平均高度),计算出新的可见区域的范围。根据新的可见区域范围,动态地创建或销毁列表项的DOM元素。
- 渲染列表项:根据可见区域的索引范围,渲染可见区域内的列表项。这可以通过使用虚拟DOM或其他渲染技术来实现。
通过以上步骤,虚拟列表可以在滚动时动态地渲染可见区域内的列表项,从而提高页面的渲染性能和用户体验。
需要注意的是,虚拟列表的实现方式可能因使用的前端框架或库而有所不同。一些流行的前端框架,如React、Vue等,提供了相应的虚拟列表组件或插件,可以方便地实现虚拟列表的功能。
react实现虚拟列表(使用react库)
在React中,可以使用一些库或组件来实现虚拟列表的功能。其中,react-window 和 react-virtualized 是两个常用的库,它们提供了虚拟列表的组件和功能。
下面是使用 react-window 实现虚拟列表的基本步骤:
- 安装 react-window 库:
npm install react-window
- 导入所需的组件和函数:
import { FixedSizeList } from 'react-window';
- 创建列表项组件:
const ListItem = ({ index, style }) => {
// 根据索引获取列表项的数据
const item = data[index];
return (
<div style={style}>
{/* 渲染列表项的内容 */}
<p>{item.title}</p>
</div>
);
};
- 渲染虚拟列表组件:
const VirtualList = () => {
return (
<FixedSizeList
height={400} // 列表容器的高度
width={300} // 列表容器的宽度
itemCount={data.length} // 列表项的总数
itemSize={50} // 列表项的高度
>
{ListItem} // 列表项组件
</FixedSizeList>
);
};
在上述代码中,FixedSizeList 组件是 react-window 提供的虚拟列表组件,它接收一些必要的属性,如列表容器的高度、宽度,列表项的总数和高度等。ListItem 组件则是自定义的列表项组件,根据索引获取对应的数据,并渲染列表项的内容。
通过以上步骤,就可以使用 react-window 实现一个简单的虚拟列表。你可以根据实际需求进行进一步的定制和优化,例如添加滚动事件监听、动态加载数据等。
请注意,以上代码仅为示例,实际使用时需要根据具体情况进行适当的修改和调整。
原生Js实现虚拟列表
- 创建列表容器元素:
<div id="list-container" style="height: 400px; overflow-y: scroll;"></div>
- 获取列表容器和列表项的相关信息:
const listContainer = document.getElementById('list-container');
const itemHeight = 50; // 列表项的高度
const totalItems = 1000; // 列表项的总数
- 渲染可见区域内的列表项:
function renderVisibleItems() {
const scrollTop = listContainer.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + Math.ceil(listContainer.clientHeight / itemHeight), totalItems);
for (let i = startIndex; i < endIndex; i++) {
const listItem = document.createElement('div');
listItem.style.height = itemHeight + 'px';
listItem.textContent = 'Item ' + i;
listContainer.appendChild(listItem);
}
}
- 监听列表容器的滚动事件,并在滚动时更新可见区域的列表项:
listContainer.addEventListener('scroll', renderVisibleItems);
通过以上步骤,就可以实现一个简单的原生虚拟列表。在滚动时,会根据可见区域的范围动态地创建和销毁列表项,从而提高性能和用户体验。
需要注意的是,以上代码仅为示例,实际使用时需要根据具体情况进行适当的修改和调整。例如,可以添加数据加载、缓存机制等来优化列表的性能。
4. 使用数组分块技术,基本思路是为要处理的项目创建一个队列,然后设置定时器每过一段时间取出一部分数据,然后再使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
5. 怎么进行站点内的图片性能优化?
在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以对图片进行优化势在必行。
如果页面启动时加载了几十张图片(甚至更多),而这些图片请求几乎是并发的,在 Chrome 浏览器,最多支持的并发请求次数是有限的,其他的请求会推入到队列中等待或者停滞不前,直到上轮请求完成后新的请求才会发出。所以相当一部分图片资源请求是需要排队等待时间的,过多的图片必然会影响页面的加载和展示
。
选择合适的图片格式
JPEG
JPEG 是由 Joint Photographic Experts Group 所开发出的一种图片。
它最大的特点是 有损压缩
。
这种高效的压缩算法使它成为了一种非常轻巧的图片格式。
另一方面,即使被称为“有损”压缩,JPG 的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。
此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉。
- 优点
- JPEG 格式的图片可以呈现数百万种颜色。所以每当网站需要呈现色彩丰富的图片,JPEG 总是最佳选择。
- 有损压缩,你可以通过压缩大大的减少图片的体积,一般图片用 60%级别比较合适,如果选择大于 75%的压缩等级,则会使图片有明显的质量下降。
- 无兼容性问题,所以开发者可以放心随意使用。
- 使用场景
- JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPEG 图片经常作为大的背景图、轮播图或 Banner 图出现。
- 但是有损压缩后的图片确实很容易露出马脚,当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩的图片模糊会相当明显。
- JPEG 图像不支持透明度处理,透明图片可选择使用 PNG。
PNG
PNG(可移植网络图形格式)是由 W3C 开发的图片格式,是一种无损压缩
的高保真的图片格式。它同时支持 8 位和 24 位,这里都是二进制数的位数。按照我们前置知识里提到的对应关系,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。
PNG 图片具有比 JPEG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPEG 的局限性,唯一的缺点就是 体积太大
。
- 应用场景
- PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
- 支持透明度处理,透明图片可选择使用 PNG
GIF
GIF 是一种最多支持 256 种颜色的 8 位无损图片格式。这个限制让 GIF 格式对于多颜色或者摄影图片的展示无能为力。
- 优点
- 支持 256 种颜色,文件体积通常都很小
- 支持透明
- 应用场景
- 支持动画,适合去展示一些无限循环的动画,比如图标、表情、广告栏等。
- 对于一些只有简单色彩的图片非常合适。
SVG
SVG(Scalable Vector Graphics)是一种基于XML的矢量图形格式,用于描述二维图形和图像。与位图图像(如JPEG、PNG)不同,SVG使用数学公式来描述图形,因此可以无损地缩放和放大而不会失真。
SVG图像由一系列的矢量图形元素组成,如路径(path)、矩形(rect)、圆形(circle)、椭圆(ellipse)、线条(line)、多边形(polygon)等。这些元素可以通过属性来定义其位置、大小、颜色、填充等样式。
SVG具有以下特点和优势:
- 矢量图形:SVG图像是基于数学公式的矢量图形,可以无损地缩放和放大而不会失真。这使得SVG非常适合在不同尺寸和分辨率的设备上显示,如高清屏幕、移动设备等。
- 小文件大小:由于SVG使用文本格式存储图形数据,相对于位图图像,SVG图像通常具有较小的文件大小,这有助于减少网络传输和页面加载时间。
可编辑性:SVG图像可以通过文本编辑器进行编辑和修改,可以添加、删除或修改图形元素和属性,使其更具交互性和动态性。 - 动画和交互性:SVG支持动画和交互效果,可以通过CSS和JavaScript来实现图形的动态变化、交互操作和响应。
- 可搜索和可索引:由于SVG图像是基于文本的,搜索引擎可以读取和索引其中的文本内容,从而提高网页的可搜索性和可访问性。
SVG广泛应用于Web开发、数据可视化、图标设计、动画制作等领域,它提供了一种灵活、可扩展和高质量的图形解决方案。
WebP
WebP 是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式 VP8。它像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片,集多种图片文件格式的优点于一身。
WebP 最初在 2010 年发布,目标是减少文件大小,但达到和 JPEG 格式相同的图片质量,希望能够减少图片档在网络上的发送时间。根据 Google 较早的测试,WebP 的无损压缩比网络上找到的 PNG 档少了 45%的文件大小,即使这些 PNG 档在使用 pngcrush 和 PNGOUT 处理过,WebP 还是可以减少 28%的文件大小。
虽然 webP 有诸多优点,但是它不能完全替代 JPEG 和 PNG,因为浏览器对 WebP 支持并不普遍。特别是移动端 IOS 系统基本不支持。
AVIF
目前最新的图片格式是AVIF(AV1 Image File Format)。AVIF是一种基于AV1视频编解码器的图像格式,它使用先进的压缩算法来提供更高的图像质量和更小的文件大小。
AVIF格式具有以下优点:
- 更高的压缩效率:相比于JPEG和WebP等传统的图像格式,AVIF可以提供更高的压缩效率,从而在相同的图像质量下减小文件大小。
- 更好的图像质量:AVIF支持更高的色彩深度和更广的色域范围,可以提供更好的图像质量和更准确的颜色表示。
- 动态范围:AVIF支持高动态范围(HDR)图像,可以呈现更丰富的色彩和更高的对比度。
- 透明度支持:与WebP和PNG等格式类似,AVIF也支持透明度通道,可以实现图像的透明效果。
尽管AVIF具有许多优点,但由于它是一种相对较新的格式,目前还不被所有的浏览器和图像编辑软件广泛支持
。因此,在使用AVIF格式时,需要确保目标平台和设备支持该格式,或者提供备用的图像格式作为兼容性方案。
图片压缩
一张图片加载过程
1.发起请求:当浏览器解析HTML文档时,遇到标签或CSS中的background-image属性时,会发起对图片资源的请求。浏览器会根据src属性或url()函数中指定的图片路径,向服务器发送请求。
2.服务器响应:服务器接收到浏览器的请求后,会返回对应的图片资源。服务器的响应时间取决于网络连接的速度和服务器的性能。
3.下载图片:一旦浏览器接收到服务器的响应,它会开始下载图片。下载过程中,浏览器会根据网络状况和带宽限制,以及服务器的响应速度,逐步接收图片的数据。
4. 图片解码:当图片的数据完全下载到浏览器后,浏览器会对图片进行解码。解码过程将图像数据转换为像素,以便在屏幕上显示。
5.图片渲染:一旦图片解码完成,浏览器会将像素数据渲染到屏幕上的相应位置。图片的渲染过程包括将像素绘制到屏幕上的像素点,并根据CSS样式和布局进行相应的调整和显示。
加载完成:当图片渲染完成后,浏览器会触发load事件,表示图片加载完成。此时,可以通过JavaScript等方式对图片进行进一步的操作。
需要注意的是,图片加载过程中可能会受到网络状况、服务器响应速度、图片大小等因素的影响。优化图片加载的方法包括使用适当的图片格式、压缩图片大小、使用懒加载等技术,以提高页面加载速度和用户体验。
图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,有部分图片达到几百 kB,甚至 2M,直接导致了加载时间过长。所以对于体积过大的图片,在保持图片在可接受的清晰度范围内可适当对图片大小进行压缩。
图片压缩又分为有损压缩
和无损压缩
。
有损压缩
有损压缩指在压缩文件大小的过程中,损失了一部分图片的信息,也即降低了图片的质量(即图片被压糊了),并且这种损失是不可逆的。常见的有损压缩手段是按照一定的算法将临近的像素点进行合并。压缩算法不会对图片所有的数据进行编码压缩,而是在压缩的时候,去除了人眼无法识别的图片细节。因此有损压缩可以在同等图片质量的情况下大幅降低图片的体积。例如 jpg 格式的图片使用的就是有损压缩。
无损压缩
无损压缩指的是在压缩图片的过程中,图片的质量没有任何损耗。我们任何时候都可以从无损压缩过的图片中恢复出原来的信息。压缩算法对图片的所有的数据进行编码压缩,能在保证图片的质量的同时降低图片的体积。例如 png、gif 使用的就是无损压缩。
工具压缩
- tinypng 免费、批量、速度块
- 智图压缩 ,免费、批量、好用
- squoosh 在线图片压缩工具
- compressor 支持 JPG、PNG、SVG、GIF
webpack 压缩
工程化的项目可以在 webpack 里面配置 image-webpack-loader 进行图片压缩
- 安装依赖
npm install --save-dev image-webpack-loader
- 配置 webpack
module.exports = {
...
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash:7].[ext]'
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 50,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.5, 0.65],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: { // 不支持WEBP就不要写这一项
quality: 75
},
},
},
],
},
],
},
}
至于要不要使用插件自动压缩就见仁见智了,因为有些 UI 和产品会说压缩出来的效果图片不是他们想要的。
使用雪碧图
雪碧图,CSS Sprites,国内也叫 CSS 精灵,是一种 CSS 图像合成技术,主要用于小图片显示。
浏览器请求资源的时候,同源域名请求资源的时候有最大并发限制,chrome 为 6 个,就比如你的页面上有 10 个相同 CDN 域名小图片,那么需要发起 10 次请求去拉取,分两次并发。第一次并发请求回来后,发起第二次并发。如果你把 10 个小图片合并为一张大图片的画,那么只用一次请求即可拉取下来 10 个小图片的资源。减少服务器压力,减少并发,减少请求次数。
优点:
把诸多小图片合成一张大图,利用 backround-position 属性值来确定图片呈现的位置,可以有效的较少请求个数,而且,而不影响开发体验,使用构建插件可以做到对开发者透明。适用于页面图片多且丰富的场景。
缺点:
生成的图片体积较大,减少请求个数同时也增加了图片大小,不合理拆分将不利于并行加载。
合成雪碧图
在 webpack 中,有相应的插件提供了自动合成雪碧图的功能并且可以自动生成对应的样式文件—— webpack-spritesmith,使用方法如下:
var path = require('path')
var SpritesmithPlugin = require('webpack-spritesmith')
module.exports = {
// ...
plugins: [
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, 'src/ico'),
glob: '*.png',
},
target: {
image: path.resolve(__dirname, 'src/spritesmith-generated/sprite.png'),
css: path.resolve(__dirname, 'src/spritesmith-generated/sprite.styl'),
},
apiOptions: {
cssImageRef: '~sprite.png',
},
}),
],
}
通过上面配置就能将 src/ico 目录下的所有 png 文件合成雪碧图,并且输出到对应目录,同时还可以生成对应的样式文件,样式文件的语法会根据你配置的样式文件的后缀动态生成。
使用 iconfont
iconfont(字体图标),即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
优点
- 像使用字体一样,设置大小、颜色及其他样式,不失真
- 轻量,易修改
- 有效减少 HTTP 请求次数
推荐使用阿里的字体图标库:iconfont
使用 base64 格式
**原理:**将图片转换为 base64 编码字符串 inline 到页面或 css 中。
优点
-
提升性能: 网页上的每一个图片,都是需要消耗一个 http 请求下载而来的, 图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,base64 可以随着 HTML 的下载同时下载到本地.减少 https 请求。
-
加密: 让用户一眼看不出图片内容 , 只能看到编码。
-
方便引用: 在多个文件同时使用某些图片时, 可以把图片转为 base64 格式的文件, 把样式放在全局中, 比如 common.css, 以后在用的时候就可以直接加类名, 二不需要多层找文件路径, 会提升效率
但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。
base64 化最常见的就是在 url-loader 中使用。
module.exports = {
...
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240,
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
}
},
],
},
}
这样就能将项目中小于 10kb 的图片转化为 base64 应用到页面中
使用 css 代替图片
比如实现修饰效果,如半透明、边框、圆角、阴影、渐变等,在当前主流浏览器中都可以用 CSS 达成,这样能减少图片的请求,达到优化的目的。
缺点:
- 受限于 css 的浏览器的兼容性
- 对于较复杂的图案就无能为力了,写也麻烦,开发成本大
使用 CDN 图片
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。
举个简单的例子:
以前买火车票大家都只能去火车站买,后来我们买火车票就可以在楼下的火车票代售点买了。
基本原理
CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
基本思路
CND 的基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN 系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet 网络拥挤的状况,提高用户访问网站的响应速度。
CDN 的优势
- CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源站的负载。
图片懒加载
懒加载是一种网页性能优化的方式,它能极大的提升用户体验。图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以进入页面的时候,只请求可视区域的图片资源。
总结出来就是:
- 减少资源的加载,页面启动只加载首屏的图片,这样能明显减少了服务器的压力和流量,也能够减小浏览器的负担。
- 防止并发加载的资源过多而阻塞 js 的加载,影响整个网站的启动,影响用户体验
- 浪费用户的流量,有些用户并不想全部看完,全部加载会耗费大量流量。
原理
图片懒加载的原理就是暂时不设置图片的 src 属性,而是将图片的 url 隐藏起来,比如先写在 data-src 里面,等当前图片是否到了可视区域再将图片真实的 url 放进 src 属性里面,从而实现图片的延迟加载。
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
// 可以使用节流优化一下
window.addEventListener('scroll', lazyload)
通过上面例子的实现,我们要实现懒加载都需要去监听 scroll 事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算 scrollTop,offsetHeight 等属性,有没有简单的不需要计算这些属性的方式呢,答案是有的—IntersectionObserver
IntersectionObserver
IntersectionObserver是一个用于监测目标元素与其祖先元素或视窗(viewport)之间交叉状态的API。它提供了一种异步的、高性能的方法来观察元素是否进入或离开视窗,或者与其祖先元素发生交叉。
使用IntersectionObserver可以实现以下功能:
- 监测元素的可见性:可以观察元素是否进入或离开视窗,以便在元素可见或不可见时执行相应的操作。
- 实现懒加载:可以延迟加载图片、视频或其他资源,只有当它们进入视窗时才进行加载,从而提高页面加载速度和性能。
- 实现无限滚动:可以在滚动时动态加载更多的内容,当滚动到特定位置时触发加载新数据的操作。
- 监测元素与其祖先元素的交叉状态:可以观察元素与其祖先元素之间的交叉状态,例如元素是否完全进入祖先元素、部分进入或完全离开。
使用IntersectionObserver的优势包括:
- 异步执行:IntersectionObserver使用异步的方式进行观察,不会阻塞主线程,从而提高页面的响应性能。
- 高性能:IntersectionObserver使用浏览器内部的优化机制,可以高效地检测元素的交叉状态,减少不必要的计算和重复操作。
- 简化代码:相比于传统的滚动事件监听和计算元素位置的方式,IntersectionObserver提供了更简洁、易于使用的API,减少了开发者的工作量。
总之,IntersectionObserver是一个强大的API,可以帮助开发者实现各种与元素可见性和交叉状态相关的功能,提升用户体验和页面性能。
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
图片预加载
图片预加载,是指在一些需要展示大量图片的网站,将图片提前加载到本地缓存中,从而提升用户体验。
常用的方式有两种,一种是隐藏在 css 的 background 的 url 属性里面,一种是通过 javascript 的 Image 对象设置实例对象的 src 属性实现图片的预加载。
1. 用 CSS 和 JavaScript 实现预加载
<!DOCTYPE html>
<html>
<head>
<style>
.preload-image {
display: none;
background-image: url('https://example.com/image.jpg');
}
</style>
<script>
function preloadImage(url) {
const img = new Image();
img.src = url;
}
// 预加载图片
preloadImage('https://example.com/image.jpg');
</script>
</head>
<body>
<div class="preload-image"></div>
</body>
</html>
在上面的代码中,我们首先在 标签中定义了一个 CSS 样式,使用 .preload-image 类来设置预加载图片的背景图。这个元素默认是隐藏的,因为我们使用了 display: none;。
然后,在
2. 使用 JavaScript 实现预加载
function preloadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('图片加载失败'));
img.src = url;
});
}
// 预加载图片的 URL 数组
const imageUrls = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
'https://example.com/image3.jpg'
];
// 存储预加载的图片对象
const preloadedImages = [];
// 遍历图片 URL 数组,逐个进行预加载
imageUrls.forEach(url => {
preloadImage(url)
.then(img => {
preloadedImages.push(img);
console.log(`图片 ${url} 预加载完成`);
})
.catch(error => {
console.error(`图片 ${url} 预加载失败: ${error.message}`);
});
});
在上面的代码中,preloadImage 函数接受一个图片的 URL,返回一个 Promise 对象。在 Promise 的 resolve 回调中,我们将加载完成的图片对象传递给 resolve 函数;在 reject 回调中,我们将加载失败的错误信息传递给 reject 函数。
然后,我们定义了一个图片 URL 数组 imageUrls,以及一个用于存储预加载图片对象的数组 preloadedImages。
接下来,我们使用 forEach 方法遍历图片 URL 数组,并逐个调用 preloadImage 函数进行预加载。在 then 回调中,我们将加载完成的图片对象存储到 preloadedImages 数组中,并输出预加载完成的消息;在 catch 回调中,我们输出加载失败的消息。
这样,当所有图片都预加载完成后,preloadedImages 数组中将包含所有预加载的图片对象。
响应式图片加载
什么是响应式图片加载?其实就是在不同分辨率的设备上显示不同尺寸的图片,避免资源的浪费。
常用的方法就是 css3 的媒体查询(media query)。
@media screen and (min-width: 1200px) {
img {
background-image: url('1.png');
}
}
@media screen and (min-width: 992px) {
img {
background-image: url('2.png');
}
}
@media screen and (min-width: 768px) {
img {
background-image: url('3.png');
}
}
@media screen and (min-width: 480px) {
img {
background-image: url('4.png');
}
}
此外,还可以使用 HTML5 的 picture
属性进行响应式处理。方法如下:
- 创建 picture 标签。
- 放置多个 source 标签,以指定不同的图像文件名,进而根据不同的条件进行加载。
- 添加一个回退的元素
<picture>
<source srcset="src/img/l.png" media="(min-width: 1200px)" />
<source srcset="src/img/2.png" media="(min-width: 992px)" />
<source srcset="src/img/4.png" media="(min-width: 768px)" />
<img src="src/img/4.png" />
</picture>
需要注意的是:现在很多浏览器对于 picture 这个标签还不支持,使用的时候需要加以注意。
渐进式图片
渐进式图片的意思是在高画质图像加载完之前会先显示低画质版本。低画质版本由于画质低、压缩率高,尺寸很小,加载很快。在两者之间我们也可以根据需要显示不同画质的版本。
渐进式图片可以让用户产生图片加载变快的印象。用户不再盯着一片空白区域等待图片加载,而能看到图像变得越来越清晰,这样对用户体验也是友好的。
骨架屏技术也是类似的原理。
总结
- 选择合适的图片格式和压缩大图,可从根源上截图大图加载过慢的问题。
- 使用雪碧图,iconfont,base64,css 代替图片等可减少图片 http 请求,提高页面加载速度。
- 使用 CDN 图片可达到分流的效果,减少服务券压力。
- 图片懒加载,预加载,渐进式图片等可不同程度减少白屏时间,提高产品体验。
6. DNS 预解析是什么?怎么实现?
DNS优化
在介绍dns-prefetch之前,先要提下当前对于DNS优化主流方法。
一般来说,一次DNS解析需要耗费 20-120ms,所以为了优化DNS,我们可以考虑两个方向:
- 减少DNS请求次数
- 缩短DNS解析时间dns-prefetch
什么是dns-prefetch?
dns-prefetch(DNS预获取)是前端网络性能优化的一种措施。它根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS解析时间,进而提高网站的访问速度。
为什么要用dns-prefetch?
每当浏览器从(第三方)服务器发送一次请求时,都要先通过DNS解析将该跨域域名解析为 IP地址,然后浏览器才能发出请求。
如果某一时间内,有多个请求都发送给同一个服务器,那么DNS解析会多次并且重复触发。这样会导致整体的网页加载有延迟的情况。
我们知道,虽然DNS解析占用不了多大带宽,但是它会产生很高的延迟,尤其是对于移动网络会更为明显。
因此,为了减少DNS解析产生的延迟,我们可以通过dns-prefetch预解析技术有效地缩短DNS解析时间。
<link rel="dns-prefetch" href="https://baidu.com/">
dns-prefetch背后原理
当浏览器访问一个域名的时候,需要解析一次DNS,获得对应域名的ip地址。 在解析过程中,按照:
- 浏览器缓存
- 系统缓存
- 路由器缓存
- ISP(运营商)DNS缓存
- 根域名服务器
- 顶级域名服务器
- 主域名服务器
的顺序逐步读取缓存,直到拿到IP地址。
dns-prefetch就是在将解析后的IP缓存在系统中。
这样,dns-prefetch就有效地缩短了DNS解析时间。因为,在本地操作系统做了DNS缓存,使得DNS在解析的过程中,提前在系统缓存中找到了对应IP。
这样一来, 后续的解析步骤就不用执行了,进而也就缩短了DNS解析时间。
假如浏览器首次将一个域名解析为IP地址,并缓存至操作系统,那么下一次DNS解析时间可以低至0-1ms
。
倘若结果不缓存在系统,那么就需要读取路由器的缓存,进而后续的解析时间最小也要约15ms
。
如果路由器缓存也不存在,则需要读取ISP(运营商)DNS缓存,一般像taobao.com、baidu.com这些常见的域名,读取ISP(运营商)DNS缓存需要的时间在80-120ms
,如果是不常见的域名,平均需要200-300ms
。
一般来说,大部分的网站到运营商这块都能找到IP。
那也就是说,dns-prefetch可以给DNS解析过程带来15-300ms
的提升,尤其是一些大量引用很多其他域名资源的网站,提升效果就更加明显了
浏览器DNS缓存与dns-prefetch
现代浏览器为了优化DNS解析,也设有了浏览器DNS缓存。
每当在首次DNS解析后会对其IP进行缓存。至于缓存时长,每种浏览器都不一样,比如Chrome的过期时间是1分钟,在这个期限内不会重新请求DNS。
Tip:
每当Chrome浏览器启动的时候,就会自动的快速解析浏览器最近一次启动时记录的前10个域名。所以经常访问的网址就不存在DNS解析的延迟,进而打开速度更快。
而dns-prefetch 相当于在浏览器缓存之后,在本地操作系统中做了DNS缓存,个人理解,为的是给浏览器缓存做保障,一般来说,DNS在系统的缓存时间是大于浏览器的。
浏览器与系统DNS缓存时间
TTL(Time-To-Live),就是一条域名解析记录在DNS服务器中的存留时间
-
浏览器DNS缓存的时间跟DNS服务器返回的TTL值无关, 它的缓存时间取决于浏览器自身设置。
-
系统缓存会参考DNS服务器响应的TTL值,但是不完全等于TTL值。
国内和国际上很多平台的TTL值都是以秒为单位的,很多的默认值都是3600,也就是默认缓存1小时。
dns-prefetch缺点
dns-prefetch最大的缺点就是使用它太多。
过多的预获取会导致过量的DNS解析,对网络是一种负担。
最佳实践
请记住以下三点:
-
dns-prefetch 仅对跨域域上的 DNS查找有效
,因此请避免使用它来指向相同域。
DNS预获取只对跨域域名有效是因为当浏览器加载网页时,它会自动解析同一域名下的资源的DNS记录。因此,使用DNS预获取来指向相同域名下的资源是没有意义的,因为浏览器已经在加载网页时自动解析了这些资源的DNS记录。
另外,使用DNS预获取也可能会增加额外的网络请求,从而增加网页加载时间。因此,如果要使用DNS预获取,最好将其用于跨域域名,以最大程度地提高网页加载性能。 -
除了使用HTML的标签来指定DNS预获取,还可以通过HTTP链接字段将其指定为HTTP标头。这可以通过在HTTP响应头中添加Link字段来实现。
下面是一个示例,展示如何在HTTP响应头中使用Link字段来指定DNS预获取:
Link: </example-domain.com>; rel=dns-prefetch
在上面的示例中,</example-domain.com>是要预获取的域名,rel=dns-prefetch表示这是一个DNS预获取的链接关系。
通过将DNS预获取指定为HTTP标头,可以在浏览器请求网页时一起获取DNS记录,从而加快网页加载速度。这种方法适用于所有资源,不仅仅是HTML页面。
需要注意的是,使用HTTP链接字段来指定DNS预获取需要在服务器端进行配置,以确保正确的HTTP响应头被发送到浏览器。
- 考虑将 dns-prefetch 与 preconnect(预连接)提示配对。
将DNS预获取(dns-prefetch)与预连接(preconnect)提示配对是一种优化网页加载性能的常见做法。这两个技术可以一起使用,以减少网络延迟并加快网页加载速度。
DNS预获取用于在浏览器加载网页时提前解析域名的DNS记录,以减少DNS查找的延迟。而预连接则用于在浏览器加载网页时提前建立与服务器的连接,以减少建立连接的延迟。
通过将这两个技术配对使用,可以在浏览器加载网页时同时进行DNS解析和建立连接的操作,从而进一步减少加载时间。
下面是一个示例,展示如何在HTML中同时使用DNS预获取和预连接:
<link rel="dns-prefetch" href="//example-domain.com">
<link rel="preconnect" href="//example-domain.com">
在上面的示例中,dns-prefetch指定了要预获取的域名,preconnect指定了要预连接的域名。通过将这两个标签添加到HTML中,浏览器将在加载网页时同时进行DNS预获取和预连接的操作。
需要注意的是,不是所有的域名都适合进行DNS预获取和预连接。只有在确实需要加载来自特定域名的资源时,才应该使用这些优化技术。此外,使用预连接时还需要确保服务器支持HTTP/2或HTTP/3协议,以获得最佳效果。
7. 在 React 中可以做哪些性能优化?
-
使用 shouldComponentUpdate 避免不需要的渲染,但是如果对 props 和 state 做深比较,代价很大,所以需要根据业务进行些取舍;在有子组件的情况下,为了避免子组件的重复渲染,可以通过父组件来判断子组件是否需要 PureRender。
-
将 props 设置为数组或对象:每次调用 React 组件都会创建新组件,就算传入的数组或对象的值没有改变,他们的引用地址也会发生改变,比如,如果按照如下的写法,那么每次渲染时 style 都是一个新对象
// 不推荐
<button style={{ color: 'red' }} />
// 推荐
const style = { color: 'red' }
<button style={style} />
// 不推荐
<button style={this.props.style || {} } />
// 推荐
const defaultStyle = {}
<button style={this.props.style || defaultStyle } />
将props设置为数组或对象的好处是可以确保每次渲染时都创建一个新的对象,即使传入的数组或对象的值没有改变。这样做的好处有以下几点:
- 避免副作用:在React中,组件的渲染应该是纯粹的,不应该有副作用。如果在组件的渲染过程中修改了传入的props对象,可能会导致意外的行为和难以调试的问题。通过每次渲染时创建一个新的对象,可以确保props对象的不可变性,避免副作用。
- 引用比较:React在进行虚拟DOM的比较时,会使用浅比较(shallow comparison)来判断是否需要重新渲染组件。如果传入的props对象是同一个引用地址,即使对象的值没有改变,React也无法判断是否需要重新渲染组件。通过每次渲染时创建一个新的对象,可以确保每次都有一个新的引用地址,从而使React能够正确地判断是否需要重新渲染组件。
- 性能优化:虽然每次渲染时创建新的对象可能会增加一些开销,但在大多数情况下,这种开销是可以忽略不计的。而通过确保props对象的不可变性,可以帮助React进行更精确的比较和优化,从而提高组件的性能。
需要注意的是,如果在父组件中频繁地创建新的数组或对象作为props传递给子组件,可能会导致子组件频繁地重新渲染。在这种情况下,可以考虑使用React.memo或shouldComponentUpdate等优化技术来避免不必要的重新渲染。
- 将函数的绑定移动到构造函数内:可以避免每次都绑定事件。
- 使用 immutable 不可变数据,在我们项目中使用引用类型时,为了避免对原始数据的影响,一般建议使用 shallowCopy 和 deepCopy 对数据进行处理,但是这样会造成 CPU 和 内存的浪费,所以推荐使用 immutable,优点如下
- 降低了“可变”带来的复杂度
- 节省内存,immutable 使用结构共享尽量复用内存,没有被引用的对象会被垃圾回收
- 可以更好的做撤销/重做,复制/粘贴,时间旅行
- 不会有并发问题(因为数据本身就是不可变的)
- 拥抱函数式编程
- 给子组件设置一个唯一的 key,因为在 diff 算法中,会用 key 作为唯一标识优化渲染
8. 前端性能优化指标有哪些?怎么进行性能检测?
1. 概述
web性能说简单点就是网站打开速度快不快,页面中的动画够不够流畅,表单提交的速度是否够快,列表滚动页面切换是否卡顿。性能优化就是让网站变得快。
在MDN上对web性能的定义是网站或应用程序的客观度量和可感知的用户体验。比如减少页面加载事件(减少文件体积,减少HTTP请求,使用预加载),让网站尽快可用(懒加载或者分片加载),平滑的交互性(使用CSS替代JS动画,减少UI重绘),感知表现(加载动画,loading等给用户感觉快),性能测定(性能指标,性能测试,性能监控以便持续优化,毕竟性能优化是个持续的过程)。
页面性能关乎到用户的留存,网站的转换率,用户体验和网站的传播,甚至影响搜索排名遭到用户投诉,当然也会影响开发的效率。
2. 性能指标
进行性能优化之前首先要知道要在哪些方面做性能优化。
首先需要了解性能指标,多快的速度才算快呢?可以使用专业的工具可量化的评估出网站或应用的性能表现。
立足于网站页面响应的生命周期,分析出造成较差性能表现的原因,最后进行技术改造,可行性分析等具体的优化措施,持续迭代优化就可以了。
事实上性能是相对的,他并不是绝对的概念。对于一个用户而言在不同的网络环境下访问页面的速度可能是不同的。即使相同的网站在懒加载的情况下也会显得快。
在讨论性能的时候精确地,可量化的指标是很重要的。但是仅仅因为一个度量标准是基于客观准备并且可以定量的度量的,并不一定意味这些度量是有用的。对于Web开发人员来说,如何恒量一个Web页面的性能一直都是一个难题。
最初,开发人员使用Time to To Byte。DomContentLoaded和Load这些恒量文档加载进度的指标,但他们不能直接反应用户视觉体验。
为了恒量用户视觉体验,Web标准中定义了一些性能指标。这些性能指标被各大浏览器标准化实现,例如First Paint和First Contentful Paint。
还有一些由Web孵化器社区组提出的性能指标,如Largest COntentful Paint, Time to Interactive, First Input Delay, First CPU Idle。
另外还有Google提出的First Meaningful Paint, Speed Index。
百度提出的First Screen Paint。
这些指标之间并不是毫无关联,而是在以用户为中心的目标中不断演进出来的,有的已经不再建议使用,有的被各种测试工具实现,有的则可以作为通用标准可用于生产环境测量的API。
3. RAIL性能模型
RAIL是Response,Animation,Idle和Load的首字母缩写,是一种由Google Chrome团队于2015年提出的性能模型,用于提升浏览器的用户体验和性能。
RAIL模型的理念是以用户为中心,最终目标并不是让你的网站在任何特定设备上都能运行很快,而是使用户满意。
Response: 应该尽可能快速的响应用户的操作,应在在100ms以内响应用户输入。
Animation: 在展示动画的时候,每一帧应该以16ms进行渲染,这样可以保持动画效果的一致性,并且避免卡顿。
Idle: 当使用js主线程的时候,应该把任务划分到执行时间小于50ms的片段中去,这样可以释放线程以进行用户交互。50ms为单位是为了保证用户在发生操作的100ms内做出响应。
要使网站响应迅速,动画流畅,通常都需要较长的处理时间,但以用户为中心来看待性能问题,就会发现并非所有工作都需要在响应和加载阶段完成,完全可以利用浏览器的空闲时间处理可延迟的任务,只要让用户感受不到延迟即可。利用空闲时间处理延迟可减少预加载的数据大小,以保证网站或应用快速完成加载。
Load: 应该在小于1s的时间内加载完成你的网站,并可以进行用户交互。根据网络条件和硬件的不同,用户对性能延迟的理解也有所不同,在3G网络需要花费更多的时间,5s是一个更现实的目标。
基于用户体验的性能指标其中包括一下几个比较重要的性能指标。
1. FCP (First Contentful Paint)
首次内容绘制,浏览器首次绘制来自DOM的内容的时间,内容必须包括文本,图片,非白色的canvas或svg,也包括带有正在加载中的web字体文本。这是用户第一次看到的内容。
2. LCP (Largest Contentful Paint)
最大内容绘制,可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户的可见时间。img图片,video元素的封面,通过url加载到的北京,文本节点等,为了提供更好的用户体验,网站应该在2.5s以内或者更短的时间最大内容绘制。
3. FID (First Input Delay)
首次输入延迟,从用户第一次与页面进行交互到浏览器实际能够响应该交互的时间,输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户,发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大量计算的JavaScript。
4. TTI (Time to Interactive)
网页第一次完全达到可交互状态的时间点,浏览器已经可以持续的响应用户的输入,完全达到可交互的状态的时间是在最后一个长任务完成的时间,并且在随后的5s内网络和主线程是空闲的。从定义上来看,中文名称叫持续可交互时间或可流畅交互时间更合适。
5. TBT (Total Block Time)
总阻塞时间,度量了FCP和TTI之间的总时间,在该时间范围内,主线程被阻塞足够长的时间以防止输入响应。只要存在长任务,该主线程就会被视为阻塞,该任务在主线程上运行超过50毫秒。
线程阻塞是因为浏览器无法中断正在进行的任务,因此如果用户确实在较长的任务中间与页面进行交互,则浏览器必须等待任务完成才能响应。
6. CLS (Cumulative Layout Shift)
累计布局位移,CLS会测量在页面整个生命周期中发生的每个意外的布局移位的所有单独布局移位分数的总和,他是一种保证页面的视觉稳定性从而提升用户体验的指标方案。
用人话来说就是当点击页面中的某个元素的时候,突然布局变了,手指点到了其它位置。比如想点击页面的链接,突然出现了一个banner。这种情况可能是因为尺寸未知的图像或者视频。(页面抖动)
4. Web Vitals
这也是谷歌指定的web性能指标标准, 谷歌认为之前的标准太复杂,指标太多了,在2020年重新进行了梳理,简化到了三个。加载性能LCP,交互性FID,视觉稳定性CLS。只需要做好这三个,网站的性能基本上就可以了。
测量Web Vitals的工具有很多,比如Lighthouse,web-vitals,浏览器插件web vitals。
1. Web-Vitals
// npm install web-vitals -g
import { getLCP, getFID, getCLS } from 'web-vitals';
getLCP(conole.log)
getFID(conole.log)
getCLS(conole.log)
2. 浏览器插件
谷歌浏览器可以直接在插件市场中查找并且安装web vitals。安装完成之后浏览器的右上角会多出插件标志,点击就会显示页面的性能指标。
如图:
5. 性能测试
性能检测是作为性能优化过程中的一环,他的目的通常是给后续优化工作提供指导方向,参考基线以及千户对比的依据。性能检测并不是一次性执行结束后就完成的工作,他会在检测,记录和改进的迭代过程中不断重复。来协助网站的性能优化不断接近期望的效果。
1. Lighthouse(灯塔)
Lighthouse是谷歌开发并开源的web性能测试工具,用于改进网络应用的质量,可以将其作为一个Chrome扩展程序运行,或从命令行运行。只需要为其提供一个需要审查的地址,Lighthouse就会对页面进行一连串的测试,生成一个有关页面性能的报告。
在浏览器的调试工具中默认就存在lighthouse选项,只需要切换至lighthouse,在右侧的选项区选中需要的选项。点击生成报告。
2. WebPageTest
在线web性能测试工具(https://www.webpagetest.org), 提供多地点测试。他只能测试已经发布了的网站。输入需要测试的网页地址,点击start test按钮就开始测试了,可以选择测试地理位置,测试的浏览器等。
6. Chrome DevTools
1. 浏览器的任务管理器
可以查看当前Chrome浏览器中,所有进程关于GPU,网络和内存空间的使用情况,这些进程包括当前打开的各个标签页,安装的各种扩展插件,以及GPU,网络,渲染等浏览器的默认进程,通过监控这些数据,可以定位可能存在内存泄露或网络资源加载异常的问题进程。
更多工具 -> 任务管理器
可以看到所有进行的进程,可以看到内存占用网络消耗。
2. Network网络分析
Network面板是一个常被用到的工具,通过它可以获取到网站所有资源的请求情况,包括加载时间,尺寸大小,优先级设置以及HTTP缓存等信息。可以帮助开发者发现可能由于未进行有效压缩而导致资源尺寸过大的问题,未配置缓存策略导致二次请求加载时间过长的问题。
- 缓存测试
Disable cache - 吞吐测试,模拟网速
- Coverage
监控并统计出网站应用运行过程中代码执行的覆盖率情况。
统计的对象是JavaScript脚本文件与css样式文件,统计结果主要包括文件的字节大小,执行过程中已覆盖的代码字节数,可视化的覆盖率条形图。
根据执行结果可以发现到底哪些尺寸较大的代码文件覆盖率较低,这就意味着这些代码文件中可能存在较多的无用代码。
Ctrl + shift + p 搜索 coverage 就会显示出来。
可以看到第一个文件有58%没有被使用到,第二个有95.2%没有使用到。 - Memory 面板
主要用于分析内存占用情况,如果出现内存泄露,那么就可能带来网站崩溃的后果。
为了更细致和准确的监控应用网站当前的内存使用情况,Chrome浏览器提供Memory面板,可以快速生成当前的堆内存快照。
结束后可以查看到内存占用大小,就可以对对应的模块进行优化。
- Performance
使用Performance面板主要对网站应用的运行时性能表现进行检测和分析,包括页面的每秒帧数,CPU的消耗和各种请求花费的时间。
点击开始后等待两三秒就可以停止了。
这里面就可以统计出网站的信息。 - FPS
另一个非常方便的工具是FPS计数,可以在页面运行时提供对FPS的实时估计。
Ctrl + Shift + P 输入 fps 选择显示渲染。就会在浏览器中出现监控面板。
还可以使用性能监视器,这是一个事实的监视器。
Ctrl + Shift + P 输入 monitor