移动端H5活动页优化方案
背景
项目:移动端H5电商项目
痛点:慢!!!
初始方案:最基本的图片懒加载,静态资源放到cdn,predns等等已经都做了。但是还是慢,慢在哪?
显而易见的原因:由于前后端分离,所有的数据都由接口下发,之后根据模板渲染页面。也就是说,我们需要先加载js,等到js加载完毕之后,请求接口,接口返回数据之后,渲染页面,加载图片等等。尽管使用了模块化的加载方式,但是对于要求高的首页和活动页,给用户的感知也不是很好。
初版解决方案
最初,由于时间紧迫,基本上都是从客户端作优化处理,基本上可以总结为以下几个方面。
一、本地缓存
我们做了本地缓存优化的策略,第一次请求之后就把接口数据缓存到localStorage里面,并且存储当时的时间,设定过期时间,一般设置为5分钟,用户在5分钟内重复打开页面,不会再次请求接口,从localstorage中拿取数据,直接渲染页面。
后续干脆把模板渲染好的html片段存储了起来,直接拼接,省去了模板计算的时间。
基本实现方案如下:
var cache = localStorage.getItem('cache') , expires = 5 * 60 * 1000 ; // 判断是否过期 function isOverdue(pastTime, expires) { return Date.now() - pasttime >= expires; } if (cache && !isOverdue(cache.time, expires)) { // 说明缓存存在,并且没有过期 // 就正常取cache.data做相应的渲染 } else { // 说明缓存不存在或者已经过期了 // 重新请求接口 $.get('a.cn', funciton (res) { // do something // 把对应的渲染操作处理完成之后,将数据缓存,并记录当前的时间 localStorage.setItem('cache', { data: res, time: Date.now() }) }) }
然而还是不够,新用户在首次打开时,还是不能秒开页面,并且用户在5分钟之后重新加载之时,仍然会有一定的延迟(由于浏览器会缓存一部分静态资源,此时再打开并不会像用户初次打开一样那么慢)。
二、进一步缓存静态资源
在日常开发中,有很多依赖库,常用的fastclick,swipe等等,这些库,没有必要每次都去加载,虽然浏览器会对一些静态资源做缓存,但是却不能完全被我们控制,所以,可以将这些不常发生变化的静态资源缓存起来,同样的,存到localStorage里面。
需要注意
这个方案有一个问题,如果是直接加载的script标签,是无法直接拿到它的脚本内容的。
<script id="script1" src="js/jquery.js"></script>
console.log(document.querySelector('#script1').innerHTML); // 此时输出的是undefined,因为innerHTML是获取标签内容,此时script标签里并没有内容。
<script id="script2"> console.log('2'); </script>
console.log(document.querySelector('#script2').innerHTML); // 此时输出的是console.log('2'); // 因为innerHTML是获取标签内容,此时script标签里并没有内容。
这当然不是我们想要的,我们需要的是外链js的可执行代码。
动态添加js的两种方案
我在前一篇高性能JavaScript读书笔记中提到了两种方案。
1. 动态脚本元素
var script = document.createElement('script'); script.type = 'text/javascript'; script.onload = function () { // do something } script.src = 'jquery.js'; document.getElementByTagName('heda')[0].appendChild(script);
这种方法可以监控到脚本的完成事件,但是由于也是通过添加一个script标签,并不能拿到我们想要的js代码。
2. 通过XMLHttPRequest脚本注入
var xhr = new XMLHttpRequest(); xhr.open("get", "file1.js", true); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { localStorage.setItem('file1', xhr.responseText); } } }
需要特别注意的是,这个方法有跨域的风险,所以,我们需要静态资源服务器的allow-origin设置为*。或者直接加载本域名下的js。
有同学要问了,既然localStorage这么强大,为什么不把所有的东西都缓存起来呢?
当然是因为它的大小有限制,在FireFox和chrome中,一般来说,sessionStorage和localStorage大小为10MB,而safari只有5MB。详见这篇文章。
这就限制了我们什么都存的想法。如果图片较小,可以缓存起来。
第二版解决方案
在前一版方案里,我们解决了后续加载的速度缓慢问题,在后续的页面打开速度上,基本上可以做到秒开。
但是这个方案还是有不足,对于新用户的体验不是很好,如果用户在5min的这个时间点上打开,速度还是会有所下降。
最后还是只能做SSR。
知乎上有一个问题,为什么现在又流行服务端渲染html?。
服务端渲染有很多好处,对我们此时而言,最大的好处就是页面直出。省去了请求接口这一步操作。并且对于提高用户体验上来说,很有好处。
如果时间充裕,可以使用node做服务端,但是由于历史原因,我们这个项目迁移起来也比较费时间,所以最后决定使用openresty来做。
openResty的教程网上很多了,我也不多说,除了官方git,推荐开涛博客学习。
不论我们是在客户端取接口数据,还是服务端,活动页的数据一般来说都有一定持续时长,也就是说,我们也可以在我们的nginx服务器上做一个缓存,假设有一个用户访问了一个活动,那么在接下来的五分钟之内,其他任何用户(相同权限下)访问到的就是第一个用户访问时缓存好的页面。
local args = ngx.req.get_uri_args() local acId = args['acId'] local key = 'ac' .. acId local expire = 5 * 60 --缓存时间5分钟 local value = dict.getData(key, expire) if not value then local res = ngx.location.capture('/a.json') if res.status == 200 then -- dict是一个用来处理存储和读取逻辑的脚本 dict.setData(key, res.body) else ngx.say(dict.getData(key)) end else -- ngx.log(4, type(value)) ngx.say(value) end
在dict中,我们可以把接口数据转换成通过模板转换为html片段存储起来。这样,用户在第一次进入我们的页面以后,也不会感觉慢了。
what's more
openResty能做的事不仅仅是这些,一些网关上权限的控制等等都可以用它来实现。
上面两个方案还有一些不足之处,比如首屏可能内容比较多,一次都加载过来,不一定会快。直观的首屏的由服务端渲染,对于不重要的内容,可以通过AJAX来异步加载。
如何计算活动页这样的页面中,首屏有多大,如何组织代码,哪些部分采用服务端渲染,哪些采用AJAX。这些问题在实际开发中也需要考虑。
限于时间,此次没有能比较完善的解决这个问题,在日后开发中,还会继续完善活动页优化方案。
- 【第1143期】优化移动端window.onscroll的执行频率方案
- web移动端页面性能优化方案
- 【转】数据库SQL优化大总结之 百万级数据库优化方案
- 数据库SQL优化大总结之 百万级数据库优化方案(转)
- 数据库优化之百万级数据方案
- 说说前端性能优化方案
- MySQL大表优化方案
- Tomcat 优化方案 和 配置详解
- 移动端兼容性问题解决方案(三)
- sql 百万级数据库优化方案分享
- 数据库SQL优化大总结之 百万级数据库优化方案
- 【转】C代码优化方案
- MySQL 性能优化的21个方案
- MySQL大表优化方案
- 倾力总结40条常见的移动端Web页面问题解决方案
- 移动端Web界面滚动性能优化: Passive event listeners
- 海量数据库的查询优化及分页算法方案
- 详解MySQL大表优化方案
- 数据库SQL优化大总结之 百万级数据库优化方案