当前位置: 首页 > Web前端 > CSS

鸟瞰前端,再谈性能优化

时间:2023-03-30 14:29:34 CSS

欢迎来到腾讯云技术社区,获取更多腾讯海量技术实践的干货~简介:本人从事前端至今6+年,从最开始的美术到重构,再到偏向js逻辑开发的前端开发,一直在前端行业探索和学习。下面我就系统的梳理一下我这些年的心得,写一篇关于性能优化的专题文章。希望对大家有所帮助。我们也欢迎您提出意见和建议。作者:刘永刚前端工程师是近5-6年才逐渐被互联网公司重视的职业。可以说是一个新兴的行业。我会用一张简单的思维导图带大家回顾一下前端技术的发展。历史与展望:1.0时代无话可说。在html和css一统天下的时代,那个时候你可以用js开发一个计算器,你就牛逼了。2.0时代是最好的时代。新技术、新理念百花齐放。堪称前端产业革命。前端人员的地位得到了充分的认可,门槛也得到了一定程度的提高。前端性能优化涉及到对服务器、协议和宿主环境本身有比较深刻的理解。目前业界主要总结了前端性能优化的35条黄金军规(http://www.cnblogs.com/siqi/p...)供参考。今天想看一下目前前端性能优化的一些实践以及做这件事的出发点。文章的初衷主要是回顾一些性能优化的基础知识和梳理系统,没有对具体的技术点进行深入分析,至此,如果我的理解有误,欢迎大家提出建议。在介绍正题之前,先从一个老生常谈的话题说起:“从用户输入URL到页面显示给用户浏览器客户端的过程中发生了什么?”下面用一张图简单说明几个步骤:网页优化的目标是如何让用户更快、更容易使用、更流畅地使用我们的服务,对于前端开发来说,就是如何让我们的资源更小、更精简,内容呈现更早,交互更人性化。Web性能优化中有一个普遍接受的第28条原则,即资源经过处理后从服务端发送到客户端的浏览器(上图中的第6步)大约占整个过程的20%,也就是说的服务器端可优化空间的效率提升不会很明显。前端性能优化已经成为web性能优化的重点考虑领域。我将从以下几个维度做自己的思考(涉及35条军规必有重叠)和总结:1.浏览器宿主环境1.突破单线程解析渲染阻塞的限制。浏览器是单线程的解析方式,对从服务器获取的html文本进行解析渲染。加载脚本资源造成阻塞,加载脚本也会阻塞后续DOM结构的解析,导致页面空白时间增加。雅虎的35条军规之一就是样式文件放在头部,脚本文件放在DOM节点的末尾。,以减少阻塞。这里再对脚本文件做几个优化:不需要DOM操作的js脚本(主要考虑是需要操作DOM的脚本往往需要获取一些样式信息)可以通过动态创建脚本的方式来加载,动态加载的脚本呢不阻塞后续资源的加载。脚本文件加载可以标上defer或者async属性来防止阻塞(参考两者的区别)2.使用浏览器事件模型冒泡特性的事件冒泡特性(浏览器事件模型不清楚的自行搜索)我认为是最强大的设计之一,解决了开发者向DOM对象注册事件回调时,由于DOM模型解析不同步导致浏览器找不到对象的问题。浏览器事件注册有3个级别,DOM0级事件注册(使用DOM元素的onclick事件属性注册事件回调),DOM1级事件注册(使用DOM元素对象的onclickAPI对外注册事件回调),DOM2Level事件注册(使用DOM元素对象的addEventListner/attachEventAPI对外注册事件回调)。这里的性能优化建议是使用DOM2级别在目标DOM的父标签中注册回调(大部分框架统一在body标签中注册事件监听器),收集事件监听器入口,节省DOM节点引用开销。3.避免cookie性能bugCookie是前后端登录状态校验最常用的缓存方案。但是,鉴于浏览器在对同一域内任意资源的每次http请求中都会自动带上cookie信息,这里需要优化一下,因为css、js、image等资源的请求是不需要cookie信息的,这样会无缘无故浪费请求带宽(假设我们的cookie大小假设为10K,100个请求接近1M大小,高并发下,我们现在的网络带宽也是相当的负担)。无cookie性能优化方案的处理方式是将我们前端的css、js、图片资源部署在CDN异构静态资源服务器上。以我目前负责的香港跨境汇款为例。页面路径下的资源请求:CDN资源加载请求:对比CDN单独部署的资源请求,没有cookie信息。4.突破浏览器的并发连接数限制。浏览器的域而非页面的并发连接限制功能。domainhash的技术优化方案是将资源分域单独部署,但是由于分域太多会增加冗余DNS开销,所以这里的流量数不到3。目前我们的香港-菲律宾汇款业务只有两个域名需要单独部署,一个主站,一个CDN。我个人建议最好将CDN中的图片资源划分到一个单独的域名中进行部署。为什么要单独提取图像?会谈到。5.使用GPU硬件加速浏览器渲染。对于一些界面渲染过程比较耗时的情况,可以使用CSS3属性来开启GPU来加速我们DOM的渲染。启用非常简单。一般我使用-webkit-transform:translateZ(0)False3D属性来唤起系统GPU加速渲染功能。至于为什么会这样,我在这里做一个简单的解释:对于我们的浏览器来说,我们拿到我们的html文本字符串,开始按顺序解析成DOM树,并与解析后的CSS进行同步匹配,生成渲染图tree(与DOM树的节点没有一一对应关系,例如带有display:none的节点不会插入到渲染树中)图片来源:https://segmentfault.com/a/11...Browse渲染器用层表示渲染树的节点,让层堆叠在一起生成布局,有点像ps中层堆叠的概念(通过3D中的Firefox浏览器开发人员工具)。节点大小的任何变化都会引起布局的重排和重绘(重排和重绘是造成浏览器渲染性能损失最大的因素),但是有一种情况是CompositeLayers(复合层)直接交互给我们在GPU中使用了一个单独的compositor进程,它自身的变化不会引起其他layer位置的变化,不会引起重排和重绘。tranform3d属性可以悄悄地告诉我们的浏览器将元素解析作为一个复合层处理到一个单独的进程。注意:这里有个原则。我们不能滥用我们的加速,因为过多的硬件加速会占用更多的用户内存空间,消耗更多的电量。一般建议开启CSS3动画。二、http维度1.减少http请求次数a.常用解决方案css和脚本集成:gulp和webpack可以通过任务脚本轻松自动解决。目前,我们团队使用自研的前端构建工具和我们的Dust库来创建预发布资源打包任务的核心是gulp。csssprites雪碧图:将网站常用的一些小图整合成一张大图,通过样式中的background-position二维坐标定位找到自己的图片。这里有个原则。一般网站复用率高且不易更改的图标和图片,如按钮、小平铺背景图片等。font-icon字体图标:字体图标库的使用是一个很创新的方式,因为是矢量图,解决了位图像素放大和虚化的问题,体验很好,比同样的矢量图好用SVG,一个cssfont-family可以用作通常的字体设置。淘宝是国内这方面的先行者,有自己的一套非常开放的矢量图标库平台。淘宝本身很多小图标都是用字体图标显示的。图片base64编码传输:图片base64编码后,浏览器可以减少自己的http请求,但是由于自身的一些缺陷,不能被滥用(即使是小图片编码后也会有一大串字符,增加了我们的CSS体积,性能不会降低反而会增加),我的建议是对整个站点中常见的图标进行编码,或者对太小而无法集成到sprite图中的图标进行编码。当然,您可以权衡许多不同的场景。图片延迟加载:主要是减少首屏一次性图片的加载。具体方法是为图片或标签设置一个私有的内联属性data-image(当然也可以自己定义),用于存储目标图片的地址信息,监听浏览器的滚动事件,将图片放入当标签到达浏览器可见区域时,地址进入图片。src属性或作为标签样式的背景图像。淘宝首页的做法是用一个div做图片延迟加载,通过背景图显示最终图片。图片显示前:图片显示后:b.缓存机制协议缓存方案:使用http缓存协议头cache-control做304缓存,或者根据资源修改时间设置缓存方案,ETAG设置更精准。但是目前比较有效或者说极端的方法是使用max-expire-time,设置资源的最长缓存时间假设为1年的长缓存,使用不覆盖更新的方式进行更新是目前大公司的做法。这样,每次资源请求都只从客户端缓存中读取(状态:200,大小:来自缓存),而不是向服务器运行http请求以获得304状态。还是以淘宝首页图片长缓存截图为例:appCache应用缓存方案:离线应用缓存是h5提供的一种比较有效的离线应用方案,使用navigator.online,window.applicationCache对象,server.appcache(原.manifest)配置文件,以确保离线移动Web应用程序仍然可以照常使用。如果数据要离线,需要添加window.localStorage来保存离线数据。下面是访问离线应用的几个步骤:1.为需要离线缓存的页面的html标签设置manifest属性,并指定缓存配置文件cache.appcahe(可以设置任何扩展名,只要是在服务器端配置mime-type为text/cache-manifest)。2、创建上一步指定的cache.appcache配置文件,按照下图配置资源。3.配置server端配置文件扩展名映射的mime-type为text/cache-manifest。收入不多,有慢慢沦为弃儿的趋势。这里提出让大家权衡一下**PWA(ProgressiveWebApps)方案**:Google提出的一套新的离线web方案,使用manifest.json配置文件,window.serviceWorker对象实现类似如下的离线应用方案nativeapp体验,可以说是浏览器应用缓存的无形化升级方案(限于篇幅,这里不做介绍)。2.减少http数据请求的大小常用解决方案css、脚本、图片压缩:这些可以通过在gulp或webpack自动化脚本中定义脚本任务来完成。在服务器上开启gzip压缩:一般现在服务器都开启了gzip压缩,压缩率一般在30%以上,效果还是不错的。原图:Gzip压缩后:图片服务器动态响应方案:该方案对应引入了基于上述宿主环境维度的域哈希将图片资源部署在独立域名中的方案。图片资源在网站请求资源中是一个非常大的开销。以前可以在静态资源服务器中创建一个图片目录存放。随着网站服务的发展,图片不仅面临着多样化和高并发的压力,而且在移动端的wap站点中,更需要动态适配不同分辨率屏幕下的图片大小,以节省带宽。图片服务器的分离架构有一定的复杂性(如果考虑高并发下的容灾和缓存机制,那不亚于大型网站集群的搭建,这里推荐一篇文章给大家阅读),这里我们只讨论负责图片切割服务(简称图片切割服务器)功能的服务器功能。图片切割服务器对外提供restfulurl调用,比如http://www.xxx.com/xxx图片路径..."xxx图片路径"下的xxx图片按比例压缩为130*大小120并返回。这个服务在前端可以使用我们比较熟悉的node来创建,当然也可以用PHP来提供。b.页面切片预加载方案的性能优化。静态资源维度的最后一块内容是针对页面的。如何尽快输出页面模块,减少空白时间是一个思考点。facebook使用的BigPipe解决方案是一个很好的参考思路。淘宝对于首页也有相应的分片方案,将页面合理分块,在服务端和客户端之间建立相应的机制,让每个页面块在服务端并行。终端拼接完成,吐出。目前,我对这块还没有深入的了解。这里我只是提出bigPipe方案,供大家参考。3.TCP维度TCP连接中的3次握手和慢启动注定了连接通道的利用效率成为制约性能的一大因素。因为http是基于TCP的应用协议,TCP层维度的考虑不得不从http的几个版本的发展历史来考虑:http1.0时期:tcp连接是基于单通道顺序等待请求的requestmustre-establishedtheconnection)是在特定历史背景下产生的,效率低下难以跟上时代发展。1999年在1.0的基础上修订了1.1版本,沿用至今。http1.1时期:在请求头信息中加入keepAlive以保持连接活跃(当然也加入了100Status以节省带宽、缓存特性等),允许在生命周期内重复发送不同的应用请求一个连接通道,在一定程度上缓解了连接资源利用效率的问题,但是当用户浏览网页的时间超过连接活动周期再次请求时,这些请求仍然需要重新建立。在大型科技公司高并发、高可用下高效利用资源的背景下,1.1版本的难度还是很大的。满足大公司在高性能条件下对网络资源高效利用的要求。Google(就是谷歌,牛逼)在2009年率先开发了基于TCP的新SPDY应用协议,解决了多路复用请求优化和服务器推送的痛点,也为后续http2.0的推出奠定了基础。我们可以做的优化:减少一些不必要的请求(剔除404死连接,使用我们对304请求的长缓存机制)进行优化,尽量减少不必要的连接请求。4.代码实现鉴于js语言本身的灵活性以及大家的开发习惯,很难有一个很好的方式来验证开发者代码实现的效率(目前更多的是通过测速的方式来监控代码)执行时间),更多的是建议,大家有更好的建议可以分享。单线程限制:使用异步回调&多线程API,突破单线程js语言带来的内存开销不足的问题。可以尝试使用一些可以使用的异步回调,比如settimeout,setinterval,requestAnimationframe(推荐)),多线程的API方法有WebWork。这里推荐Concurrent.Thread.js,一个日本开发者开发的多线程前端库(是作者用setTimeout和setInterval模拟的多线程模拟,大家可以上网搜索一下)。重点优化代码中的循环结构:就代码本身而言,大部分影响执行效率的是循环结构和算法设计不合理导致的时间复杂度和空间复杂度的增加。下面放两张实际项目中的代码截图进行对比:示例功能需求:实现输入框每4个字符用空格分隔的效果低效的实现方式,使用for循环:改进方式:合理使用设计模式来优化代码结构:合理使用设计模式(不能滥用)可以优化内存,提高执行效率,比如在创建xhr请求对象时使用单例和简单工厂:创建一个简单工厂对外提供xhr对象,以及在factory内部使用closures包开辟了一个数组队列单例来存放xhr对象。当调用者需要xhr对象时,工厂会从队列中取出readyState为空闲(0/4)的xhr对象,否则重新创建并放入队列中。在有大量ajax对象请求的应用中,可以最大程度的节省创建xhr的内存开销。下面用一张简单的图来说明思路:其他优化建议:比如减少js频繁操作dom节点的次数(这个本来是打算放在host环境维度的),减少浏览器的重排和重绘;比如对于dom标签对象(主要针对ie下dom对象的引用会被GC回收漏掉的问题),以及闭包内部引用类型的变量,记得及时Release,避免内存泄露。5、产品交互逻辑性能优化一般都是从技术角度出发,但是我们的目标之一“让用户更容易使用”也是性能优化的一部分。当技术性能缺陷难以避免时,作为前端交互实现的执行者,我们应该配合产品和交互设计师提出我们认为更好的交互逻辑体验方案,这样我们的数据加载才不会让用户觉得waiting,让我们的提示更人性化,更舒适。(交互设计师比较专业,这里不敢耍花样)Web3.0时代的前端展望:文末对Web3.0时代做一个自己的猜想。Web3.0在业界还没有明确的定义,大家可以大胆猜测前端行业的未来形态。让我在这里先YY。人工智能和大数据的广泛应用应该成为推动前端进入3.0时代的最佳契机,这将引发前端新的革命:浏览器成为系统生态(至于哪个浏览器做目前还不好说,现在把谷歌浏览器PWA方案提供给前端app开发方案,有这个趋势,以后不用再装系统了)。前端不再是数据搬运工。在web领域,很有可能除了底层维护数据库的工程师继续培养(主要切入云计算领域),其他的可以转为浏览器前端系统生态(我的天,有一种当农奴当主人的感觉O(∩_∩)O~~)。传统的html语义超文本标记语言已经难以承载更多的信息数据。HTML5不断向纵深发展,不再只是浏览器特有的标记语言。它可能成为具有各种信息表示的跨平台标准信息表示层。改造前无古人,到时候可能都不叫html了。http2.0已成为广泛使用的标准,并得到进一步深化。安全验证层类似于区块链去中心化的思想,实现极致安全(估计https可以下岗)。js已经成为跨平台公认的标准实现语言(目前前端跨平台的基本形式已经存在),随着Es6的广泛推广和深入完善,可能没有现在这么灵活,并且更像是一种合格的标准“高级”脚本语言。(以上推测纯属个人理解,没有权威认证,大家畅所欲言)结语:刚来鹅厂没多久,以前端攻城狮的身份进入国内顶级互联网公司而自豪。性能优化是一个永恒的话题。性能优化的话题层出不穷。整理了自己这些年来的一些不成文的理解,希望对大家有所帮助。第一次发文章,如有错误请多多指教。相关阅读Web前端性能优化:如何有效提升静态文件加载速度使用SkeletonScreen提升用户感知体验Box-sideCSS动画性能提升研究