雄关漫道真如铁,而今迈步从头越。
全部博文(348)
分类: 架构设计与优化
2015-03-28 21:24:21
本博文主要介绍了Web前端的一些优化的方法和思路,这些技术秉承了前人的思想,我只是负责吸收和传播。这些技术一般也都是大型网站的技术经验,然而小规模的网站由于资源限制,某种程度上更需要优化。本文将逐个讲解在优化网站前端的过程中遇到的问题,思考的过程以及解决的方法,由于本文作者的水平有限,如果有错误和误解的地方,强烈欢迎您来批评指正。
一、JavaScript 模块化误区
加快 JavaScript 加载和执行的速度,一直是一个优化的热点。现在我们通过实践来体现模块化技术在使用时的注意事项,避免滥用。
I. 为什么会有模块化技术?
长久以来,编写 JavaScript 一直以文件为单位,一般一个类型的 JavaScript 功能代码会被放在同一个文件里。在一个页面里,引用的文件一般是写死的,也就是不管页面用不用,只要你引入了这个文件,这个文件就会被加载。举个例子,我们开发了一个内容复杂、功能强大的页面,JavaScript 文件大到 500K,当页面费劲的把这 500K 加载下来,然而用户真正只使用了这 500K 里极少的一部分功能,但我们又不得不把这 500K 加载下来,因为不同的用户使用的功能点可能不一样,我们必须满足所有需求。而模块化技术提出按需加载,也就是当用户触发该功能的时候,那个功能才真正的被加载。好比 500K 被拆成了 50 个模块,每个模块 10K,当用户触发一个功能时,加载 10K,再触发再加载,以这样懒加载的方式来加载模块,可以很大的提高响应速度。这样,管理模块懒加载的技术也随之诞生。
II. 模块化技术并非到处靠谱
笔者在优化web站点时,也想到了利用模块化技术来优化 JavaScript 的想法。上网搜索和了解一番,被一个模块化的技术所吸引:SeaJS,它是一个遵循 CommonJS 规范的 JavaScript 模块加载框架,可以实现 JavaScript 的模块化开发及加载机制。与 JQuery 等 JavaScript 框架不同,SeaJS 不会扩展封装语言特性,而只是实现 JavaScript 的模块化及按模块加载。SeaJS 的主要目的是令 JavaScript 开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的 JavaScript 文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。说白了就是有 Lazy Load 的特性,用到某模块时,SeaJS 才会去加载模块的 JS 文件。我们可以按功能划分多个模块,触发模块功能时,SeaJS 先加载功能模块的文件,然后执行相应的功能。
这个 SeaJS 拥有的特性,初看非常吸引人,对于不是非常熟悉 JavaScript 的我,更是如获至宝,它可以说是新定义了一种开发和管理 JavaScript 文件的模式。遵循这个模式,你会享受起 JavaScript 的开发。实践证明,它也的确可以使 JavaScript 模块化,根据功能划分模块,每个模块对应一个 JavaScript 文件,当执行到模块的功能,或者你需要加载模块时,模块才会被下载,同时不会造成重复下载。这一切看起来如此的合理,如此的顺畅。但是我在使用后发现了一些问题:
1.由于网站功能相对简单,JavaScript 文件并不是非常大,过多的模块,反而会导致总加载的时间变多了。
2.由于是 Lazy Load 特性,不适合的模块划分导致网站出现反应慢的现象,原因是得先加载模块的文件,才能执行模块的功能。当网络情况不好时,该现象表现的更为严重。
可以说,问题出在了对新技术的不了解就使用,从而出现了问题,预期是 SeaJS 可以处理 JavaScript 优化的问题,因为它具有避免加载不必要模块的功能。然后在使用后我们发现了上面的问题,从而我们可以收获以下两点:
1.因地制宜,根据实际需要使用模块化技术,切勿跟风技术,在自己没有完全掌握时,谨慎用于产品中。
2.模块如果是懒加载的,粒度不能太小,也就是模块不能太小。
有了如上的觉悟后,我对模块重新划分。根据大功能,分为基础模块和功能模块,基础模块包括页面头部,尾部相关的公共部分,功能模块则根据前后台划分,前台部分,又根据具体的页面来划分,能合并到一起的,就合并成一个模块,增加粒度。结果 JavaScript 文件减少,也达到了预期的效果。
从实际体验来看,对于此种类型的小型网站来说,没有必要使用懒加载 JavaScript 的相关技术,因为本身 JavaScript 文件就不是非常大。因此在网页加载时,就可以先加载所需要的模块。避免不必要的延迟。
III.模块化方法总结:
根据网站的规模、团队的规模、自己对模块化技术的掌握程度来选择是否使用模块化技术。当你的团队预计或者已经发展到一个很大的规模,需要多人维护和开发自己的模块,那么模块化是个不错的选择。如果你的网站发展壮大到功能之间经常互相影响,大量可重用模块无组织,那么模块化就是一个最好的解决方案。当然,这一切都要取决于你是否可以 hold 住模块化技术。
二、 JavaScript 的位置问题
这一部分,我将讨论 JavaScript 的位置问题对网页网站性能的影响。
I. 为什么要考虑位置问题
为什么要考虑 JavaScript 的位置问题?其实不管是 CSS 还是 JavaScript,都需要考虑位置的问题,因为 HTML 的渲染和加载顺序是从上往下,也就是如果前面插入了 JavaScript 的引用,那么必须等到这个 JavaScript 下载完毕才会渲染后续的部分。因此 JavaScript 的插入位置就成为一个值得考虑的问题,因为不适合的位置可能引起渲染的延迟等,造成不好的用户体验。
II. 传统方案带来的问题和思考
CSS 放在头部,JavaScript 放在尾部,这是传统的经验,笔者开始也是这么做的,它的好处是可以让页面优先渲染, 从而页面可以快速显示。但是随着后期部署后,我们发现我们的使用方式会造成不好的用户体验,表现在:当页面已经渲染完毕时,我们立刻去使用网站的功能,很多时候会出现没有反应的现象,原因也很简单,就是 JavaScript 放在页面的尾部,还没来得及加载造成的。
这时候我们会纠结,假设只有这两种 JavaScript 放置方式,一种放在头部,一种放在尾部(我相信还有别的方式来解决这个问题,比如部分放在头部,部分放在尾部),一个牺牲了渲染速度,一个牺牲了用户体验。这样,我们就需要做一个权衡,而不是根据别人的经验就一定要放在尾部。根据实际经验,我发现对于我的网站来说,用户体验造成的问题,要远远大于页面渲染速度引起的问题。比如,经常由于网络原因(也可能是网站部署在虚拟主机的原因)导致页面显示后,要再过许久才有功能反馈。而我们将 JavaScript 放在头部,优先加载,反而没有造成太大的问题,二者得到了很好的平衡。因为我们的 JavaScript 并不是很大,不会造成太久的页面空白。
因此,JavaScript 放在尾部并不是绝对的,一定要根据实际的情况,包括功能特点、服务器网络情况等来综合考虑。为此,笔者写下一个自认为较为合理的位置选择方案,仅供参考。
主功能,需要及时反馈,如页面点击监听等 |
头部 |
由于需要及时反馈给用户,因此这段 JS 需要优先加载,自然应该放在头部 |
辅功能,不着急加载如 SNS 分享按钮,流量统计,后台任务等 |
尾部 |
由于是辅助功能因此这部分代码完全不用着急加载放在尾部自然合适不过了 |
从上面的分类介绍,我们也可以看出,将功能代码按类型归类到不同的 JavaScript 文件是多么的重要,比如应该放头部和应该放尾部的代码,最好不要合并在一起。
III. 总结:
在设计之初,开发人员就应该谨记不同功能代码放置到不同的 JavaScript 文件里,不要等到出问题要优化的时候再去整理和重构,这样会增加很多不必要的工作量。同时,这不仅仅是为自己工作负责,也是为后面要读你代码的新人负责。养成好的设计编码习惯,也是技术积累的一部分。最后再根据 JavaScript 文件的功能类型,来决定是放在页面的头部还是尾部。
三、图片 CSS Sprite 问题
前面的文章我们也介绍过 CSS Sprite 的使用,它可以减少小图片的数量,将很多小图片合并成大图片,然后用 CSS Sprite 显示图片的部分模块,减少图片的请求。本部分将讨论 CSS Sprite 在实际使用过程中可能遇到的问题。
I. CSS Sprite 缺点思考
其实从这个技术本身的特点我们也可以看出端倪,它是把很多图片合并到一张,好处是减少了请求数量,坏处自然是这个请求的加载时间会延长,同时这个请求的重要性也随之加大。就好比单台服务器好管理,但是服务器挂了影响就大了,而分布式多台服务器,挂一台也不会有巨大的影响,因此 CSS Sprite 技术也要合理的使用。笔者在之前的文章也提到过,一个技术的产生,对外宣传的永远都是它的优点,而它的缺点只有认真思考或亲自上手使用时才会渐有体会,因为官方不会出一个文档说自己的缺点。一个技术一定有其适用环境,滥用反而造成反效果。
II. 实例证实 CSS Sprite 并非到处靠谱
CSS Sprite 是一个很好的技术,当然我的网站也会用到,包括头部导航的背景,模块头部背景,一些小图标等,都放在了一张图片上,然后我信心十足的这么做了,但是问题也随之而来:网页框架显示之后,许多背景部分迟迟没有显示出来,包括头部,模块头部背景,小图片,过一会就出现用 CSS Sprite 的背景一次性显示出来的现象。原因也很好理解,因为我们把所有小图片并到了一张图片上,那么这个图片也就相对的大了许多,而 CSS 用到的图片和网页内容有时候是并行下载的,网页内容下载完毕,框架就会被渲染出来,而此时图片很可能还没下载下来,需要等到图片下载完毕,才可以渲染网页的各个背景。也就出现了图片一下载下来,相关的部分会一起渲染,形成所说的背景延迟现象。
这个本身没有性能的问题,但是却带来了不好的用户体验。对于我的网站来说,就出现了有内容没背景的现象,而且会经常出现。我本可以将部分的背景单独抽成图片,但是又造成图片多,网页加载慢的问题,那么如何平衡的解决这个问题呢?
实践发现,如果能让图片优先加载,虽然会出现短暂的页面空白,但是不影响展示,而且具有较好的用户体验。但是 CSS 用的图片和网页内容又是并行加载的,因此如何让 CSS 的图片在网页框架显示前就被加载,就变为了一个难题。由于我用到的 CSS Sprite 的图片并不多,而且一个网页里如果相同的图片被多次引用,浏览器却不会多次请求,而是会用本地的缓存。因此,我在网页的顶部,添加隐藏的图像标签,来加载 CSS 图片,CSS 链接在图像标签后面,也就是网页会等到图片加载完后加载 CSS,CSS 再请求图片。发现如果图片已经被加载过,就不会再加载,而是直接渲染页面,这就达到了 CSS 图片优先于 CSS 文件和网页内容加载的目的。而且这样还有一个好处,当网页渲染到隐藏的时,会优先的下载这个图片,而不是并行下载其他内容,这个图片也就加载的更快。实践表明,这样使用对于我们网站来说,不仅没有影响加载速度,而且可以使页面完全显示,不会出现延迟的现象,达到了较好的用户体验。
或许会有更好的方式、方法,但是从这个例子我们也可以看出,一个方案的使用,还是要因地制宜,根据自身的情况,来分析和选择优化方案,而不是生搬硬套,最终造成适得其反的效果。
III.总结
每当我们遇到一个问题,就会寻求解决方案,当我们发现一个方案可以解决问题时,一般会毫不犹豫的采用,殊不知新方案也可能会有新方案的缺点。特别是采用一个我们不熟悉的解决方案时(从搜索引擎搜索拷贝过来),一定要彻头彻尾的学习和了解原理,虽然这很难做到,但是又必须做到。否则的话只能是拆东补西,一个自己埋下的苦果,最后只能自己吃掉了。
四、 因地制宜的懒加载
这一节,我将尝试讨论懒加载问题,这是一个传统的技术解决方案,可以帮助减少不必要的图片请求,然而我觉得完全没有必要限制住解决方法,灵活的应用和变通,就可以用最少的投入来获得最大的产出。
I.懒加载的目的:减少压力 or 用户体验?
我们首先要清楚懒加载的目的,大部分实现懒加载的网站目的是为了减少对自己服务器的请求次数,减轻服务器压力。而另外一部分是为了改善用户体验,因为同时加载大量的图片会造成网页延迟。根据具体的目的,来设计适合自己的懒加载,个人觉得对整个网站正常运行及优化都非常重要。
II.因地制宜
在我们的网站部署后,我们发现由于首页很长,同时有许多的图片(链接到淘宝的图片),很多时候,想看底部的内容,总要等许久,原因是图片很多时,需要等图片下载完毕,后面的内容才会显示。然而我们希望的是尽快的显示网页内容的框架,淘宝的图片不能影响网页内容的加载,这也是考虑到我们网站的带宽和计算能力有限,所以需要优先加载框架,然后,淘宝图片加载的速度肯定是很快的。因此,我们网站的内容和淘宝图片要分开处理。 刚开始,我和大家采用一样的思路:传统的懒加载策略,这其实可以解决这方面的问题。一开始不显示图片,当用户往下拖拽时,逐步的加载和显示图片,我们也的确这么实现了,但是实际使用后,发现效果并不好,表现在:
1. 当用户匀速一直下拉网页时,网页图片的加载时常有卡顿现象,原因是促发的多个图片批量下载导致的;
2.对网页上下移动的控制要求很高,在频繁的上下移动、快速移动后,图片有时无法加载;
3.研究一些网站的懒加载效果后发现,许多网站的懒加载,只是模拟的虚假现象,也就是网站并没有实现随着用户拖拽才去下载图片。当用户往下拽时,其实图片已经加载完毕了,它只是负责渐变展示,但这不是我们要的效果。
我们其实可以改进网站,将技术问题攻克。然而我们实际实验之后发现,针对我们网站,我们没有必要把问题想的复杂, 就好像很多网站只是将图片渐变显示,也没有实现真正的懒加载一样,我们可以另辟蹊径,寻找简单可用的策略。后来我们只做了这样的操作:所有的 img 标签,定义为:“wait.png” oriSrc=“真实图片地址”>,然后在网页 onload 事件后,设置 2 秒的定时器:setTimeout(function(){var im = $(“img[src=‘wait.png’]”); im.attr(“src”, im.attr(“oriSrc”)); }, 2000); 代码和原理很简单,先让图片都显示等待,由于同一张图片被多次引用是不会被重复加载的,所以这样没问题。然后在网页加载 2 秒后显示所有的图片。就这样简单的设计,我们通过实验证明,网页框架被快速的显示,由于图片来自淘宝,加载也很快。同时,提示性的等待图片加载,主动的将响应信息反馈给用户,也是一种用户体验的提升,总体来说已经达到了理想中的效果。
III.总结:
从网站的角度,我们总希望对用户说“只拿你要拿的”,这样既减少了我的负担,也满足了你的需要,懒加载就是为了这个目的而出现。但是一切东西,都不能太追求极致,在达到自己所要效果的情况下,多给用户一点,也就是多给用户提供一些选择。从这个案例,我们总结了两点:
1. 不要过分追求技术的难度,按需灵活改变方案;
2. 当遇到技术困难时,我们完全可以另辟蹊径,根据用户需求,具体问题具体对待。
IV.适当的提示掩盖问题
或许你读到这里,会觉得这篇更像解决用户体验问题的文章,但是笔者觉得,前端优化的最终目的,就是为了改善用户体验,任何基于用户体验的方案,都可以叫前端优化技术。
由于我们网站使用的是低廉的虚拟主机,这就避免不了有时响应速度慢的问题,因此纠结于什么技术来解决是不实际的,然而我们用一些小技巧,也可以改善用户等待的体验。比如,我们为每个非_target 的超链接或者表单的点击,添加点击事件处理,处理逻辑也很简单,就是设置 3 秒超时,显示等待对话框。如果在 3 秒内页面发生跳转,那么这个等待对话框就不会出现,否则会弹出提示用户等待,从而及时的将响应反馈给用户,减少用户空白等待时间。
这并不是什么高深的技术,但是运用这个技巧,却可以大大的改善用户体验,我们优化的目的,就是不要让用户一直得不到响应,避免空白等待,让用户体验越来越好。
五、结束语
本文介绍了笔者在优化某网站前端时遇到的具体问题,以及如何思考和如何解决问题。文中一些问题处理方式和传统的解决方案并不是完全一致,或者完全推翻了前人的做法,也许这种做法不一定是最好的,但是它还是满足了网站的基本需求,完善了用户体验。所以,解决问题,读者不应该只追求新技术和热门技术,正所谓满足需求、适合自己的才是最好的!本文笔者水平有限,欢迎批评指正。