一.前言
软件行业极其缺乏前端人才这是圈内的共识了,某种程度上讲,同等水平前端的工资都要比后端高上不少,而圈内的另一项共识则是——网页是公司的脸面!
几年前,谷歌的一项统计表明,如果亚马逊页面加载每慢 100ms,将影响他们 1% 的收入;如果谷歌页面加载慢 500ms,流量将锐减 20%,这个数据现在必将更加恐怖!
在前端高性能优化(一)、(二)中,笔者介绍了一些关于前端优化的技术,这些技术都依赖于前人的辛苦努力,但我们仍要明白的是,前端的情况十分复杂,优化前端性能是必须因地制宜、因时制宜。
在本篇文章中,主要介绍的就是在一些条件下,传统优化 JavaScript 的技术并不像我们认为的那样适用。
二.JavaScript 模块化误区
加快 JavaScript 加载和执行的速度,一直是前端优化的一个热点。因此我们先来说下 JavaScript 模块化技术的相关知识,希望通过实践来体现模块化技术在使用时的注意事项,避免滥用。
为什么会有模块化技术?
长久以来,编写 JavaScript 一直以文件为单位,一般一个类型的 JavaScript 功能代码会被放在同一个文件里。在一个页面里,引用的文件一般是写死的,也就是不管页面用不用,只要你引入了这个文件,这个文件就会被加载。
举个例子,我们开发了一个内容复杂、功能强大的页面,JavaScript 文件大到 500K,当页面费劲的把这 500K 加载下来,然而用户真正只使用了这 500K 里极少的一部分功能,但我们又不得不把这 500K 加载下来,因为不同的用户使用的功能点可能不一样,我们必须满足所有需求。
而模块化技术提出按需加载,也就是当用户触发该功能的时候,那个功能才真正的被加载。好比 500K 被拆成了 50 个模块,每个模块 10K,当用户触发一个功能时,加载 10K,再触发再加载,以这样懒加载的方式来加载模块,可以很大的提高响应速度。这样,管理模块懒加载的技术也随之诞生。
模块化技术并非到处靠谱!!
之前笔者在网上搜索到了一个模块化技术:SeaJS。它是一个遵循 CommonJS 规范的 JavaScript 模块加载框架,可以实现 JavaScript 的模块化开发及加载机制。与 JQuery 等 JavaScript 框架不同,SeaJS 不会扩展封装语言特性,而只是实现 JavaScript 的模块化及按模块加载。
SeaJS 的主要目的是令 JavaScript 开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的 JavaScript 文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。说白了就是有 Lazy Load 的特性,用到某模块时,SeaJS 才会去加载模块的 JS 文件。我们可以按功能划分多个模块,触发模块功能时,SeaJS 先加载功能模块的文件,然后执行相应的功能。
这个 SeaJS 拥有的特性,初看非常吸引人,它可以说是新定义了一种开发和管理 JavaScript 文件的模式。遵循这个模式,你会享受起 JavaScript 的开发。
实践证明,它也的确可以使 JavaScript 模块化,根据功能划分模块,每个模块对应一个 JavaScript 文件,当执行到模块的功能,或者你需要加载模块时,模块才会被下载,同时不会造成重复下载。这一切看起来如此的合理,如此的顺畅。。。。。
但是在使用后发现了一些问题:由于当时开发的网站功能相对简单,JavaScript 文件并不是非常大,过多的模块,反而会导致总加载的时间变多了。
由于是 Lazy Load 特性,不适合的模块划分导致网站出现反应慢的现象,原因是得先加载模块的文件,才能执行模块的功能。当网络情况不好时,该现象表现的更为严重!!!
可以说,问题出在了对新技术的不了解上,从而出现了问题,预期是 SeaJS 可以处理 JavaScript 优化的问题,因为它具有避免加载不必要模块的功能,结果反而南辕北辙。
总结
虽然尝试出现了问题,但是,收获也是巨大的,简单来说就是以下两点:
1)因地制宜,根据实际需要使用模块化技术,切勿跟风技术,在自己没有完全掌握时,谨慎用于产品中。
2)模块如果是懒加载的,粒度不能太小,也就是模块不能太小。
根据大功能来划分模块也是一个不错的尝试。
笔者尝试将所有模块划分为基础模块和功能模块,基础模块包括页面头部,尾部相关的公共部分,功能模块则根据前后台划分,前台部分,又根据具体的页面来划分,能合并到一起的,就合并成一个模块,增加粒度。结果 JavaScript 文件减少,也达到了预期的效果。
从实际体验来看,对于流量不大的网站来说,没有必要使用懒加载 JavaScript 的相关技术,因为本身 JavaScript 文件就不是非常大。因此在网页加载时,就可以先加载所需要的模块。避免不必要的延迟。
三.JavaScript 的位置问题
这一部分,我们来说说JavaScript 的位置问题对网页网站性能的影响。
为什么要考虑位置问题?
其实不管是 CSS 还是 JavaScript,都需要考虑位置的问题,因为 HTML 的渲染和加载顺序是从上往下,也就是如果前面插入了 JavaScript 的引用,那么必须等到这个 JavaScript 下载完毕才会渲染后续的部分。
因此 JavaScript 的插入位置就成为一个值得考虑的问题,因为不适合的位置可能引起渲染的延迟等,造成不好的用户体验。
传统方案带来的问题和思考
CSS 放在头部,JavaScript 放在尾部,这是传统的经验,它的好处是可以让页面优先渲染, 从而页面可以快速显示。
但有事实往往没有我们预想的那么美好。
有的时候会出现这么一种情况:当页面已经渲染完毕时,我们立刻去使用网站的功能,但很多时候功能按钮会没有反应。原因也很简单,就是 JavaScript 放在页面的尾部,还没来得及加载。。。。
这就纠结了。。。。
两种 JavaScript 放置方式,一种放在头部,一种放在尾部(暂时忽略部分放在头部,部分放在尾部的方式),一个牺牲了渲染速度,一个牺牲了用户体验。所以很多时候 js 的问题我们需要做权衡。
对一般的小型网站来说,用户体验问题要远远大于页面渲染的问题,就比如上文提到的那种功能按钮不可用的情况。而且,如果 JavaScript 不是很大的话,放在头部就很好,既不会有太久的页面空白,也能让其优先加载,二者得到了很好的平衡。
因此,很多经验上的东西并不是绝对的,一定要根据实际的情况,包括功能特点、服务器网络情况等来综合考虑。
为此,笔者写下一个自认为较为合理的位置选择方案,仅供参考。
图 1. 判断 JavaScript 放置位置决策表
从上面的分类介绍,我们也可以看出,将功能代码按类型归类到不同的 JavaScript 文件是多么的重要,比如应该放头部和应该放尾部的代码,最好不要合并在一起,不要等到出问题要优化的时候再去整理和重构,这样会增加很多不必要的工作量。
这不仅仅是为自己工作负责,也是为后面要读你代码的新人负责。养成好的设计编码习惯,也是技术积累的一部分。最后再根据 JavaScript 文件的功能类型,来决定是放在页面的头部还是尾部。
四.怎样确定是不是 JavaScript 的问题?
这个问题在之前的前端高性能优化(一)、(二)中也都简单的说过,之所以在这再次提及是因为确实觉得这个工具用着很舒服。
没错,就是舒服。
随着信息爆炸时代的到来,网站本身性能也深刻影响着公司的形象、利益等问题。但是大多数前端测试工具都太碎片化,没有办法针对多个使用场景,而且很多都是像 yslow 这样简单打个分,也不是真实的用户体验。前一段时间在网上找到了一款前端性能优化分析工具——Browser Insight,里面的功能相当全面,而且可以针对多个使用场景,包括:PC端,移动微信,移动浏览器,移动webview,还是真实的用户体验,也就是说,用户访问你的网页是什么样的,从这个工具中体现出的就是什么样子的。
基于 JavaScript 这个维度 Bi 做的也是相当丰富了。
首先是脚本错误板块。Bi 里面可以从不同的时间维度查看被监控页面出现过的脚本错误,具体信息包括:发生时间、设备类型、报错的浏览器及其版本号、错误堆栈信息都可以看到,不论是线上还是线下测试或者页面维护都是够用了。
不但能看到时间、系统、浏览器等,还可以具体定位到出错的代码行,这个确实很方便。
图 2.Bi 脚本错误
其次是页面响应时间板块,这个算是意外的收获了。通过响应时间板块里面的慢加载追踪,可以看到本次慢加载的页面资源加载情况,然后我们就知道该优化哪个页面的哪些 js 、css、img等。
图 3.Bi 资源列表-时序图
五.前端优化?用户体验?
各位看客,如果你真能坚持看到这里,是否会觉得这篇更像解决用户体验问题的文章?但是讲心里话,前端优化的最终目的,难道不是为了改善用户体验吗?
任何基于用户体验的方案,都可以叫前端优化技术。
不管使用多么好的机器、多么高的技术,响应速度问题总是避免不了的,因此纠结于什么技术来解决是不实际的。但是有一款好的工具,使用一些小技巧,也可以改善用户等待的体验。
比如,我们为每个非 _target 的超链接或者表单的点击,添加点击事件处理,处理逻辑也很简单,就是设置 3 秒超时,显示等待对话框。如果在 3 秒内页面发生跳转,那么这个等待对话框就不会出现,否则会弹出提示用户等待,从而及时的将响应反馈给用户,减少用户空白等待时间。
## 扫描下方二维码,关注微信公众号:H5开讲啦,获取更多学习资料。
![qr.jpg](https://upload-images.jianshu.io/upload_images/4834364-6ea6871a8bc84f25.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)