Java Web 高性能开发,第 3 部分: 网站优化实战

时间:2022-12-07 02:51:03

这个系列的前两篇,介绍了前端的优化技术,这些技术秉承了前人至高无上的智慧,我只是负责吸收和传播。然而,这些技术一般也都是某某大型网站的技术经验,我们大部分人或许只能接触到相对小规模的网站,小规模的网站由于资源限制,某种程度上更需要优化,而传统的优化经验,是否就一定适合呢?不一定!

查看本系列更多内容 | 0Java Web 高性能开发,第 3 部分: 网站优化实战 评论:


, 软件工程师, 阿里巴巴

2014 年 12 月 31 日

  • Java Web 高性能开发,第 3 部分: 网站优化实战内容
Java Web 高性能开发,第 3 部分: 网站优化实战

在 IBM Bluemix 云平台上开发并部署您的下一个应用。

现在就开始免费试用

引言

按照本系列上篇文章的规划,本来是要继续讲解服务器缓存之类的内容,然而我觉得前端的内容还欠缺实践的部分,这部分的价值甚至更大,如果没有出乎意料的所得,讲解也就索然无味了。

本文讲解的是笔者网站优化的一个实战,有许多部分和传统的优化技术是不一样的,传统的优化技术没有问题,只是不一定适用任何情况,读者可以从本文了解到理解以及合理的利用优化技术是多么的重要。

回页首

文章内容概要

本文的目标优化网站,名为选礼 365(www.xuanli365.com,注:2013 年开始,我不再维护这个网站,所以可能文章内容会与现在的选礼
365 有些许出入,页面也可能发生改变,请读者见谅), 是一个小型商品导航网站,本文将按如下列表的顺序分别讲述我针对这个网站进行优化的过程。分析开发过程中遇到的问题,以及优化技术的选取原因。

文章将逐个讲解在优化网站前端的过程中遇到的问题,思考的过程以及解决的方法,由于本文作者的水平有限,如果有错误和误解的地方,强烈欢迎您来批评指正。

回页首

JavaScript 模块化误区

加快 JavaScript 加载和执行的速度,一直是一个优化的热点。因此第一节,主要讲述 JavaScript 模块化技术的相关知识,希望通过实践来体现模块化技术在使用时的注意事项,避免滥用。

为什么会有模块化技术

长久以来,编写 JavaScript 一直以文件为单位,一般一个类型的 JavaScript 功能代码会被放在同一个文件里。在一个页面里,引用的文件一般是写死的,也就是不管页面用不用,只要你引入了这个文件,这个文件就会被加载。举个例子,我们开发了一个内容复杂、功能强大的页面,JavaScript 文件大到 500K,当页面费劲的把这 500K 加载下来,然而用户真正只使用了这 500K 里极少的一部分功能,但我们又不得不把这 500K 加载下来,因为不同的用户使用的功能点可能不一样,我们必须满足所有需求。而模块化技术提出按需加载,也就是当用户触发该功能的时候,那个功能才真正的被加载。好比
500K 被拆成了 50 个模块,每个模块 10K,当用户触发一个功能时,加载 10K,再触发再加载,以这样懒加载的方式来加载模块,可以很大的提高响应速度。这样,管理模块懒加载的技术也随之诞生。

模块化技术并非到处靠谱

笔者在开发选礼 365 网后,也想到了利用模块化技术来优化 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 文件就不是非常大。因此在网页加载时,就可以先加载所需要的模块。避免不必要的延迟。

本节小述:

根据网站的规模、团队的规模、自己对模块化技术的掌握程度来选择是否使用模块化技术。当你的团队预计或者已经发展到一个很大的规模,需要多人维护和开发自己的模块,那么模块化是个不错的选择。如果你的网站发展壮大到功能之间经常互相影响,大量可重用模块无组织,那么模块化就是一个最好的解决方案。当然,这一切都要取决于你是否可以 hold 住模块化技术。

回页首

JavaScript 的位置问题

这一部分,我将讨论 JavaScript 的位置问题对网页网站性能的影响。

为什么要考虑位置问题

