网站js 打开新页面为什么这么慢, 而且图片大小也很小,哪方面需要优化??, js文件也很少啊

更新日期:日 分类:
优化的重要指标:
页面打开速度(Fully Loaded)
网站首页(或列表页)之 First View :打开速度应在 3秒+0.5秒 内;
对 Repeat View 时的各项指标暂不作要求;
首屏打开时间(Start Render)
网站首页(或列表页) 之 First View&:首屏渲染速度应在 1秒+0.5秒 内;
文档解析完毕时间(Document Complete):
对此指标暂不作要求。
指标测试方法参考附录A。
遵循常规优化建议
外联内联js/css的位置摆放建议
combo handler的引入
图片无损压缩的优化
减少 dom elements 的数量
引入 textarea/script 元素做延迟解析异步渲染
优化第一阶段:遵循常规优化建议
本阶段所使用工具参考附录B。
常规优化建议:
javascript 外联文件引用放在 html 文档底部:具体如何摆放内联JS/CSS和外联JS/CSS,请参考 优化第二阶段;
css 外联文件引用在 html 文档头部:位于 header 内;
http 静态资源尽量用多个子域名:充分利用现代浏览器的多线程并发下载能力。浏览器的多线程下载能力,参考 附录C;
具体建议:
JS、CSS、CSS背景图片、CSSSprite图片分散在 s0~ 下;
业务类图片分散在 p0~ 下;
服务器端提供 html 文档和 http 静态资源时,尽量开启 gzip 压缩;
json/xml 等格式的文本响应,也建议开启 gzip ;
jepg/png 等图片,可以选择不开启 gzip,因为可能服务器端图片无损压缩算法已经很强悍了,不需要依赖于 gzip;
在 js、css、image 等资源响应的 http headers 里,设置 expires、last-modified;
含义参考 附录D;
实例示范:
Nginx设置示范:
location ~ .*\.(js|css)${
expires 30d;
location ~ .*\.(gif|jpg|jpeg|png|bmp)${
expires 1M;
尽量减少 HTTP Requests 的数量;
通过 combo handler 合并 js 和 css 的下载,参考 优化第三阶段;
本阶段请尽量减少外联 js/css 文件数,尽量减少 ajax 调用;
js/css 的 minify:可统一通过 combo handler 做到压缩加合并;
减少不必要的 301/302 跳转:别让页面打开时间浪费在302多次跳转上(每次可能几十毫秒);
请大量使用 CSS Sprites:这样做可以大大地减少CSS背景图片的HTTP请求次数;
首屏不需要展示的较大尺寸图片,请使用 LazyLoad&;
避免404错误:请求一个外联 js 失败时获得的404错误,不但会堵塞并行的下载,而且浏览器会尝试分析404响应的内容,来辨识它是否是有用的Javascript代码;
减少 cookies 的大小:尽量减少 cookies 的体积对减少用户获得响应的时间十分重要;
去除不必要的 cookie&;
尽量减少 cookie 的大小;
留心将 cookie 设置在适当的域名下,避免影响到其他网站;
设置适当的过期时间。一个较早的过期时间或者不设置过期时间会更快地删除 cookie,从而减少响应时间。
使用无 cookies 的域:
当浏览其请求一个静态图片并一同发送 cookie 时,服务器并不需要这些 cookies 。这样只是毫无益处地创建了多余的网络流量。应当保证静态资源在请求时没有携带 cookies,所以需要把你的静态资源放在另一个子域名下。
如果你的域名是 www.example.org,你可以将你的静态资源放在 static.example.org 中。如果你把 cookie 设置在顶级域名 example.org 上而不是 www.example.org,那么所有发送至 static.example.org 的请求会包括那些 cookies。在 这种情况下,你可以用一个全新的没有 cookie 的域名来放置你的静态资源。
避免使用 javascript 来定位布局;
优化第二阶段:外联内联js/css的位置摆放建议
玉伯定义的加载和阻塞三定律如下:
定律一:资源是否下载依赖 JS 执行结果&&JS 有可能会修改 DOM。典型的,可能会有&document.write。这意味着,在当前 JS 加载和执行完成前,后续所有资源的下载有可能是没必要的。这是 JS 阻塞后续资源下载的根本原因。
定律二:JS 执行依赖 CSS 最新渲染&&JS 的执行有可能依赖最新样式。比如,可能会有&var width = $('#id').width(). 这意味着,JS 代码在执行前,浏览器必须保证在此 JS 之前的所有 css(无论外链还是内嵌)都已下载和解析完成。这是 CSS 阻塞后续 JS 执行的根本原因。
定律三:现代浏览器存在 prefetch 优化&&&现代浏览器在竞争中,在 UI update 线程之外,还会开启另一个线程,对后续 JS 和 CSS 提前下载(注意,仅提前下载,并不执行)。有了 prefetch 优化,这意味着,在不存在任何阻塞的情况下,理论上 JS 和 CSS 的下载时机都非常优先,和位置无关。
根据三定律,认为:
一,如果真的需要把外联js和css放在head里,那也需要从下面这种排序
不要script和css交替混编。
原因是,据有人称:『
从Firefox 4+开始,对prefetch的策略有些许调整,调整在于&head&中&css&与&js&的位置。
css&在外联&js&后面时,可能会在&script&后面的&css&加载好之前,提前进行首次渲染。
然后等后面的&css&加载好后,再次更新&Render Tree&并进行渲染,造成页面闪烁现象。
即,各种优化策略,似乎随着浏览器版本不同,头可能发生细微差异。
所以认为,保守做法是,js&和&css&不要在&head&里交替混编,统一为先外联css再外联js!
二,但只有万不得已时,才会在 head 里放外联js,否则请把外联js放置到&/body&前。
原因是张克军的《》指出:
& & 只要 head 里出现外联js,无论如何放,css文件都不能和body里的请求并行。
& &&body 里 dom 渲染取决于 head 里的js执行完。
外联js放在页面最后,高级浏览器会自动做优化(prefetch),你不用担心,它也可能会提前下载。
优化第三阶段:combo handler 的引入
Combo Handler 是 Yahoo! 开发的一个 Apache 模块,它实现了开发人员简单方便地通过URL来合并JavaScript和CSS文件,从而大大减少文件请求数。
它满足 Yahoo! 前端优化第一条原则:Minimize HTTP Requests,来减少三路握手和HTTP请求的发送次数。
淘宝网首页meta里多个js合并的声明:
& src="" data-fp-timestamp=""&&/&
js之间用英文逗号或&符号分隔。此src的Response是多个js文件的内容拼装。
国内的 Combo Script 支持
淘宝李晶-拔赤在
下发布了combo.php和minfy.php,能够做到合并文件(不压缩),以及合并且压缩。
文件列表:
- combo.php 合并文件,不压缩
- minify.php 合并压缩文件
- cssmin.php 压缩css
- jsmin.php 压缩js
- cb.php 淘宝CDN合并文件策略的模拟
- 要求php5及以上版本
- 程序在找不到本地文件的情况下,会去指定的cdn上找同名文件
- 程序会自动转义-min文件为源文件,因此要约定-min文件和原文件要成对出现
- 需要定义combo.php和minify.php中的$YOUR_CDN变量
- 如果只是合并压缩local文件,则不必重置$YOUR_CDN变量
- 这里提供cb.php,用来实现tbcdn的开发环境的模拟,apache的配置在cb.php中
CDN上的 Combo Handler支持
2)淘宝CDN支持Combo Handler,用逗号分隔js/css,用两个问号来触发combo特性:
&- /??1.js,2.js
&- /subdir/??1/js,2.js
用一个问号来添加时间戳,如:
- /??fp/directpromo.js?t=0712
为了避免 CDN 缓存错误的版本,combo上线的访问策略是:
1)静态文件传到服务器端;
2)部署人员使用线上静态文件服务器的IP地址直接请求combo服务,挨个儿combo请求一次;
3)部署人员确认上面的请求都成功、内容无误后,再换成CDN地址再次请求,确保CDN缓存正确的文件内容。
优化第四阶段:图片无损压缩的优化
页面上的各种图片是否有优化的余地,推荐使用&&检测一下。
下面的建议来自于冯凯。
由于专卖店等各种业务上传的图片有 jpeg、png 和 gif 等格式,因此三种格式都需要优化:
1)jpegtran和jpegoptim的压缩效果几乎完全相同。
但jpegtran有progressive编码(渐进式的展示,先显示模糊的,再逐步清晰),而且通常(84%的概率)对于大图片(10k+)压缩比更高。
虽然我们的大部分页面已经改成延迟加载了,但对于非延迟加载的页面,效果明显更好。
经测试,pagespeed 并没有按照 progressive 方式提供建议。
2)测试了png的几种压缩方式,压缩效果各异。测试的一张图片 optipng 只压缩了约5%,但其他几种达到了20%+
经测试,pagespeed上给出的可压缩比例是按照optipng给出的。
pngout据说采用了不同的编码,因此对小图片压缩效果更好。用imageoptim测试确实略优,但命令行上还没找到合适的调用参数。
目前决定采用pngcrush。
对于采用png8,以大幅压缩的方法,我们不做技术处理。
3)gif就采用gifsicle做压缩。
大部分情况下,我们不建议采用gif图片。对于单帧gif更应该用png格式替代。
这里我们暂不考虑通过技术处理来吧单帧gif转换成png。
4)采用php的exec调用shell脚本的方式来执行这些bin文件。
参考资料:
,幻灯片;
优化第五阶段:减少 DOM Elements 的数量
据,对于典型的淘宝商品详情页,经测试发现,每增加一个 DOM 节点,会导致首屏渲染时间延迟约 0.5ms。
2011年时,我们首页一个商品节点包含了21个DOM节点,充满了大量的em、strong、span。
所以前端开发部门必须与产品部交互设计人员积极沟通,而不仅仅是在她们提供的交互设计稿件上切图,必须在简化视觉元素和精简DOM节点上表达自己的意见。
2012年,简化设计后,首页一个商品节点包含13个节点。
优化第六阶段:引入 textarea/script 元素做延迟解析异步渲染
textarea 延迟渲染原理
据,HTML 元素中有一种 RCDATA elements,含 textarea 和 title 。
RCDATA指的是,Replaceable Character Data。
如果用隐藏的 textarea 来存放 html 代码,textarea 中的内容会按照 RCDATA 规则来解析:
遇到 & 时,会尽可能得到实体字符。
遇到 &/textarea(\s|\\|&) 时,会结束解析。
其他都直接作为 textarea 的内容。
获取也非常简单:
据,对于屏幕外延迟渲染的 html 存放在隐藏(visibility:hidden)的 textarea 中,并且该 textarea 占据本该渲染的位置,监控窗体滚动,当textarea进入可见区域,将该 textarea 内的 value, 插入到 textarea 之前,并删除掉 textarea&。
这样,把大量不需要在首屏展示的html代码分模块放入一个一个的 textarea 里,大大减少了DOM节点数,从而给浏览器合理的喘息(UI Update)时间,等首屏真正在显示器上绘制出来后,再得到 textarea.value ,填充回 DOM Tree。
textarea+datalazyload,相对于其他延迟加载异步渲染解决方案,最大好处,还是减少首屏绘制时的DOM节点总数。
参考资料:
玉伯(王保平,id@lifesinger)《》(需FQ)
BigRender所依赖的
yiminghe《》
script 延迟渲染原理&
玉伯在《》中提到,
与前面说的 textarea 存放 html 代码一样,你也可以用 script 来存放,目的都是减少 DOM 节点数。
浏览器在拿到 html 代码时,首次 Tokenization & Tree Construction 的速度就会大大加快。
某网是怎么实践的
在某网商品详情页上,HTML 文档底部遍布着这样的代码:
注意这些 script 的 type 是 text/x-template ,这是YUI类库自己定义的元素type。
你可以注意到,LABjs 也玩过这个小技巧,也是自己定义了一个元素 type&text/cache&, 由于浏览器不认识这种 type,就会主动忽略这个 HTML元素。
什么时候取出这些隐藏HTML代码呢?
那就要用到这些 script 的 id 了。
是这么获得 HTML 代码:
&&&&template: Y.one('#todo-item-template').getHTML(),
某网的做法是:
某网由于走的是 YUI3.0 体系,所以可以利用 script 存放html代码技巧,让商品详情页首屏更快地渲染出来。
我们的实践
在商品详情页上,我们把很多不需要首屏渲染的 html 代码放入了类似于
&textarea id="goodsAll_info" &style="display:"&&/textarea&
的隐藏 textarea 里了。
然后在 html 文档底部,放内联 js 来读取:
&script type="text/javascript"&
$(function(){
var area = document.getElementById("goodsAll_info").
document.getElementById("goodsAll_info_div").innerHTML =
&/script&&
附录A:页面打开速度和首屏打开时间的测量
推荐工具:
推荐使用&&评测,由于它受限于并发测试和带宽,所以资源下载速度较差,只能作为与竞争对手对比测试的依据;
Test Location 请选择亚洲的中国江苏节点;
Browser 请选择 Firefox、IE9、Chrome等现代浏览器;
如下图所示:
运维部的每周博睿检测数据报告,我们以博睿的数据为准;
博睿从它的各地监测节点以及不同电信链路访问,得到一个响应速度的平均值;
Google PageSpeed&&的 Critical Path Explorer;
附录B:能提出常规优化建议的工具
推荐工具:
Firefox插件 YSlow! ;
Google PageSpeed&&;
附录C:浏览器多线程下载能力一览
参考怪飞的文章《&》:
Firefox 3+
Chrome 11+
附录D:expires和last-modified概念
1)Expires
给出的日期/时间后,被响应认为是过时。如Expires: Thu, 02 Apr :08 GMT
需和Last-Modified结合使用。用于控制请求文件的有效时间,当请求数据在有效期内时客户端浏览器从缓存请求数据而不是服务器端. 当缓存中数据失效或过期,才决定从服务器更新数据。
2)Last-Modified和Expires
Last-Modified标识能够节省一点带宽,但是还是逃不掉发一个HTTP请求出去,而且要和Expires一起用。而Expires标识却使得浏览器干脆连HTTP请求都不用发,比如当用户F5或者点击Refresh按钮的时候就算对于有Expires的URI,一样也会发一个HTTP请求出去,所以,Last-Modified还是要用的,而 且要和Expires一起用。
赠图一枚:
:【看图】做事感觉特别困难的时候,可能收获也会特别巨大。
:每次看产品经理发来的文档,都是这种感觉。。。
阅读(...) 评论()wordpress博客优化的种类有很多,今天部落主要来讲一下关于js文件方面的优化,主要有JS文件压缩,JS文件合并,延迟加载和在网站的最后加载js文件。
合理优化网站中的JS文件,可以减少HTTP请求的次数和压缩文件的大小,能有效提升我们的网站加载速度,实际上,对于网站中的CSS样式文件,也可以按JS的操作方法来处理。
一,JS文件压缩
一般的服务器都开启gzip功能,另外还有一些wordpress插件也可以将您网站中的JS文件进行压缩操作,但很明显,如果仅仅是执行压缩,完全没必要使用插件,现在提供在线压缩的网站不少,我们可以直接将js内容复制上去,从而得到相应的压缩好的JS文件,这里部落推荐大家用开源中国提供的在线工具,地址是:http://tool.oschina.net/jscompress,圧缩过程如下图:
我们将压缩结果覆盖原来的文件就可以了。
二,JS文件合并
如果您的JS文件很多,那么建议您进行合并处理。当然,如果您的js文件大小都超过几M,那么合并可能反而会影响速度,只是这种容量超过几M的js文件很少见,大多是几十kb左右。
如何合并呢,简单的方法就是安装插件,现在支持这个功能的wordpress插件比较多,例如Better WordPress Minify,它会自动压缩合并您网站中的JS和CSS文件,部落在wordpress的官方网站上看到,这个插件一直在更新,不像Minify这个插件,已经很长时间没有进行过更新了。其它的还有JS & CSS Optimizer,Scripts Gzip等,您可以根据自已的要求选择一款适合自已的。
对于网站外部加载的js,部落暂时还没有找到好的方法。可能利用插件是目前唯一比较保险的方法,而内部JS,我们则可以根据在其网站中的调用顺利,自已手工将其合并到一个文件中,例如部落网站中原来有8个js,如下图:
现在将其减少到只有3个了,有朋友可能要问,为什么不合并到一个js文件中,这是因为另外两个js文件会冲突,导致功能不正常,所以如此。
当然,如果用Minify压缩合并JS工具来处理,可能可以实现所有的js完美融合到一个js文件中,如果您有兴趣,可以找找Minify这方面的教程。
JS文件合并后,您可以使用一些工具来检测您的网站加载速度,效果还是很明显的,如下图:
这是一张来自谷歌开发中心的截图,JS文件合并后,查询次数由7变成2,请求的字节由2681缩小为783了。部落合并后,可以在百度页面优化建议中看到,由于合并JS减少了0.6秒左右的加载时间。
不知道如何手工合并,或者手工合并出现js文件不能正常使用的情况,那么您可以试下这个方法:
1.新建一个的all.js.php文件,加入以下内容
require_once('目录/wp-blog-header.php' ) ;
include ("/js/a.js" ) ;
include ("/b.js" ) ;
放在您的当前主题下,用下面的代码来替换您之前的js代码:
&script xsrc="all.js.php" type="text/javascript"/&
三,JS延迟加载
关于延迟加载,部落之前介绍过两篇这样的文章,和您可以参考一下,道理基本上是一样的,JS延迟加载的代码如下:
&script language="JavaScript" src="" id="my"&&/script&
setTimeout("document.getElementById('my').src='include/php100.php'; ",2000);//延时2秒
上面代码中的2000代表2秒,您可以根据您的主机速度来进行调整。这个方法来源自网络,部落没有去验证,另外,现在大多数的外部js都可以用异步加载的方式,比如统计代码,的代码等,通过异步加载能有效提升网站加载速度,这些以后部落会再作介绍。
四,JS最后加载
这里先要说明一下,大多数时候,我们的header.php中都会存在一些js文件,而这里面有一些不一定是在一开始就非得要加载的,我们可以手工将网站中的JS代码移到footer.php这个文件中实现最后加载。
通过上面几个js处理,会让您的wordpress博客速度得到一定的提升,当然,如果想更快,那么最好的办法当然是换主机,不愿意的换的,没钱换的,也可以使用来进行加速,例如部落前几天介绍的百度加速乐提供的免费CDN加速效果就相当不错。
使用wordpress合并JS的插件,大多数都能同时对JS文件进行压缩处理,JS延迟加载的话,会将网站中需要加载的一些div段先展示出来,速度是并无提升,只是我们会先看到网页中的实体部分,JS最后加载和延迟加载是同样的道理,但能给用户一个好的体验。
文章如未注明来源,皆为免费部落原创,免费部落网站保留所有版权,如需转载,请注明文章转载自: 本文固定链接:
真的,我想您也会喜欢
随便找了点看您喜欢不
2013 年 12 月 3 日 11:34
楼上这个刚刚还在我那留了条LJ评论,网站的JS压缩和最后加载是能提升不少的速度
你的邮箱(必填)
你的网站(选填)
3 + 1 = ? 验证码
评论内容(必填)
有人回复时邮件通知我
Copyright &copy ; . All Rights Reserved |浙ICP备号|482,381 十二月 独立访问用户
您目前处于:
让我们再聊聊浏览器资源加载优化
让我们再聊聊浏览器资源加载优化
相关厂商内容
相关赞助商
全球软件开发大会,4月23-25日,北京,!
为什么要在标题中使用&再&这个字?因为在工作中逐渐发现,我们经常谈论的一些页面优化技巧,比如上面所说的总是把脚本放在页面的底部,压缩合并样式或者脚本文件等,时至今日已不再是最佳的解决方案,甚至事与愿违,转化为性能的毒药。这篇文章所要聊的,便是展示某些不被人关注的浏览器特性或者技巧,来继续完成资源加载性能优化的任务。
一. Preloader
什么是Preloader
首先让我们看一看这样一类资源分布的:
&link rel=&stylesheet& type=&text/css& href=&&&
&script type=&text/javascript&&&/script&
&img src=&&&
&img src=&&&
&img src=&&&
&img src=&&&
&img src=&&&
&img src=&&&
&img src=&&&
&img src=&&&
&script type=&text/javascript&&&/script&
&script type=&text/javascript&&&/script&
&script type=&text/javascript&&&/script&
这类页面的特点是,一个外链脚本置于页面头部,三个外链脚本置于页面的底部,并且是故意跟随在一系列img之后,在Chrome中页面加载的网络请求瀑布图如下:
值得注意的是,虽然脚本放置在图片之后,但加载仍先于图片。为什么会出现这样的情况?为什么故意置后资源能够提前得到加载?
虽然浏览器引擎的实现不同,但原理都十分的近似。不同浏览器的制造厂商们(vendor)非常清楚浏览器的瓶颈在哪(比如network, javascript evaluate, reflow, repaint)。针对这些问题,浏览器也在不断的进化,所以我们才能看到更快的脚本引擎,调用GPU的渲染等一推陈出新的优化技术和方案。
同样在资源加载上,早在IE8开始,一种叫做lookahead pre-parser(在Chrome中称为preloader)的机制就已经开始在不同浏览器中兴起。IE8相对于之前IE版本的提升除了将每台host最高并行下载的资源数从2提升至6,并且能够允许并行下载脚本文件之外,最后就是这个lookahead pre-parser机制
但我还是没有详述这是一个什么样的机制,不着急,首先看看与IE7的对比:
以上面的页面为例,我们看看IE7下的瀑布图:
底部的脚本并没有提前被加载,并且因为由于单个域名最高并行下载数2的限制,资源总是两个两个很整齐的错开并行下载。
但在IE8下,很明显底部脚本又被提前:
并没有统一的标准规定这套机制应具备何种功能已经如何实现。但你可以大致这么理解:浏览器通常会准备两个页面解析器parser,一个(main parser)用于正常的页面解析,而另一个(preloader)则试图去文档中搜寻更多需要加载的资源,但这里的资源通常仅限于外链的js、stylesheet、image;不包括audio、video等。并且动态插入页面的资源无效。
但细节方面却值得注意:
比如关于preloader的触发时机,并非与解析页面同时开始,而通常是在加载某个head中的外链脚本阻塞了main parser的情况下才启动;
也不是所有浏览器的preloader会把图片列为预加载的资源,可能它认为图片加载过于耗费带宽而不把它列为预加载资源之列;
preloader也并非最优,在某些浏览器中它会阻塞body的解析。因为有的浏览器将页面文档拆分为head和body两部分进行解析,在head没有解析完之前,body不会被解析。一旦在解析head的过程中触发了preloader,这无疑会导致head的解析时间过长。
Preloader在响应式设计中的问题
preloader的诞生本是出于一番好意,但好心也有可能办坏事。
filamentgroup有一种著名的响应式设计的图片解决方案:
&title&&/title&
&script type=&text/javascript& src=&./responsive-images.js&&&/script&
&img src=&./running.jpg?medium=_imgs/running.medium.jpg&large=_imgs/running.large.jpg&&
它的工作原理是,当responsive-images.js加载完成时,它会检测当前显示器的尺寸,并且设置一个cookie来标记当前尺寸。同时你需要在服务器端准备一个.htaccess文件,接下来当你请求图片时,.htaccess中的配置会检测随图片请求异同发送的Cookie是被设置成medium还是large,这样也就保证根据显示器的尺寸来加载对于的图片大小。
很明显这个方案成功的前提是,js执行先于发出图片请求。但在Chrome下打开,你会发现执行顺序是这样:
responsive-images.js和图片几乎是同一时间发出的请求。结果是第一次打开页面给出的是默认小图,如果你再次刷新页面,因为Cookie才设置成功,服务器返回的是大图。
严格意义上来说在某些浏览器中这不一定是preloader引起的问题,但preloader引起的问题类似:插入脚本的顺序和位置或许是开发者有意而为之的,但preloader的这种&聪明&却可能违背开发者的意图,造成偏差。
如果你觉得上一个例子还不够说明问题的话,最后请考虑使用(或者@srcset)元素的情况:
&source src=&med.jpg& media=&(min-width: 40em)& /&
&source src=&sm.jpg&/&
&img src=&fallback.jpg& alt=&& /&
&/picture&
在preloader搜寻到该元素并且试图去下载该资源时,它应该怎么办?一个正常的paser应该是在解析该元素时根据当时页面的渲染布局去下载,而当时这类工作不一定已经完成,preloader只是提前找到了该元素。退一步来说,即使不考虑页面渲染的情况,假设preloader在这种情形下会触发一种默认加载策略,那应该是&mobile first&还是&desktop first&?默认应该加载高清还是低清照片?
二. JS Loader
理想是丰满的,现实是骨感的。出于种种的原因,我们几乎从不直接在页面上插入js脚本,而是使用第三方的加载器,比如seajs或者requirejs。关于使用加载器和模块化开发的优势在这里不再赘述。但我想回到原点,讨论应该如何利用加载器,就从seajs与requirejs的不同聊起。
在开始之前我已经假设你对requirejs与seajs语法已经基本熟悉了,如果还没有,请移步这里:
BTW: 如果你还是习惯在部署上线前把所有js文件合并打包成一个文件,那么seajs和requirejs其实对你来说并无区别。
seajs与requirejs在模块的加载方面是没有差异的,无论是requirejs在定义模块时定义的依赖模块,还是seajs在factory函数中require的依赖模块,在会在加载当前模块时被载入,异步,并且顺序不可控。差异在于factory函数执行的时机。
为了增强对比,我们在定义依赖模块的时候,故意让它们的factory函数要执行相当长的时间,比如1秒:
// dep_A.js定义如下,dep_B、dep_C定义同理
define(function(require, exports, module) {
(function(second) {
var start = +new Date();
while (start + second * 1000 & +new Date()) {}
})(window.EXE_TIME);
// window.EXE_TIME = 1;此处会连续执行1s
exports.foo = function() {
console.log(&A&);
为了增强对比,设置了三组进行对照试验,分别是:
//require.js:
require([&dep_A&, &dep_B&, &dep_C&], function(A, B, C) {
define(function(require, exports, module) {
var mod_A = require(&dep_A&);
var mod_B = require(&dep_B&);
var mod_C = require(&dep_C&);
//sea.js(定义依赖但并不require):
define([&dep_A&, &dep_B&, &dep_C&], function(require, exports, module){
接下来我们看看代码执行的瀑布图:
1.require.js:在加载完依赖模块之后立即执行了该模块的factory函数
2.sea.js: 下面两张图应该放在一起比较。两处代码都同时加载了依赖模块,但因为没有require的关系,第三张图中没有像第二张图那样执行耗时的factory函数。可见seajs执行的原则正如CMD标准中所述Execution must be lazy。
我想进一步表达的是,无论requirejs和seajs,通常来说大部分的逻辑代码都会放在模块的factory函数中,所以factory函数执行的代价是非常大的。但上图也同样告诉我们模块的define,甚至模块文件的Evaluate代价非常小,与factory函数无关。所以我们是不是应该尽可能的避免执行factory函数,或者等到我们需要的指定功能的时候才执行对应的factory函数?比如:
document.body.onclick = function () {
require(some_kind_of_module);
这是非常实际的问题,比如爱奇艺一个视频播放的页面,我们有没有必要在第一屏加载页面的时候就加载登陆注册,或者评论,或者分享功能呢?因为有非常大的可能用户只是来这里看这个视频,直至看完视频它都不会用到登陆注册功能,也不会去分享这个视频等。加载这些功能不仅仅对浏览器是一个负担,还有可能调用后台的接口,这样的性能消耗是非常可观的。
我们可以把这样称之为&懒执行&。虽然seajs并非有意实现如上所说的&懒执行&(它只是在尽可能遵循CommonJS标准靠近)。但&懒执行&确实能够有助于提升一部分性能。
但也有人会对此产生顾虑。
记得玉伯转过的一个帖子:。我们看看其中反对这么做的人的:
我个人感觉requirejs更科学,所有依赖的模块要先执行好。如果A模块依赖B。当执行A中的某个操doSomething()后,再去依赖执行B模块require('B');如果B模块出错了,doSomething的操作如何回滚? 很多语言中的import, include, useing都是先将导入的类或者模块执行好。如果被导入的模块都有问题,有错误,执行当前模块有何意义?
而依赖dependencies是工厂的原材料,在工厂进行生产的时候,是先把原材料一次性都在它自己的工厂里加工好,还是把原材料的工厂搬到当前的factory来什么时候需要,什么时候加工,哪个整体时间效率更高?
首先回答第一个问题。
第一个问题的题设并不完全正确,&依赖&和&执行&的概念比较模糊。编程语言执行通常分为两个阶段,编译(compilation)和运行(runtime)。对于静态语言(比如C/C++)来说,在编译时如果出现错误,那可能之前的编译都视为无效,的确会出现描述中需要回滚或者重新编译的问题。但对于或者脚本语言,大部分执行都处在运行时阶段或者解释器中:假设我使用Nodejs或者Python写了一段服务器运行脚本,在持续运行了一段时间之后因为某项需求要加载某个(依赖)模块,同时也因为这个模块导致服务端挂了&&我认为这时并不存在回滚的问题。在加载依赖模块之前当前的模块的大部分功能已经成功运行了。
再回答第二个问题。
对于&工厂&和&原材料&的比喻不够恰当。难道依赖模块没有加载完毕当前模块就无法工作吗?requirejs的确是这样的,从上面的截图可以看出,依赖模块总是先于当前模块加载和执行完毕。但我们考虑一下基于CommonJS标准的Nodejs的语法,使用require函数加载依赖模块可以在页面的任何位置,可以只是在需要的时候。也就是说当前模块不必在依赖模块加载完毕后才执行。
你可能会问,为什么要拿AMD标准与CommonJS标准比较,而不是CMD标准?
玉伯在这篇文章中已经告诉了我们CMD某种程度上遵循的就是CommonJS标准:
从上面可以看出,Sea.js 的初衷是为了让 CommonJS Modules/1.1 的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。
更好的一种方式是,Sea.js 专注于 Web 浏览器端,CommonJS 则专注于服务器端,但两者有共通的部分。对于需要在两端都可以跑的模块,可以 有便捷的方案来快速迁移。
其实AMD标准的推出同时也是遵循CommonJS,在requirejs官方文档的中说道:
CommonJS defines a module format. Unfortunately, it was defined without giving browsers equal footing to other JavaScript environments. Because of that, there are CommonJS spec proposals for Transport formats and an asynchronous require.
RequireJS tries to keep with the spirit of CommonJS, with using string names to refer to dependencies, and to avoid modules defining global objects, but still allow coding a module format that works well natively in the browser.
CommonJS当然是一个理想的标准,但至少现阶段对浏览器来说还不够友好,所以才会出现AMD与CMD,其实他们都是在做同一件事,就是致力于前端代码更友好的模块化。所以个人认为依赖模块的加载和执行在不同标准下实现不同,可以理解为在用不同的方式在完成同一个目标, 并不是一件太值得过于纠结的事。
其实我们可以走的更远,对于非必须模块不仅仅可以延迟它的执行,甚至可以延迟它的加载。
但问题是我们如何决定一个模块是必须还是非必须呢,最恰当莫过取决于用户使用这个模块的概率有多少。Faceboook早在09年的时候就已经注意到这个问题:,只不过他们是以样式碎片来引出这个问题。
假设我们需要在页面上加入A、B、C三个功能,意味着我们需要引入A、B、C对应的html片段和样式碎片(暂不考虑js),并且最终把三个功能样式碎片在上线前压缩到同一个文件中。但可能过了相当长时间,我们移除了A功能,但这个时候大概不会有人记得也把关于A功能的样式从上线样式中移除。久而久之冗余的代码会变得越来越多。Facebook引入了一套静态资源管理方案(Static Resource Management)来解决这个问题:
具体来说是将样式的&声明&(Declaration)和请求(Delivery)请求,并且是否请求一个样式由是否拥有该功能的 html片段决定。
当然同时也考虑也会适当的合并样式片段,但这完全是基于使用算法对用户使用模块情况进行分析,挑选出使用频率比较高的模块进行拼合。
这一套系统不仅仅是对样式碎片,对js,对图片sprites的拼合同样有效。
你会不会觉得我上面说的懒加载还是离自己太远了? 但然不是,你去看看现在的人人网个人主页看看
如果你在点击图中标注的&与我相关&、&相册&、&分享&按钮并观察Chrome的Timeline工具,那么都是在点击之后才加载对应的模块
三. Delay Execution
利用浏览器缓存
脚本最致命的不是加载,而是执行。因为何时加载毕竟是可控的,甚至可以是异步的,比如通过调整外链的位置,动态的创建脚本。但一旦脚本加载完成,它就会被立即执行(Evaluate Script),页面的渲染也就随之停止,甚至导致在低端浏览器上假死。
更加充分的理由是,大部分的页面不是Single Page Application,不需要依靠脚本来初始化页面。服务器返回的页面是立即可用的,可以想象我们初始化脚本的时间都花在用户事件的绑定,页面信息的丰满(用户信息,个性推荐)。发现在Alexa上排名前十的美国网站上的js代码,只有29%在window.onload事件之前被调用,其他的71%的代码与页面的渲染无关。
Steve Souders的是我认为一直被忽视的一个加载器,它与Labjs一样能够控制的脚本的异步加载,甚至(包括行内脚本,但不完美)延迟执行。它延迟执行脚本的思路非常简单:既然只要在页面上插入脚本就会导致脚本的执行,那么在需要执行的时候才把脚本插入进页面。但这样一来脚本的加载也被延迟了?不,我们会通过其他元素来提前加载脚本,比如img或者是object标签,或者是非法的mine type的script标签。这样当真正的脚本被插入页面时,只会从缓存中读取。而不会发出新的请求。
在它的文章中详细描述了这个技巧, 如果判断浏览器是IE就是用image标签,如果是其他浏览器,则使用object元素:
window.onload = function () {
var i = 0,
preload = [
// list of stuff to preload
isIE = navigator.appName.indexOf('Microsoft') === 0;
for (i = 0, max = preload. i & i += 1) {
if (isIE) {
new Image().src = preload[i];
o = document.createElement('object');
o.data = preload[i];
// IE stuff, otherwise 0x0 is OK
//o.width = 1;
//o.height = 1;
//o.style.visibility = &hidden&;
//o.type = &text/plain&; // IE
o.height = 0;
// only FF appends to the head
// all others require body
document.body.appendChild(o);
同时它还列举了其他的一些尝试,但并非对所有的浏览器都有效,比如:
使用&link&元素加载script,这么做在Chrome中的风险是,在当前页有效,但是在以后打开需要使用该脚本的页面会无视该文件为缓存
改变script标签外链的type值,比如改为text/cache来阻止脚本的执行。这么做会导致在某些浏览器(比如FF3.6)中压根连请求都不会发出
type=prefetch
延迟执行并非仅仅作为当前页面的优化方案,还可以为用户可能打开的页面提前缓存资源,如果你对这两种类型的link元素熟悉的话:
&link rel=&subresource& href=&jquery.js&&: subresource类型用于加载当前页面将使用(但还未使用)的资源(预先载入缓存中),拥有较高优先级
&link rel=&prefetch& href=&http://NextPage.html&&: prefetch类型用于加载用户将会打开页面中使用到的资源,但优先级较低,也就意味着浏览器不做保证它能够加载到你指定的资源。
那么上一节延迟执行的方案就可以作为subresource与prefeth的回滚方案。同时还有其他的类型:
&link rel=&dns-prefetch& href=&//host_name_&&: dns-prefetch类型用于提前dns解析和缓存域名主机信息,以确保将来再请求同域名的资源时能够节省dns查找时间,比如我们可以看到淘宝首页就使用了这个类型的标签:
&link rel=&prerender& href=&http://example.org/index.html&&: prerender类型就比较霸道了,它告诉浏览器打开一个新的标签页(但不可见)来渲染指定页面,比如这个:
这也就意味着如果用户真的访问到该页面时,就会有&秒开&的用户体验。
但现实并非那么美好,首先你如何能预测用户打开的页面呢,这个功能更适合阅读或者论坛类型的网站,因为用户有很大的概率会往下翻页;要注意提前的渲染页面的网络请求和优先级和GPU使用权限优先级都比其他页面的要低,浏览器对提前渲染页面类型也有一定的要求,具体可以参考
利用LocalStorage
在聊如何用它来解决我们遇到的问题之前,个人觉得首先应该聊聊它的优势和劣势。
Chris Heilmann在文章中指出了一些常见的LS劣势,比如同步时可能会阻塞页面的渲染、I/O操作会引起不确定的延时、持久化机制会导致冗余的数据等。虽然Chirs在文章中用到了比如&terrible performance&, &slow&等字眼,但却没有真正的指出究竟是具体的哪一项操作导致了性能的低下。
Nicholas C. Zakas于是写了一篇针对该文的文章,从文章的名字就可以看出,Nicholas想要捍卫LS,毕竟它不是在上一文章中被描述的那样一无是处,不应该被抵制。
比较性能这种事情,应该看怎么比,和谁比。
就&读&数据而言,如果你把&从LS中读一个值&和&从Object对象中读一个属性&相比,是不公平的,前者是从硬盘里读,后者是从内存里读,就好比让汽车与飞机赛跑一样,有一个benchmark各位可以参考一下::
跑分的标准是OPS(operation per second),值当然是越高越好。你可能会注意到,在某个浏览器的对比列中,没有显示关于LS的红色列&&这不是因为统计出错,而是因为LS的操作性能太差,跑分太低(相对从Object中读取属性而言),所以无法显示在同一张表格内,如果你真的想看的话,可以给你看一张放大的版本:
这样以来你大概就知道两者在什么级别上了。
在浏览器中与LS最相近的机制莫过于Cookie了:Cookie同样以key-value的形式进行存储,同样需要进行I/O操作,同样需要对不同的tab标签进行同步。同样有benchmark可以供我们进行参考:
从Brwoserscope中提供的结果可以看出,就Reading from cookie, Reading from localStorage getItem, Writing to cookie,Writing to localStorage property四项操作而言,在不同浏览器不同平台,读和写的效率都不太相同,有的趋于一致,有的大相径庭。
甚至就LS自己而言,不同的存储方式和不同的读取方式也会产生效率方面的问题。有两个benchmark非常值得说明问题:
在第一个测试中,Nicholas在LS中用四个key分别存储了100个字符,500个字符,1000个字符和2000个字符。测试分别读取不同长度字符的速度。结果是:读取速度与读取字符的长度无关。
第二个测试用于测试读取1000个字符的速度,不同的是对照组是一次性读取1000个字符;而实验组是从10个key中(每个key存储100个字符)分10次读取。结论: 是分10此读取的速度会比一次性读取慢90%左右。
LS也并非没有痛点。大部分的LS都是基于同一个域名共享存储数据,所以当你在多个标签打开同一个域名下的站点时,必须面临一个同步的问题,当A标签想写入LS与B标签想从LS中读同时发生时,哪一个操作应该首先发生?为了保证数据的一致性,在读或者在写时 务必会把LS锁住(甚至在操作系统安装的杀毒软件在扫描到该文件时,会暂时锁住该文件)。因为单线程的关系,在等待LS I/O操作的同时,UI线程和Javascript也无法被执行。
但实际情况远比我们想象的复杂的多。为了提高读写的速度,某些浏览器(比如火狐)会在加载页面时就把该域名下LS数据加载入内存中,这么做的副作用是延迟了页面的加载速度。但如果不这么做而是在临时读写LS时再加载,同样有死锁浏览器的风险。并且把数据载入内存中也面临着将内存同步至硬盘的问题。
上面说到的这些问题大部分归咎于内部的实现,需要依赖浏览器开发者来改进。并且并非仅仅存在于LS中,相信在IndexedDB、webSQL甚至Cookie中也有类似的问题在发生。
考虑到移动端网络环境的不稳定,为了避免网络延迟(network latency),大部分网站的移动端站点会将体积庞大的类库存储于本地浏览器的LS中。但将这个技术也应用到了PC端,他们将所依赖的jQuery类库存入LS中。用一段很简单的来保证对jQuery的正确载入。我们一起来看看这段代码。代码详解就书写在注释中了:
!function (globals, document) {
var storagePrefix = &mbox_&;
globals.LocalJs = {
require: function (file, callback) {
如果无法使用localstorage,则使用document.write把需要请求的脚本写在页面上
作为fallback,使用document.write确保已经加载了所需要的类库
if (!localStorage.getItem(storagePrefix + &jq&)) {
document.write('&script src=&' + file + '& type=&text/javascript&&&/script&');
var self =
并且3s后再请求一次,但这次请求的目的是为了获取jquery源码,写入localstorage中(见下方的_loadjs函数)
这次&一定&走缓存,不会发出多余的请求
为什么会延迟3s执行?为了确保通过document.write请求jQuery已经加载完成。但很明显3s也并非一个保险的数值
同时使用document.write也是出于需要故意阻塞的原因,而无法为其添加回调,所以延时3s
setTimeout(function () {
self._loadJs(file, callback)
// 如果可以使用localstorage,则执行注入
this._reject(localStorage.getItem(storagePrefix + &jq&), callback)
_loadJs: function (file, callback) {
if (!file) {
return false
var self =
var xhr = new XMLHttpR
xhr.open(&GET&, file);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
localStorage.setItem(storagePrefix + &jq&, xhr.responseText)
xhr.send()
_reject: function (data, callback) {
var el = document.createElement(&script&);
el.type = &text/javascript&;
关于如何执行LS中的源码,我们有三种方式
2. new Function
3. 在一段script标签中插入源码,再将该script标签插入页码中
关于这三种方式的执行效率,我们内部初步测试的结果是不同的浏览器下效率各不相同
参考一些jsperf上的测试,执行效率甚至和具体代码有关。
el.appendChild(document.createTextNode(data));
document.getElementsByTagName(&head&)[0].appendChild(el);
callback && callback()
isSupport: function () {
return window.localStorage
}(window, document);
function () {
var url = _GET_HASHMAP ? _GET_HASHMAP(&/player/static/js/naga/common/jquery-1.7.2.js&) : &/player/static/js/naga/common/jquery-1.7.2.js&;
url = url.replace(/^\/\/mu[0-9]*\.bdstatic\.com/g, &&);
LocalJs.require(url, function () {})
因为桌面端的浏览器兼容性问题比移动端会严峻的多,所以大多数对LS利用属于&做加法&,或者&轻量级&的应用。最后一瞥不同站点在PC平台的对LS的使用情况:
比如百度和github用LS记录用户的搜素行为,为了提供更好的搜索建议
Twitter利用LS最主要的记录了与用户关联的信息(截图自我的Twitter账号,因为关注者和被关注者的不同数据会有差异):
userAdjacencyList表占40,158 bytes,用于记录每个字关联的用户信息
userHash表占36,883 bytes,用于记录用户被关注的人信息
Google利用LS记录了样式:
天猫用LS记录了导航栏的HTML碎片代码:
No silver bullet.没有任何一项技术或者方案是万能的,虽然开源社区和浏览器厂商在提供给我们越来越丰富的资源,但并不意味着今后遇见的问题就会越来越少。相反,或许正因为多样性,和发展中技术的不完善,事情会变得更复杂,我们在选择时要权衡更多。我无意去推崇某一项解决方案,我想尽可能多的把这些方案与这些方案的厉害呈现给大家,毕竟不同人考虑问题的方面不同,业务需求不同。
还有一个问题是,本文描述的大部分技术都是针对现代浏览器而言,那么如何应对低端浏览器呢?
从百度统计这张17个月的浏览器市场份额图中可以看出(当然可能因为不同站点的用户特征不同会导致使用的浏览器分布与上图有出入),我们最关心的IE6的市场份额一直是呈现的是下滑的趋势,目前已经降至几乎与IE9持平;而IE9在今年的市场份额也一直稳步上升;IE7已经被遥遥甩在身后。领头的IE8与Chrome明显让我们感受到有足够的信心去尝试新的技术。还等什么,行动起来吧!
其他参考文献
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
commonjs规范
Roland Tristan
发表时间好囧
Re: 发表时间好囧
关于localstorage,手机腾讯网基于ls做了字符级别的增量更新
yongfugx lu
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Donald Raab
赞助商链接
InfoQ每周精要
通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7

我要回帖

更多关于 js打开新窗口 的文章

 

随机推荐