全链路前端性能优化
通常来讲前端性能优化是指从用户开始访问我们的网站到整个页面完整的展现出来的过程中,通过各种优化策略和优化方法让页面加载的更快,让用户的操作响应更及时,给用户更好的使用体验。
这里我们介绍的是前端性能优化知识的解决方案,从静态资源优化开始入手,从表象深入体系化的讲解页面渲染架构,掌握搞笑的技术方案。
本文并非细节的讲述如何实现性能优化,而是从各个方面介绍性能优化的方式方法,并且不仅限于H5,因为当今的前端也不仅仅只有H5。
图片资源优化
- 图片格式介绍
jpeg: 一种针对彩色照片而广泛使用的有损压缩图形格式。是一种栅格图形,常用文件扩展名为jpg,jpeg,jpe。在互联网上常被应用于存储和传输照片。不适合线条图形和文字,图标图形,因为他的压缩算法不支持这些类型的图形,并且不支持透明度。常用于色彩丰富的照片,彩色图大焦点图banner等结构不规则的图形。
png: 便携式网络图形,是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色,以及Alpha通道等特性。他最初的设计是为了代替GIF,能够支持半透明和透明特性,最高支持24位彩色图形和8位灰度图像。不过由于是无损压缩所以文件体积太大。比较适合纯色,透明,线条绘图,图标以及颜色较少的需要半透明的图片。
GIF: 位图图形文件格式,8位色重现真彩色的图像,采用LZW压缩算法进行编码。支持256色,仅支持完全透明和完全不透明,可以支持动图,不过每个像素只有8比特,不适合存储彩色图片。常用与动画和图标。
webp: 是一种现代图像格式,可以提供无损压缩和有损压缩两种。可以同时办证一定程序上的图像质量和较小的体积,可以插入多帧,实现动画效果。支持透明度。采用8位压缩算法,无损的webp比png小26%,有损的webp比jpeg小25-34%,比gif有更好的动画。不过最多可以处理256色,不适合彩色图片。常用于图形和半透明图像。
- 图片优化
对于png图片来说,可以使用jdf-png-native进行压缩, 他是node-pngquant-native工具的封装包,这个工具跨平台,压缩比高,而且压缩png24也非常的好。
const pngquant = require('jdf-png-native');
const fs = require('fs');
fs.readFile('./in.png', (err, buffer) => {
if (err) {
throw err;
}
const resBuffer = pngquant.option({}).compress(buffer);
fs.writeFile('./out.png', resBuffer), {
flags: 'wb'
}, () => {})
})
压缩jpg可以使用jpegtran这个工具,他也是一个node工具。使用方法比较简单,直接使用命令即可。大概压缩10%的占比。
jpegtran -copy node -optimize-outfile out.jpg in.jpg
对于gif文件来说可以使用gifsicle工具,他是通过改变每帧比例,减小gif文件大小,同时可以使用透明来达到更小的文件体积。是一个公认的解决方案。可以去http://www.lcdf.org/gifsicle/
中去安装。使用方式同样也是命令行方式。
gifsicle --optimize=3 --crop-transparency -o out.gif in.gif
这里的优化级别不要小于2,1的话代表不压缩。压缩后基本不失帧。
还有一种压缩方式是图片可以根据网络环境来展示不同尺寸和像素的图片,通过在url后缀加不同参数来实现。比如下面的地址, 430可以修改为800来获得不同体积的图片。
https://img.alicdn.com/imgextra/i1/2616970884/O1CN01x6HnoK1IOuj5IosXO_!!2616970884.jpg_430x430q90.jpg
- 响应式图片
响应式图片是我们可以在用户不同的窗口大小还有设备像素的情况下来展示不同大小的图片,可以用以下三种方式来实现, 第一种是可以使用js来绑定事件检测窗口大小,以此来设置图片的大小。第二种方式就是css的媒体查询。
@media screen and (max- 640) {
my_image { 640px; }
}
第三种可以使用html5的srcset来设置,他会根据设备的像素比来自动选择需要的图片。而且不支持srcset的浏览器也可以正常展示src的属性。
<img
srcset="img-320w.jpg, img-640w.jpg 2x, img-960w.jpg 3x"
src="img-960w.jpg"
alt="img"
/>
- 逐步加载图片的方式
其实就是延迟加载,在真实的图片加载出来之前,可以使用一张公共的图片,一般是公司的logo,先将布局撑起来,然后再换成真实的图片。
lqip这个工具可以将真实的图片虚化,转换为很小的base64编码。这样我们可以先使用base64加载虚化的图片。
npm install lqip
const lqip = require('lqip');
const file = './in.png';
// image
lqip,base64(file).then(res => {
console.log(res); // 输出base64
})
// color
lqip.paletter(file).then(res => {
console.log(res); // 图片颜色值
})
也可以使用低质量图片占位符, 他是基于SVG的图像占位符实现的。
npm install sqip
const sqip = require('sqip');
const result = sqip({
filename: './in.png',
numberOfPrimitives: 10 // 效果值
});
console.log(result.final_svg); // 输出svg格式
相比lqip来说sqip效果会好很多,而且可以设置不同的大小。
- 其他方式
可以使用web font来代替图片,比如说小图标等业务小图片。
也可以用dataurl的方式,也就是前面的base64的方式来代替图片,这样用户就不需要发送http请求了。
也可以采用雪碧图将多个小图片合成一个大图,这样也会节省很多的图片请求。
- 图片服务器自动优化
图片服务器优化是指可以在图片url连接上增加不同特殊参数,让服务器自动生成不同格式,大小,质量的图片。
比如说可以对图片做一些裁剪,裁剪成我们需要的图片,也可以支持不同格式的转换,比如说jpg,gif,png,webp等也可以设置图片的压缩比。
也可以对图片添加一些水印,高斯模糊,重心处理等还可以增加一些AI的能力,比如说用户上传的图片是否涉黄。还可以通过智能抠图,智能排版,智能配色智能合成等功能完善图片。
HTML的优化方法
- 精简html代码
可以减少html的嵌套也就是层级关系尽量减小,也可以减小DOM节点数也就是尽量压缩优化DOM的节点数, 让浏览器渲染的DOM节点数最少。
减少一些无语义的代码,比如说空标签清浮动那种代码<div class="clear"></div>
能不用最好不要用。
建议连接中删除http或者https,因为一般链接的协议头和页面的协议头都是一致的,写他们多了4-5个字符其实是没有什么意义的。而且可以减少代码体积。
也可以删除多余的空格,换行符,缩进和不必要的注释,一般会用压缩工具来处理这个过程。可以省略一些标签和属性。使用相对路径的url,最大范围的减少字节数。
- 文件位置
css文件链接尽量放在页面头部,css加载不会阻塞DOM Tree解析,但是会阻塞DOM Tree渲染,也会阻塞后面js执行。也就是说DOM Tree在渲染前就要解析好CSS,从而减少浏览器重排文档的次数。而且css放在页面底部会导致页面白屏时间变长。
js文件一般放在页面底部,这是防止js的加载和解析阻塞页面元素的正常渲染。
- 用户体验
设置favicon.ico, 如果不设置控制台会报错,而且用户访问的时候地址栏也是空的,不利于品牌记忆。
增加首屏必要的css和js,一般页面需要在等待所有的依赖加载完成才会展示,这样就会导致页面存在空白。永祥用户体验,可以增加背景图或者loading或者骨架屏,比空白页好很多。
CSS优化细则
- 提升css渲染性能
谨慎使用一些expensive的属性,比如nth-child伪类或者position:fixed定位,因为这些比较消耗浏览器的渲染性能。
尽量减少一些样式层级的级数,比如,div ul li span i { color: red}, 其实我们可以给i标签设置class,直接书写样式。
避免使用占用过多cpu和内存的属性,比如text-indent不要设置太大的值。
尽量避免使用耗电量大的属性,比较占用GPU, 比如transfrom是,transitions, opacity.
合适的使用css选择器, 尽量避免使用通配符,避免使用css表达式。color: expression((new Date()).getHours() % 2 ? "#fff" : "#000")。
避免类正则的属性选择器。 *=, |=, ^=, $=,使用外链的css,可以单独形成文件放在cdn,使用缓存形式加载。避免使用@import因为他的加载会阻塞进程,需要加载完毕才会向下执行。
精简css代码,使用缩写的语句,比如margin-top可以写在margin中,再者如果值为0能删除就删除,删除不必要的单位值,删除过多的分号,删除空格和注释。尽量减小样式表的体积。其实这些都可以使用压缩工具来处理,会方便很多。
- 合理使用web fonts
可以将字体文件部署到cdn上,加快用户端的加载速度,也可以将字体以base64的形式保存在css中,并通过localStorage进行缓存。一些谷歌字体库应该使用国内托管服务不要直接使用源地址。
- css动画优化
避免同时动画,也就是说用户访问的屏幕区间里面不要有过多的动画,动画太多会干扰用户正常浏览网站,而且动画多也影响浏览器的性能。
延迟动画的初始化,可以让其它css先渲染,让动画延迟,比如说0.5或1。
可以借助svg去展示动画,样式放在css里面控制。
JavaScript优化
首先我们是当需要的时候才去优化,不是为了优化而优化,一般的优化是在某一个时间点进行的,而且优化也需要考虑可维护性这是要结合团队的研发水平和代码的规范。
- 提升js文件的加载性能
这个基本每个人都知道,就是css文件放在head标签中,js文件放在body结尾的地方。这个是js的加载不要影响html的渲染。
- 变量和函数方面的优化
尽量使用id选择器,因为id选择器在查询效果上效率最快。
避免使用eval,这个方法比较消耗能行。
js函数尽可能保持简洁,不要把太多内容写在一个函数中。也建议使用事件的节流函数。事件委托等等。
- js动画
尽量避免添加大量的js动画,css3动画和canvas动画都比js动画性能好。
使用requestAnimationFrame来代替setTimeout和setInterval,因为requestAnimationFrame可以在正确的时间进行渲染,setTimout和setInterval无法保证渲染时机。不要在定时器里面绑定事件。
- 使用逻辑缓存
缓存dom对象,也就是用一个变量来存储dom对象,不要每次使用都查询。
缓存列表长度,也就是说用变量存储dom元素的个数,而不是每次都重新计算。
比如百度M站,会把页面的css和js放在本地存储里面,这样后面再加载的时候就直接从本地存储里面取,实现秒考的效果。不过本地存储空间有限,要谨慎使用。
减少页面回流和重绘
- css
避免过多的样式嵌套,最好可以快速的定位到元素。
避免使用css表达式,css表达式会在css绘制的过程中都会执行,会增加重排和回流的次数。
可以使用绝对定位让动画元素脱离文档流。
避免使用table布局他会引起浏览器的多次重绘,也不要使用float布局。
图片最好设置好设置width和height,这样图片在加载之后布局就可以确定了。
简化浏览器不必要额任务,使用viewport设置屏幕缩放级别。
避免频繁设置样式,将多个样式操作合并修改,一次性的更新。
- js
为了减少回流发生次数,应该避免频繁操作DOM,可以合并多次对DOM的修改,一次性批量处理。
控制绘制过程和绘制区域,绘制过程开销比较大的属性设置应当避免使用。
简化DOM操作
众所周知,页面交互卡顿和流畅度很大一部分原因就是页面有大量DOM元素,想想一下,从一个上万节点的DOM树上,使用querySelectorAll或getElementByTagName方法查找某一个节点,是非常耗时的,另外元素绑定事件时,事件冒泡和事件捕获的执行也会相对耗时。所以一般我们应该合理的不熟业务逻辑,DOM节点过多时应该延迟即将呈现的DOM内容。
对DOM的操作最好统一处理后再统一插入到DOM Tree中。可以使用fragment对DOM和样式设置好再统一放到页面中去。
目前比较流行的框架,比如Angular,React和Vue都是使用虚拟DOM技术,通过diff算法简化和减少DOM操作。
静态文件压缩工具
html-minifier: 压缩html clean-css: css的压缩工具 uglify-js: js文件的压缩工具
浏览器渲染过程
首先浏览器会解析HTML生成DOM Tree,然后解析CSS生成CSSOM Tree。接着JS会通过DOM Api和 CSSOM Api来操作DOM Tree和CSS Rule Tree 将DOM Tree 和 CSSOM Tree合成一颗渲染树Render Tree。
根据生成的渲染树进行回流,以计算每个节点的几何信息,包括位置,大小,样式等等。然后根据渲染树和回流得到的几何信息,得到每个节点上的绝对像素。
最后将像素发送给图片处理器也就是GPU进行页面展示。
前端页面渲染可以分为服务端渲染和客户端渲染。服务端渲染有传统的后端同步渲染,同构直出比如php,java, .net或者大家熟悉的node。
客户端渲染也就是js渲染,前后端分离,单页面应用。react, vue, ios,安卓,hybird app,flutter等。
懒加载,预加载,预渲染
懒加载也叫延迟加载,指的是长网页中延迟加载特定元素,可以是图片也可以是js和css。懒加载的好处是可以减少当前屏无效资源的加载。
一般我们会把img标签的src属性设置为空字符串,真实的图片地址放在data-lazy中,当页面scroll到对应的位置时再通过DOM操作将src的值替换为data-lazy的值。
预加载是让浏览器预先加载某些资源,同样也是图片,js或者css,这些资源是在将来才会被使用的。
简单来说就是讲所需要的资源提前加载到浏览器本地,后面在需要的时候可以直接从浏览器的缓存中获取,而不用再重新开始加载。好处是减少用户后续加载资源等待的时间。
可以使用new Image的方式也可以使用标签的方式preload,prefetch, preconnect
<link rel="preload" href="src/style.css" />
<link rel="prefetc" href="src/image.png" />
<link rel="dns-prefetch" href="https://my.com" /> <!-- 提前将dns缓存-->
<link rel="preconnect" href="https://my.com" /> <!-- 提前加载需要的资源 -->
另一种预加载组件的方式就是提前渲染它,在页面中渲染组件,但是并不在页面中展示,也就是渲染好后先隐藏起来,用的时候再直接展示。可以使用prerender将https://my.com页面先提前渲染好。
<link rel="prerender" href="https://my.com" />
接口服务调用优化
1.接口合并,指一个页面的众多的业务接口和依赖的第三方接口统一使用一个部署在集群的接口统一调用,以减少页面接口请求数。
2.接口上CDN,主要基于接口性能考虑,我们可以把不需要实时更新的接口同步至CDN,接口内容变更自动同步CDN。
3.接口域名上CDN可以增强可用性和稳定性。
4.接口降级,这个基于大促备战考虑,核心进行降级用基础接口进行业务实现,比如千人千面的推荐接口,在大促时间点可以直接运营编辑的数据,另外接口万一无法访问,使用预设好的垫底备份数据。
5.接口监控,监控接口的成功率不是常说的服务器TP99,而是和用户实际情况一直的成功和失败监控,比如弱网,超时,网络异常,网络切换等情况。排查出来问题需要联合后端,运维,网络岗位人员一并解决。
接口缓存优化
1.ajax/fetch缓存,前端请求的时候带上cache,依赖浏览器本身的机制来请求接口,这个比较适用于不会经常变更的数据。
2.本地缓存,异步接口数据优先使用本地localStorage中缓存的数据。可以让服务端返回数据的时候再给一个md5值,然后将md5值和数据绑定存在本地,再次请求的时候对比这个md5值,如果相同就不要再请求获取数据的接口了,如果不同就请求更新。
webview
原生的webview对于IOS来说有两种,一种是UIWebView,他从IOS2开始就作为App内展示web内容的容易,而且排版布局能力比较强。
不过UIWebView也有很多的问题,比如说内存泄漏,运行期间会有极高的内存峰值,Touch Delay延迟300毫秒。js运行性能不高,在2018年的ios12以后就不再维护了。
WKWebView是苹果在WWDC 2014上推出的新一代WebView组件,WKWebView的内存开销比UIWebView要小很多,而且在性能,稳定性,内存占用方面都有很大提升。可以实现60fps的滚动刷新率,自身就支持了右滑返回手势,支持更多的HTML属性。内存占用是UIWebView的1/4 ~ 1/3, 加载速度比UIWebView提升了一倍左右。大幅度提升了js执行速度。允许js的Nitro库的使用,UIWebView是不允许的。可以和js直接互调函数,不需要使用jsbridge来协助。
当然WKWebView不支持页面缓存,需要自己注入cookie,而UIWebView是自动注入cookie的。他也无法发送POST参数。
对于安卓来说存在webkit for webview 和 chromium for webview。
webkit是一个开源项目,前身是khtml和kjs,专注于网页内容的展示,做了一流的页面渲染引擎,他不是浏览器而且也不想成为浏览器,这个项目包含两个部分,第一个部分是WebCore, 其中包括对html,css很多w3c规范的实现,第二部分就是狭义上的webkit主要是各个平台的移植并提供相应的接口。也就是webview和类似于webview,这样的接口提供操作和显示网页的能力。
目前使用WK的主流浏览器或者webview包括chrome,safari, 安卓平台以及众多的移动浏览器。
chromium是基于webkit之上的一个浏览器项目,由谷歌来发起,这个项目发展的还是比较迅速的,他对新特性的支持还是比较好的,比如webgl,css3,h5等等,在性能方向也非常不错chrome一般选择稳定的chromium作为基础。
Webkit for Webview | Chromium from Webview | 备注 | |
---|---|---|---|
版本 | Android4.4以下 | Android4.4以上 | -- |
JS解释器 | WebCore JavaScript | V8 | -- |
H5 | 278 | 434 | -- |
远程调试 | 不支持 | 支持 | Android4.4及以上支持 |
内存占用 | 小 | 大 | 相差20-30M |
WebAudio | 不支持 | 支持 | Android 5.0及以上支持 |
WebGL | 不支持 | 支持 | Android 5.0及以上支持 |
WebRTC | 不支持 | 支持 | Android 5.0及以上支持 |
安卓第三方内核,主要是安卓的版本较多,对WebView二次封装产生的,这里主要说下X5内核。
他的速度是比较快的相比系统WebView的网页打开速度有30%的提升,在流量方面使用云端优化技术节省20%以上。安全问题可在24小时内修复。更稳定经过亿级用户的使用考研,CRASHE(崩溃)率低于0.15%。没有系统内核的碎片化问题,更小的兼容性问题,支持夜间模式,适屏排版,字体设置等浏览器增强功能。在H5和ES6上有更完整的支持,集成了强大的视频播放器,支持视频格式远多于系统的WebView,视频和文件的格式支持X5内核多于系统内核,自带防劫持。
一般webview选型,IOS建议使用WKWebView, 安卓建议使用X5。
WebView性能优化
当App首次打开时,默认是不初始化浏览器内核的,当创建WebView实例的时候,才会启动浏览器内核,打开事件需要70-700毫秒,并创建webview的基础框架。
可以使用全局的Webview对延迟的毫秒进行优化,就是在客户端启动的时候,就初始化一个全局的WebView待用,当用户访问Webview的时候直接使用这个WebView加载对应网页。
这样会减少首次打开WebView的时间,缺点是会有一些额外的内存消耗。
导航栏可以预加载,以前是在webview加载完成之后进行初始化,可以改为和webview并行一起加载。
对于登录来说H5页面上接口每次查询Cookie中是否有登录态,无登录态H5跳转统一登录页,App登录成功写入Cookie。可以改为Cookie统一在Webview中设置cookie。也就是初始化Webview的时候判断是否登录,如果登录了就打开H5页面,如果没登录就自动跳转登录页面。
webview加载页面的url尽量前置,不要放在最后,可以和业务逻辑并行处理,总而言之减少页面的白屏时间,让用户最快的看到页面。
提升滚动条的使用体验,原本是使用系统自带的滚动条的进度值,可以自己模拟滚动条的加载过程,让用户感觉页面加载变快了。也就是初始快速的加载到60%以上,给用户感觉加载很快的感觉。其实真实速度并没有变...
js-sdk优化,也就是oc和js通信的一个方式。一般jssdk有三种方式实现,第一种就是常见的scheme的方式,就是我们在h5页面里面定义一些特殊的链接,拿到这个scheme之后原生拦截。然后把需要的回调函数和参数进行拦截,但这样有个问题,url一般是256个字符,有长度的限制不能无限的传递。
第二种是iframe的形式,通过后台启一个页面进行拦截取iframe的变更拿到js的方法给oc实现通信,iframe是依赖jssdk.js文件的,需要sdk文件作为桥梁实现通信目的。
第三种是webkit的方式,他是一种直接调用的方式,无需依赖任何的sdk文件。
浏览器缓存策略
缓存机制 | 优势 | 适用场景 | Android 开关 | IOS开关 |
---|---|---|---|---|
浏览器缓存机制 | HTTP协议层支持 | 静态文件的缓存 | 浏览器负责 | 浏览器负责 |
Web Storage | 较大的存储空间,使用简单 | 临时,简单数据的缓存,浏览器上的LocalStorage、SessionStorage | webSettings.setDomStorageEnabled(true) | 默认开启无关闭 |
Web SQL Database | 存储,管理复杂结构数据 | 建议用IndexDB 替代 | webSettings.setDatabaseEnabled(true) | 默认开启无关闭 |
Application Cache | 方便构建离线App | 离线App,静态文件缓存 | webSettings.setAppCacheEnabled(true) | 默认开启无关闭 |
IndexDB | 存储任何类型数据,使用简单,支持索引 | 结构,关系复杂的数据存储 | webSettings.setJavaScriptEnabled(true) | 默认开启无关闭 |
H5离线化的实现方式
全局离线包,包含公共的资源,可供多个应用共同使用。
还可以有私有化的离线包,只可以被某个应用单独使用。
离线包的工作原理:
首先会加载一个全局的包就是一些基础的文件,加载之后会把包释放放在内存里,接着会做一个检测,查看本地是否安装,如果已经安装就释放到内存,如果没有安装就触发离线包的下载,就是我们做好的包放在服务器中,然后从服务器获取过来,在下载之前会进行一个本地和线上版本的对比,版本不一致的话就会下载最新的包,如果一致就取本地的就可以了。
最终这个包会解压释放在内存里面,当webview在加载url的时候会直接从内存里面读取,如果能读取到就加载内存中的页面数据进行展示,假设读取不到也就是说本地没有这个业务就会使用线上的url地址让页面加载就可以了。因为我们一般不会把所有的业务都做成离线化的形式,假设webview查询的到就用离线化,查询不到就用垫底的线上url展示。无论本地离线包加载失败还是没有这个离线包,都使用线上url来垫底。
离线包的下载一包情况下如果用户处于移动网络状态下,不会在后台下载离线包,如果当前用户点击app,离线包没有下载好,用户就要等待离线包下载好才能用。可以采取wifi静默下载的方案。
从服务器请求的离线包信息存储到本地数据库的过程中,离线包信息包括离线包的下载地址,离线包版本号,加密签名信息等,安装离线包其实就是将离线包从下载目录拷贝到手机安装目录。
一些大厂的离线包方案比如美团的LsLoader通用移动端WebApp离线化方案,腾讯的Alloykit手Q离线包,阿里的极致Hybrid航旅离线包再加速。原理基本上都是一致的,细节上可以做些参考。
混合开发介绍
1.RN React Native是基于React语法的, 希望实现的是一套代码可以在各个端使用。他的优势很明显,代码是可以共享的无论是IOS还是安卓还是H5,性能方面几乎也与Native相同。并且提供了非常流畅的动画,因为他在渲染之前代码就已经转换为了原生视图。
调试时无需每次代码变更都编译打包,可即时查看更小效果,极大提高了开发人力。
支持热更新,不需要每次发版都发布应用到商店,发版时间可以自由控制,安卓和ios同时发版。
一共分成四层实现,最下面是native的原生层也就是OC和Java,在这之上是UI渲染器,图片处理,网络通信,和一些工具库,再向上是C++: JSCore,Bridge也就是js的运行环境和js和native的桥接。最上面才是js层也就是js的一些组件。
RN的jsx文件通过JSBridge会针对不同平台打包成不同的格式,比如IOS的.m文件,安卓的.xml文件,以及H5的.html文件。
为什么会有RN其实是因为应用商店发版的问题,每一次发版都需要审核,可能审核不通过,而且安卓可能要发布多个商店,还有两端研发不同步的问题,也就是安卓和ios相同的业务需要开发两遍。
如果你公司的技术是React全家桶,那还是建议选用RN的。
小程序
小程序的愿景是触手可及,用户扫一扫或者搜索下就可以打开应用,不需要安装太多应用。
向程序相比App,开发门槛更低,优于H5接近Native的体验,可以使用相机,位置,网络,存储等丰富的原生能力。支持顶部下拉,搜索,扫码等入口,简单方便,用完即走,不需要像App那样下载,直接打开支持热更新。
小程序出现的行业背景,对于App大厂来说需要流量变现,比如微信,他是没办法变现的,所以可以使用小程序生态将第三方引入进来,形成了一个小型的应用市场。对于企业应用来说,移动流量枯竭,获客比较困难,可以降低获客成本和开发成本,业务上提供更多的试错机会。
平台类产品如果输出给商家端,相比多个app的方式,比较推荐使用小程序。
Flutter
号称编写一次可以部署到各个终端,web, android,ios,mac,linux,windows,fuchsia os。
底层使用Skia图形引擎,图形性能媲美原生应用,界面更像一个全屏应用程序或2D游戏,速度比较快,使用本机ARM二进制文件,做到提前编译,不需要JVM,也就是java虚拟机。
底层实现
flutter在2017年5月份出现,生态不够丰富,学习曲线相对较高,但是他的性能较好,如果考虑性能,团队人员足够的话建议选择fluttr。
CDN
CDN是内容分发网络,利用每一台最靠近用户的服务器,更快更可靠的将文件发送给用户。以加快访问速度。
CDN的有点很明显,因为会给用户指派较近,较顺畅的服务器节点,所以速度会比较快,服务器放在不同地点,减少了互联的流量,也降低了快带成本,当某个服务器故障时,自动调用临近地区的服务器。
回源是指浏览器访问CDN上静态文件时,文件缓存过期,直接穿透CDN而访问源站机器的行为。这是CDN的一个策略。
CDN的缓存分为三级,浏览器本地缓存也就是header头配置的缓存,CDN边缘节点缓存,CDN源站缓,一般是三级,也可能业务比较少就采用两级缓存,浏览器缓存和CDN源站缓存。
缓存时间设置过短的话,CDN边缘节点缓存经常失效,导致频繁回源,增大了源站负载,访问速度也会变慢,缓存时间设置的过长,文件更新慢,用户本地缓存不能及时更新,所以一般是结合业务情况而定。
一般不同资源类型缓存时间设置不用,html一般3分钟左右,js,css可以10分钟,一天,一个月,看变更情况。
现在一般我们文件的命名都会以hash串的形式,如果文件有变更生成的文件名就会有变更,否则还是之前的名字,这样我们缓存的时间就可以设置的长一些。
CDN可以灰度发布,也就是在部分地区部分运营商优先发布静态资源,验证通过后再进行全量发布。具体实施可以从域名方面下手,设置特殊VIP解析到要灰度的城市或者运行商。也可以调整源站机器,给灰度的城市或者运营商配置单独的源站机器。
一般在活动期间比如说大促,需要增加机房宽带,增加运营商流量,增大CDN缓存时间等等。
DNS
DNS是将网站域名和地址互相映射的一个分布式数据库,我们访问一个网站首先会通过DNS将域名匹配为ip地址,然后再通过ip地址去访问对应的服务器。
客户端里面有一个http dns, 客户端直接访问http的接口,可以获取业务在域名访问系统中配置的最优ip,基于容灾的考虑,app内是需要保留使用运营商DNS解析方式的,客户端再获取到ip后会直接向ip中发送业务请求。
比如一个http请求,在header中会指定host字段,向ip发送标准的http请求就可以了,总的来说采用http-dns来解析域名能绕过三四级运营商接续域名出现的一些问题,在http-dns返回的正确ip之后是直接使用ip去发送http请求的,只需要关注通信内容的安全就可以了。
安卓系统可以采用okhttp模块,他支持http2,http2可以在一个链接上一次性发送多个请求,支持gzip,也支持响应缓存避免网络重复请求,如果服务器配置了多个ip地址,当第一个ip链接失败的时候,okhttp会自动尝试下一个ip地址。
ios没有现成的模块,我们可以在app启动时,缓存所有可能要用到的域名ip比如接口,网关,同时异步处理,客户端无需得到缓存结果。如果cache中有此域名的圆环,直接返回缓存的ip,如果缓存中没有此域名,则重新向httpdns server进行申请然后缓存下来。
H5的做法一般是设置多个域名,因为浏览器对并发数是有限制的,一个域名一般最大连接数是6,所以我们可以将用户访问的一些api接口作为一个域名,页面中的样式和资源可以设置成一个域名,图片也可以单独设置成一个域名,甚至多个域名,来打破浏览器的这种限制。
http优化
http的优化主要就是减少请求数,这可能是我们日常工作中经常遇到的,也是大家耳熟能详的。
图片可以使用雪碧图,dataurl, webfont。
可以考虑将业务中的js或者css合并,不要切割的太小。如果不想合并成一个可以使用Combo的方式让服务去返回,可以在url上通过参数的形式告诉服务加载那些资源。
接口也可以合并,不要拆分的太细,可以让服务去合并,不经常变化的接口和资源也可以存储在LocalSrorage,有变化就更新,没有变化就从本地取。
有些时候我们的某个页面会出现问题,或者打开白屏,但是接口没有问题,页面也没有问题,资源也是可以访问通的,这个时候可能就是cookie太大了,已经超出了原本可控的范围,我们都知道cookie是会随着页面间的跳转携带的,这就肯能导致页面无法访问,这种问题不常见,但实际工作中确实会遇到。
可以在页面中设置cookie白名单,意思就是定期检查我们的cookie,如果是需要的就保留,不需要的就删除,定期整理。cookie控制可以减小页面间传输的大小,也可以对cookie进行有效的管理。
服务器缓存配置
当一个文件被浏览器加载的时候我们实际上是不知道这个文件是否是过期的,所以浏览器和服务器之间存在一种约定,通过header头的配置,确定文件是否过期。
一般在响应头中包含一个expires的头信息,他的值为日期+时间,表示在此时间之后,响应过期,如果数值为0,表示资源已经过期。
当然如果响应头中包含Cache-Control, 设置了max-age或者s-max-age指令,那么就会忽略expires,而取Cache-Control。
Cache-Control通过制定的指令来实现缓存机制,缓存指令是单向的,这意味着在请求中设置的指令不一定被包含在响应中。他的语法比较简单,Cache-Control: max-age=秒
设置缓存存储的最大周期,超过这个时间缓存被认为过期。
ETag是资源版本的标识符,可以让缓存更搞笑,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应,如果发生了改变,使用ETag有助于防止资源的同时更新相互覆盖。
ETag类似于指纹,也可能被某些服务器用于跟踪,比较ETag能快速确定资源是否变更,但也可能被跟踪服务器永久存留。
ETag: "5c6cccc123-1d45"
Last-Modified是一个响应收不,其中资源包含源头服务器认定的资源做出修改的日期和时间。他常被用作一个验证器来判断接收到的或者存储的资源是否一致。由于精度比ETag要低,所以这是一个备用机制,包含有if-Modified-Since或If-Unmodified-Since首部的条件请求会使用这个字段。
Date是通用的首部,其中包含了报文创建的日期和时间。
Gzip压缩
可以对文本进行压缩,一般是html,css,js,对于非文本不会压缩,比如说图片资源,压缩比率可以达到50%-70%
本地测试开启https
这个内容不应该写在这,但是实在不知道写在哪了。
https这里就不过多介绍了,毕竟不是这篇该有的内容。
浏览器目前基本上已经默认开启了https,所以为了SEO我们也建议使用https,而且https也更加安全。
如果是对外的网站我们需要和经销商购买ssl证书,可以在gogetssl,ssls.com,sslmate.com中去购买,当然这些证书是有时效的。
如果本地测试的话,也可以在本地安装一个测试证书,我们可以通过mkcert来实现。首先需要安装它。
brew install mkcert
安装根证书
mkcert ---install
生成本地签名,给123.com
mkcert 123.com
这样就生成了一对证书,我们可以将这对证书配置在nginx里面,具体配置方法可参考我之前写的nginx文章。
http2
http2是http的第二版,简称h2或h2c,它采用二进制传输数据,多路复用,允许通过一个链接发起多个请求,所以一般使用h2雪碧图就没什么用了,他超出了浏览器限制最大连接数的局限,对header头进行压缩从而降低传输体积,支持服务端推送(server push),可以从服务端将数据推送给客户端。
开启HTTP2可以降低服务器压力,提升网站访问速度,而且可以更好的保护网站安全因为他是强制使用https的。
开启http2其实也很简单,我们需要重新编译nginx,并且开启http_ssl_module和http_v2_module
cd nginx-xxx
./configure --with-http_ssl_module --with-http_v2_module
make && make install
同样这里可以参照之前我写的nginx文章,其实就是在listen 443端口后添加http2标识。
server {
listen 443 ssl http2;
}
前端的研发流程
首先是技术选型,包括页面渲染技术和混合式开发技术,然后是项目的初始化,包括React,Vue,Angular,依赖模块引入,一般会存在一个私有的NPM,接着开始本地开发,方便前端调试和看到效果,项目联调,产品和设计师对效果进行确认,最后项目整体部署上线。
项目开始之前前后端会指定一些数据接口,有了接口文档前后端就可以并行开发,前端开发页面和交互,后端开发业务逻辑。都开发完后前后端开始进行联调,最后发布上线。
自动化测试
UI自动化,上手比较简单,不过稳定性较差,常用的工具有appium,他是一个开源的工具用于自动化ios手机,安卓手机还有windows桌面的一个测试工具,robot framework 是基于python可扩展的关键字测试框架用于端到端,验收测试以及测试驱动开发,可用于测试分布式异构应用程序包括可以验证涉及多种技术的接口,selenium用于web应用程序测试工具可以直接运行在浏览器上,可以和用户的真正操作是一样的,支持ie, 火狐,谷歌,欧朋等常用浏览器,主要用来测试浏览器的兼容性。airtest支持自动化的脚本录制一键回收,轻而易举就能实现自动化测试流程,还是比较常用的。
接口自动化,使用稳定,性价比非常高,工具有java + restassured,是java实现的,轻量级的可以通过编写代码向客户端发送请求并且验证返回结果。python + requests主要对pthon接口进行测试, JMeter用于对软件做压力测试,HttpRunner只需要一份脚本就可以实现自动化测试性能测试,线上监控,持续集成等多种测试的需求。
单元测试,性价比极高,一般由开发完成,但是有一些单元测试框架,Junit5,pytest, unittest。