为什么要考虑 JavaScript 的位置问题?其实不管是 CSS 还是 JavaScript,都需要考虑位置的问题,因为 HTML 的渲染和加载顺序是从上往下,也就是如果前面插入了 JavaScript 的引用,那么必须等到这个 JavaScript 下载完毕才会渲染后续的部分。因此 JavaScript 的插入位置就成为一个值得考虑的问题,因为不适合的位置可能引起渲染的延迟等,造成不好的用户体验。

传统方案带来的问题和思考

CSS 放在头部,JavaScript 放在尾部,这是传统的经验,笔者开始也是这么做的,它的好处是可以让页面优先渲染, 从而页面可以快速显示。但是随着后期部署后,我们发现我们的使用方式会造成不好的用户体验,表现在:当页面已经渲染完毕时,我们立刻去使用网站的功能,很多时候会出现没有反应的现象,原因也很简单,就是 JavaScript 放在页面的尾部,还没来得及加载造成的。

这时候我们会纠结,假设只有这两种 JavaScript 放置方式,一种放在头部,一种放在尾部(我相信还有别的方式来解决这个问题,比如部分放在头部,部分放在尾部),一个牺牲了渲染速度,一个牺牲了用户体验。这样,我们就需要做一个权衡,而不是根据别人的经验就一定要放在尾部。根据实际经验,我发现对于我的网站来说,用户体验造成的问题,要远远大于页面渲染速度引起的问题。比如,经常由于网络原因(也可能是网站部署在虚拟主机的原因)导致页面显示后,要再过许久才有功能反馈。而我们将 JavaScript 放在头部,优先加载,反而没有造成太大的问题,二者得到了很好的平衡。因为我们的
JavaScript 并不是很大,不会造成太久的页面空白。

因此,JavaScript 放在尾部并不是绝对的,一定要根据实际的情况,包括功能特点、服务器网络情况等来综合考虑。为此,笔者写下一个自认为较为合理的位置选择方案,仅供参考。

表 1. 判断 JavaScript 放置位置决策表
功能 位置 原因
主功能,需要及时反馈,如页面点击监听等 头部 由于需要及时反馈给用户,因此这段 JS 需要优先加载,自然应该放在头部。
辅功能,不着急加载,如 SNS 分享按钮,流量统计,后台任务等 尾部 由于是辅助功能,因此这部分代码完全不用着急加载,放在尾部自然合适不过了。

从上面的分类介绍,我们也可以看出,将功能代码按类型归类到不同的 JavaScript 文件是多么的重要,比如应该放头部和应该放尾部的代码,最好不要合并在一起。

本节小述:

在设计之初,开发人员就应该谨记不同功能代码放置到不同的 JavaScript 文件里,不要等到出问题要优化的时候再去整理和重构,这样会增加很多不必要的工作量。同时,这不仅仅是为自己工作负责,也是为后面要读你代码的新人负责。养成好的设计编码习惯,也是技术积累的一部分。最后再根据 JavaScript 文件的功能类型,来决定是放在页面的头部还是尾部。

回页首

图片 CSS Sprite 问题

前面的文章我们也介绍过 CSS Sprite 的使用(点击这里查看),它可以减少小图片的数量,将很多小图片合并成大图片,然后用
CSS Sprite 显示图片的部分模块,减少图片的请求。这一节我们将讨论 CSS Sprite 在实际使用过程中可能遇到的问题。

CSS Sprite 缺点思考

其实从这个技术本身的特点我们也可以看出端倪,它是把很多图片合并到一张,好处是减少了请求数量,坏处自然是这个请求的加载时间会延长,同时这个请求的重要性也随之加大。就好比单台服务器好管理,但是服务器挂了影响就大了,而分布式多台服务器,挂一台也不会有巨大的影响,因此 CSS Sprite 技术也要合理的使用。笔者在之前的文章也提到过,一个技术的产生,对外宣传的永远都是它的优点,而它的缺点只有认真思考或亲自上手使用时才会渐有体会,因为官方不会出一个文档说自己的缺点。一个技术一定有其适用环境,滥用反而造成反效果。

实例证实 CSS Sprite 并非到处靠谱

CSS Sprite 是一个很好的技术,当然我的网站也会用到,包括头部导航的背景,模块头部背景,一些小图标等,都放在了一张图片上,然后我信心十足的这么做了,但是问题也随之而来:网页框架显示之后,许多背景部分迟迟没有显示出来,包括头部,模块头部背景,小图片,过一会就出现用 CSS Sprite 的背景一次性显示出来的现象。原因也很好理解,因为我们把所有小图片并到了一张图片上,那么这个图片也就相对的大了许多,而 CSS 用到的图片和网页内容有时候是并行下载的,网页内容下载完毕,框架就会被渲染出来,而此时图片很可能还没下载下来,需要等到图片下载完毕,才可以渲染网页的各个背景。也就出现了图片一下载下来,相关的部分会一起渲染,形成所说的背景延迟现象。

这个本身没有性能的问题,但是却带来了不好的用户体验。对于我的网站来说,就出现了有内容没背景的现象,而且会经常出现。我本可以将部分的背景单独抽成图片,但是又造成图片多,网页加载慢的问题,那么如何平衡的解决这个问题呢?

实践发现,如果能让图片优先加载,虽然会出现短暂的页面空白,但是不影响展示,而且具有较好的用户体验。但是 CSS 用的图片和网页内容又是并行加载的,因此如何让 CSS 的图片在网页框架显示前就被加载,就变为了一个难题。由于我用到的 CSS Sprite 的图片并不多,而且一个网页里如果相同的图片被多次引用,浏览器却不会多次请求,而是会用本地的缓存。因此,我在网页的顶部,添加隐藏的<img>图像标签,来加载 CSS 图片,CSS 链接在<img>图像标签后面,也就是网页会等到图片加载完后加载 CSS,CSS 再请求图片。发现如果图片已经被加载过,就不会再加载,而是直接渲染页面,这就达到了
CSS 图片优先于 CSS 文件和网页内容加载的目的。而且这样还有一个好处,当网页渲染到隐藏的<img>时,会优先的下载这个图片,而不是并行下载其他内容,这个图片也就加载的更快。实践表明,这样使用对于我们网站来说,不仅没有影响加载速度,而且可以使页面完全显示,不会出现延迟的现象,达到了较好的用户体验。

或许会有更好的方式、方法,但是从这个例子我们也可以看出,一个方案的使用,还是要因地制宜,根据自身的情况,来分析和选择优化方案,而不是生搬硬套,最终造成适得其反的效果。

本节小述:

每当我们遇到一个问题,就会寻求解决方案,当我们发现一个方案可以解决问题时,一般会毫不犹豫的采用,殊不知新方案也可能会有新方案的缺点。特别是采用一个我们不熟悉的解决方案时(从搜索引擎搜索拷贝过来),一定要彻头彻尾的学习和了解原理,虽然这很难做到,但是又必须做到。否则的话只能是拆东补西,一个自己埋下的苦果,最后只能自己吃掉了。

回页首

因地制宜的懒加载

这一节,我将尝试讨论懒加载问题,这是一个传统的技术解决方案,可以帮助减少不必要的图片请求,然而我觉得完全没有必要限制住解决方法,灵活的应用和变通,就可以用最少的投入来获得最大的产出。

懒加载的目的:减少压力 or 用户体验?

我们首先要清楚懒加载的目的,大部分实现懒加载的网站目的是为了减少对自己服务器的请求次数,减轻服务器压力。而另外一部分是为了改善用户体验,因为同时加载大量的图片会造成网页延迟。根据具体的目的,来设计适合自己的懒加载,个人觉得对整个网站正常运行及优化都非常重要。

因地制宜

在我们的网站部署后,我们发现由于首页很长,同时有许多的图片(链接到淘宝的图片),很多时候,想看底部的内容,总要等许久,原因是图片很多时,需要等图片下载完毕,后面的内容才会显示。然而我们希望的是尽快的显示网页内容的框架,淘宝的图片不能影响网页内容的加载,这也是考虑到我们网站的带宽和计算能力有限,所以需要优先加载框架,然后,淘宝图片加载的速度肯定是很快的。因此,我们网站的内容和淘宝图片要分开处理。 刚开始,我和大家采用一样的思路:传统的懒加载策略,这其实可以解决这方面的问题。一开始不显示图片,当用户往下拖拽时,逐步的加载和显示图片,我们也的确这么实现了,但是实际使用后,发现效果并不好,表现在:

  1. 当用户匀速一直下拉网页时,网页图片的加载时常有卡顿现象,原因是促发的多个图片批量下载导致的;
  2. 对网页上下移动的控制要求很高,在频繁的上下移动、快速移动后,图片有时无法加载;
  3. 研究一些网站的懒加载效果后发现,许多网站的懒加载,只是模拟的虚假现象,也就是网站并没有实现随着用户拖拽才去下载图片。当用户往下拽时,其实图片已经加载完毕了,它只是负责渐变展示,但这不是我们要的效果。

我们其实可以改进网站,将技术问题攻克。然而我们实际实验之后发现,针对我们网站,我们没有必要把问题想的复杂, 就好像很多网站只是将图片渐变显示,也没有实现真正的懒加载一样,我们可以另辟蹊径,寻找简单可用的策略。后来我们只做了这样的操作:所有的 img 标签,定义为:<img src=“wait.png” oriSrc=“真实图片地址”>,然后在网页 onload 事件后,设置 2 秒的定时器:setTimeout(function(){var im = $(“img[src=‘wait.png’]”); im.attr(“src”,
im.attr(“oriSrc”)); }, 2000); 代码和原理很简单,先让图片都显示等待,由于同一张图片被多次引用是不会被重复加载的,所以这样没问题。然后在网页加载 2 秒后显示所有的图片。就这样简单的设计,我们通过实验证明,网页框架被快速的显示,由于图片来自淘宝,加载也很快。同时,提示性的等待图片加载,主动的将响应信息反馈给用户,也是一种用户体验的提升,总体来说已经达到了理想中的效果。

本节小述:

从网站的角度,我们总希望对用户说“只拿你要拿的”,这样既减少了我的负担,也满足了你的需要,懒加载就是为了这个目的而出现。但是一切东西,都不能太追求极致,在达到自己所要效果的情况下,多给用户一点,也就是多给用户提供一些选择。从这个案例,我们总结了两点:

  1. 不要过分追求技术的难度,按需灵活改变方案;
  2. 当遇到技术困难时,我们完全可以另辟蹊径,根据用户需求,具体问题具体对待。

回页首

适当的提示掩盖问题

或许你读到这里,会觉得这篇更像解决用户体验问题的文章,但是笔者觉得,前端优化的最终目的,就是为了改善用户体验,任何基于用户体验的方案,都可以叫前端优化技术。

由于我们网站使用的是低廉的虚拟主机,这就避免不了有时响应速度慢的问题,因此纠结于什么技术来解决是不实际的,然而我们用一些小技巧,也可以改善用户等待的体验。比如,我们为每个非_target 的超链接或者表单的点击,添加点击事件处理,处理逻辑也很简单,就是设置 3 秒超时,显示等待对话框。如果在 3 秒内页面发生跳转,那么这个等待对话框就不会出现,否则会弹出提示用户等待,从而及时的将响应反馈给用户,减少用户空白等待时间。

这并不是什么高深的技术,但是运用这个技巧,却可以大大的改善用户体验,我们优化的目的,就是不要让用户一直得不到响应,避免空白等待,让用户体验越来越好。

回页首

结束语

本文介绍了笔者在优化某网站前端时遇到的具体问题,以及如何思考和如何解决问题。文中一些问题处理方式和传统的解决方案并不是完全一致,或者完全推翻了前人的做法,也许这种做法不一定是最好的,但是它还是满足了网站的基本需求,完善了用户体验。所以,解决问题,读者不应该只追求新技术和热门技术,正所谓满足需求、适合自己的才是最好的!本文笔者水平有限,欢迎批评指正。

参考资料

学习

讨论

  • 加入 developerWorks 中文社区,查看开发人员推动的博客、论坛、组和维基,并与其他
    developerWorks 用户交流。

from: http://www.ibm.com/developerworks/cn/java/j-lo-javawebhiperf3/index.html