Js 问题分析--js 影响页面性能
现状分析:问题陈述
分析问题:抽象问题根源,经过实例或推理证实问题的严重性
问题引伸:以现有问题为点开始扩散,这将致使其它什么问题,或同一类型的问题
问题总结:从分散开始回归,再次抽象问题
5.1 DOM 操做不当影响页面性能
现状分析:
咱们的页面上对 DOM 的操做在所不免,不管是焦点图切换这样的交互效果,仍是各类
数据接口的应用,DOM 操做都是重要的环节。目前页面上的 JavaScript 愈来愈多,一旦咱们
的 DOM 操做不当,必然对页面产生严重的性能问题。好比:电脑网报价库左树、appendChild
插入 js 的方式,都存在 DOM 操做阻塞整个页面渲染的问题。
分析问题:
DOM 是文档对象模型,最主要的 web 前端应用程序接口,是一个独立于语言的,使用
XML 和 HTML 文档操做的应用程序接口。
DOM 在浏览器中的接口是经过 JavaScript 实现的,客户端大多数脚本程序都须要与文档
打交道,使得 DOM 成为 JavaScript 代码平常行为中重要的组成部分。
浏览器一般要求 DOM 实现和 JavaScript 实现保持相互独立。
例如,在 Internet Explorer 中,被称为 JScript 的 JavaScript 实现位于库文件 jscript.dll 中,而
DOM 实现位于另外一个库 mshtml.dll(内部代号 Trident)。这种分离技术容许其余技术和语言,如 VBScript,
受益于 Trident 所提供的 DOM 功能和渲染功能。Safari 使用 WebKit 的 WebCore 处理 DOM 和渲染,具备一个
分离的 JavaScriptCore 引擎(最新版本中的绰号是 SquirrelFish)。Google Chrome 也使用 WebKit 的 WebCore
库渲染页面,但实现了本身的 JavaScript 引擎 V8。在 Firefox 中,JavaScript 实现采用 Spider-Monkey(最
新版中称做 TraceMonkey),与其 Gecko 渲染引擎相分离。
摘自《High Performance JavaScript》
两个独立的部分以功能接口链接就会带来性能损耗,这意味着每次访问 DOM 都须要付
出必定代价,你能够把 DOM 想象成一个岛屿,把 JavaScript 想象成另外一个岛屿,两个岛屿有
一座桥链接,每次过桥都须要支付必定的过桥费,能够想到,你访问 DOM 岛次数越多,所
支付的过桥费也就越多,因此为了减小开销,应该减小访问 DOM 的次数,尽可能停留在JavaScript 岛上。
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/dom.html
问题引伸:
1.HTML 集合:
HTML 元素的集合,例如 document.getElementsByTagName、childNodes 等获得的都是
HTML 集合,虽然 HTML 集合也有 length 属性,但它不是数组(由于它们没有诸如 push()
或 slice()之类的方法),访问 HTML 集合的 length 比数组的 length 要慢。
遍历数组比遍历集合快,若是先将集合元素拷贝到数组,访问它们的属性将更快
实在不能转换成数组,能够将集合的 length 缓存起来,避免屡次遍历集合
http://zzb.pcauto.com.cn/power/js/jsProblem/dom2.html
2.循环:
循环向来是程序效率的重中之重,不当的循环会将程序的性能问题放大不少,对于 DOM
操做来讲,原本这样的操做就已经先天不足了,若是再放到某些循环中,带来的影响将会是
灾难性的。
尽可能使用局部变量来缓存 DOM,避免在循环中访问 DOM 岛。
function toArray(coll) {
for (var i = 0, a = [], len = coll.length; i < len; i++) {
a[i] = coll[i];
}
return a;
}
var coll=document.getElementsByTagName("div");
for(var i=0,len=coll.length;i<len;i++){
....
}问题总结:
DOM 是一个独立存在的 API,独立使得其能够提供方便的接口得以操做 DOM,固然这
是以性能为代价换来的。在浏览器中,DOM 提供接口给 JavaScript 用于操做 xhtml 文档,
JavaScript 解释型语言的特性加上 DOM 的独立性,使得操做 DOM 很容易形成页面的性能瓶
颈。
5.2 首屏 js 阻塞页面
http://www.gethifi.com/tools/regex
http://zzb.pcauto.com.cn/tools/reg/reg.html
现状分析:
不管是从空白页面新打开一个地址,仍是点击一个连接打开新页面,页面首屏的显示快
慢直接决定了用户的第一感觉。而在众多决定首屏显示时间的因素中,js 带来的阻塞无疑是
最为严重的。因为浏览器自己的工做原理,在解析 html 文档时是至上而下的解析,且 js 在调
用前须要预先定义,在 html 中的体现就是不少 js 函数的定义代码会直接放在页面头部的 head
标签内。例如:ssi 合并 js(含 jquery)、头部导航、广告、快搜接口、功能函数、ivy
头部引用 js 是为了页面上的调用可以生效
分析问题:
Js 阻塞页面分为两种状况:js 阻塞渲染和 js 阻塞其余请求
for(var i=0;i<10;i++){
document.getElementsByTagName("div")[i].className="style"+i;
}
var divs=document.getElementsByTagName("div");
for(var i=0;i<10;i++){
divs[i].className="style"+i;
}
<script src="http://www.pconline.com.cn/ssi/js/channel/index.html"
type="text/javascript"></script>
<base target="_blank" />
</head>Js 阻塞渲染:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/jsblock.html
在浏览器中,js 脚本和 DOM 渲染分别由两个独立的模块来完成,而浏览器自己是单线
程工做的,也就是说,浏览器在同一时间只能调用其中的一个模块来工做,而必须暂停另外
一个。这样的工做原理使得 js 的执行对页面的渲染形成了阻塞。
图 5.2.1 浏览器不能同时进行 js 执行和 DOM 渲染
事实上,任何地方的 js 都会阻塞页面,使页面渲染中止,只是从用户体验的角度来讲,
首屏内的 js 形成的阻塞会让用户感受更加明显。其次,js 的执行有可能会带来对页面的修改,
在不清楚页面将会做何修改的时候暂停渲染,也是浏览器出于效率上的考虑。
图 5.2.2 Js 的执行一样会阻塞页面
Js 阻塞其余请求:
除了异步的 js 请求,通常的 js 请求不会和其余请求并发(用 document.write 可让 js 和
js 并发),换句话说,此时浏览器不会发出其余请求,浏览器此时也不会继续解析 html 代码,
必须等到 js 接收完毕,并执行完毕,html 的解析才会继续
图 5.2.3 Js 阻塞页面
问题引伸:
1.js 代码在 html 中的位置
因为浏览器至上而下解析 html 的工做原理,为了减小 js 对渲染的影响,应该让 js 代码
越靠近页面的底部越好,可是这样会带来两个问题:
1、将全部 js 都放在页面的最后,必然会在页面渲染完成时占用不少的时间去执行和加
载 js,此时浏览器进度条将会呈现一直运转的状态,而页面也将呈现“假死”(没法响应用户
的操做)。
2、将全部 js 都放在页面的最后,一些元素的事件绑定将会被推后,即在加载过程当中,虽然页面已经完成渲染和显示,但用户的操做会无效。
由此看出,如何在 html 中放置 js 代码,让 js 可以平稳加载并执行,让页面可以渐进的
过渡渲染,将会是另外一个值得探讨的问题。
2.页面的首屏时间
基调网络的首屏时间定义为:
据基调介绍,基调是检测 800x600 范围内的 8 个点的显示状况来断定首屏是否显示的,
这是一种相对感性的方法,目的是为了最大程度的贴近用户真实的感觉,而实际操做中,这
样的时间点倒是受到诸多因素影响,没法用技术的手段去测试或模拟得出准确的结果。例如:
在首屏结束位置插入一张 1x1 大小的 gif 图片来标记首屏时间,若是页面解析很快,这张小
图的请求就会很快发出,而此时首屏的内容有可能还在加载或是渲染中,因此这种方法并不
能反映最真实的状况。
图 5.2.4 基调的工具能够大概肯定首屏时间,但用其余技术手段并很差肯定
IE 浏览器显示第一屏主页面的消耗时间。
首屏的定义以 800X600 像素尺寸为标准。从开始监测开始计时,到 IE 浏览器页面显
示高度达到 600 像素且此区域有内容显示以后的时间。问题总结:
Js 在 web 前端应用变得日益重要,愈来愈多的应用构建在 js 上,js 一方面能给网页带来
丰富、便捷的体验,另外一方面也带来了效率上的问题。
在 js 阻塞页面渲染上主要有如下两个方面:
一方面,浏览器单线程的工做原理,使得 js 对页面产生的阻塞在所不免,加上 ie6 这样
的低效浏览器仍然是市场主流,阻塞带来的问题在现阶段显得更加明显;另外一方面,通过测
试有望找到合理安排 js 的方式,实现 js 的渐进过渡,最大程度减轻 js 对页面的阻塞,而且随
着浏览器的不断升级与用户硬件设备的不断提高,也有助于减小 js 带来的阻塞。
5.3 异步 js 执行时间不肯定
现状分析:
先说说异步和同步的概念:
异步是相对于同步而言的,同步并非指同时进行活动,而是指协同、配合的活动。我
们说 A 和 B 同步,是指 A 执行到必定程度时要依靠 B 的某个结果,因而停下来,示意 B 运
行;B 执行,再将结果给 A;A 再继续操做。
所谓同步,就是在发出一个功能调用时,在没有获得结果以前,该调用就不返回,同时其它线程也不能
调用这个方法。
摘自网络
而异步则偏偏相反,即 A 和 B 的活动互不影响、互不制约,在 A 活动时,B 也能够执
行。
在 web 中的同步异步每每是指客户端和服务器端通讯的过程,传统的过程就是同步的:图 5.3.1 Web 中的同步和异步模型
目前异步 js 主要由如下两种方式实现:
1. ajax:不能跨域,依赖 xmlhttprequest
2. appendChild(scriptDom):能够跨域,不阻塞其后的请求
因为 ajax 在跨域上的缺陷,如今用得较多的是 appendChild (scriptDom)的形式,如 needJs
和 defineJS 中的异步调用都是以 appendChild(scriptDom)做为核心方法。
为了最大程度减小 js 的请求对其后请求的阻塞,咱们的网站中正逐步推行“按需加载、
异步请求”的模式来对待外链的 js,了解异步 js 的执行时间和特色显得愈发重要。
分析问题:
异步 js 的执行时间在各浏览器中有不一样的表现:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJs.html
asynJs.html 使用 appendChild 方式异步请求 wirte1 和 write2 两个 js,write1 体积明显大于
write2,且 write1 先请求。结果,各浏览器下的执行顺序略有不一样:
ie、chrome:谁先返回谁先执行(在没有缓存的状况下,write2 会比 write1 先返回,故
能够看到 write2 先执行);
FF:谁先请求谁先执行既然 js 的执行顺序会有区别,若是两个异步请求的 js 有依赖关系则会引发浏览器错误:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJs2.html (ie、chrome 下 ctrl+F5
会出错)
图 5.3.2 ie 下 write3 先请求,后返回,后执行
问题引伸:
1.异步请求
若是发出异步请求的脚本块 A 自己执行的时间很长,长到异步请求的脚本 B 已经返回还
未执行完,这时浏览器是会继续执行原来的 A,仍是中断 A,转为执行脚本 B 呢?为此,我
们增长脚本执行的时间:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJsCut/asynJsCut.html
Ie8 页面显示(FF、chrome 执行顺序一致,时间稍有不一样):
能够看到,waitTime1 执行完后执行了 waitTime2,最后才执行 t1。从 dynatrace 的跟踪也
能够看到,t1.js 在 script 脚本还未执行完时就已经接收完毕(图 5.3.3 箭头所示棕色位置),
而此时并无马上执行 t1.js,图 5.3.4 中显示,t1.js 是在 script 脚本块执行完后才执行的。所
以,对于异步请求的 js 接收完后,并不会马上执行,而会排队待 js 引擎空闲后再执行。这
一点和 setTimeout 的机制有些相似。
waitTime1 完成:500 毫秒
waitTime2 完成:1003 毫秒
js1 执行 1088 毫秒
t1.js 载入完成 从开始到载入完成花费:1089 毫秒图 5.3.3
图 5.3.4 异步 js 返回后并无当即执行
2.script 脚本块、js 定时器
Js 的执行是按脚本块为单位一个一个来执行的。在遇到一个脚本块时,js 引擎会把这个
脚本块丢到脚本队列(也叫调用堆栈)中去排队执行,一旦当前 js 引擎出于空闲状态,就会
执行脚本队列里的脚本块,先入队列的脚本块会先执行。
因为 js 为单线程执行,因此每次只能执行一个脚本块,这时会阻塞其余异步 js 的执行(如
鼠标单机事件,定时器触发,或者异步请求完成),这些异步 js 一样被安排到脚本队列中等
待 js 引擎的空闲。
setTimeout 并非严格意义上的定时器,而是将 js 在指定时间后安排到脚本队列的一种
方式,而实际执行该 js 的时间则要看脚本队列中的状况以及 js 引擎的空闲状态来决定。另外,
如何将 js 安排到脚本队列,各个浏览器存在差别。看下面的例子:
http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/setTimeoutJsBlock.html
http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/setTimeoutJsBlock2.html
第一个例子,FF 下 setTimeout 的 b 偶尔会在 c 前执行,ie8 下即便延时增长,b 也没法在
c 前执行。
第二个例子,在不延时的状况下,FF 和 ie8 都会将 b 的执行安排到最后。(ie6 和 FF 类
似)
结论:setTimeout 执行的 js 和内嵌的 js 在执行上的顺序没法保证。
问题总结:
由异步 js 执行时间的不肯定,引出的是对 js 异步执行机制的思考。经过以上的分析能够
初步得出一下结论:
1.异步 js 的执行主要有如下几类:用户触发的事件处理(鼠标点击、滚动页面等)、异步
请求的 js、定时器触发。
2.js 引擎是单线程的
3.异步 js 会进入 js 调用堆栈中排队执行4.各浏览器对于异步 js 安排到堆栈中的处理方式存在区别,表现出来就是在异步请求 js
以及定时器触发 js 在执行顺序上的不一样
5.4 defineJS 的使用
现状分析:
为何要用 defineJS?
客观因素:
浏览器单线程的本质决定了如下两个问题:
1.js 的请求阻塞其后的请求(浏览器不知道 js 会作什么操做,因此在请求 js 的时候不会
请求其余文件,说不定此时 js 会有跳转操做呢。PS:这里指的 js 请求是<script src=””>这样
的标签形式)
2.js 的执行阻塞页面的渲染(由单线程本质决定,具体查阅 5.2 的内容)
这样两个问题使得 js 成为了页面效率的最大杀手,为了保证首屏的内容可以最快的显示,
让用户获得最快的响应,排除阻塞因素、延迟 js 成为了最直观的方法, defineJS 由此应运而
生。
现实状况:
SSI 合并 js 的作法虽然将多个 js 的请求合并成了一个,却在无心识中增长了页面 js 的冗
余度。高耦合在使用上带来了方便,也带来了冗余和笨重,加上 js 对浏览器的特殊影响,使
得在这个问题的权衡上,SSI 的作法是弊大于利的。
打破 js 冗余带来的不利影响,让 js 的调用更加灵活、准确,也是 defineJS 的另外一使命。
defineJS 能作什么?
简单来讲
defineJS 能够延迟 js 的请求。
defineJS 能够减小 js 的冗余。
defineJS 能够将内嵌 js 对外部 js 的依赖关系控制得更加灵活、准确。
分析问题
defineJS 参数详解:图 5.4.1 defineJS 的参数说明
defineJS 流程详解:
图 5.4.2 代理机制和回调函数是 defineJS 的核心。
第一步:声明对须要 defineJS 的函数进行初始化参数配置,如:
defineJS(„usejquery,!$,!jQuery,!$.getScript=/www1/js/pc.jquery1.4.js‟);
具体参数配置能够参照参数详解。
(ps:对于对象的多级声明,层级越高放越后,好比:$.getJSON 在声明时需放在$后,即:
defineJS(“$,$.getJSON=jquery.js”)
)
第二步:格式化参数
这一步会对参数作一下处理:
1.
defineJS(„usejquery,!$,!jQuery,!$.getScript=/www1/js/pc.jquery1.4.js‟);
会在格式化参数步骤中分解成彻底等效的
defineJS("usejquery =/www1/js/pc.jquery1.4.js ");
defineJS("!$=/www1/js/pc.jquery1.4.js ");
defineJS("!jQuery =/www1/js/pc.jquery1.4.js ");
defineJS("!$.getScript =/www1/js/pc.jquery1.4.js ");
2.经过!判断是异步仍是同步,若是是同步,url 是否符合规则
3.是否须要延时处理
4.函数是否已经加载
第三步:设置代理函数
这里简要说下代理机制的原理:
以(„fn=fn.js‟)为例:
代理机制会定义一个名字一样为 fn 的函数(这里为了防止混淆,用 fndl 标识,实际上名字
仍是 fn),当第一次调用 fn(arg)时,其实是调用了 fndl(arg),而 fndl 将 arg 保存,在回调
函数 ok()中将 arg 参数推入到刚加载的真实函数 fn 中。事实上,fndl 起到了保存 arg、转告
arg 的做用。打个比方:这里有 boss、小秘、打工仔三我的,boss 有些材料要叫打工仔去买,可是打工
仔外出了,因而 boss 找来小秘,让小秘等打工仔回来时转告他要去购买的材料,并交了一
份购买清单(arg 参数)给小秘,boss 就去旅游了,小秘打了个电话急招打工仔回来,等了
一会,打工仔回来了,小秘第一时间将 boss 交待的事转告给了打工仔,打工仔接到了清单,
立马出发,不到一会就将全部材料买回。
这里的小秘就是打工仔的代理函数,她起到了保存清单和转交清单的做用。
第四步:加载脚本
具体看下一小节。
第五步:加载完毕,触发 ok()
当 js 加载完毕后,新的 fn 函数会覆盖掉代理 fn,并经过 ok 把原参数推到新的 fn 函数中调
用。
defineJS 同步和异步如何选择:
在 defineJS 的参数中,若是函数名前加上了“!”号,表明此函数调用时,请求 js 用同步
模式,这样作会使得页面的解析和渲染、甚至其后的请求都收到阻塞。那为何还要在
defineJS 中提供同步模式呢?
缘由在于异步模式发出 js 请求后,主程序会继续执行,此时请求的 js 还未返回,若是主
程序有马上调用依赖外部js的函数时,就会由于未定义而报错。具体能够看下面例子中的yb2:
eg.http://zzb.pcauto.com.cn/power/js/jsProblem/defineJS/defineJSty.html
而同步模式用到了 ajax 的同步机制,当请求发出后,主程序会挂起等待 js 的返回,此时
主程序不会继续执行,避免了其后调用还未定义的函数。待 js 返回后才会继续执行主程序。
能够看上面的例子中的 tb 调用。
所以,若是行内脚本中存在依赖外部脚本返回值的代码,就须要用同步模式。
例如:jquery 的链式调用:
又如:new 对象的操做:
$(“#id”).hide();//在调用$时,会触发 jquery 的请求,若是此时不用同步模
式暂停主程序,程序会继续执行 hide 函数,而 hide 函数在 jquery 请求返回以前是未
定义的,因此会报错。固然,若是对于 jquery 这类调用必定要使用异步模式也是有办法的:
1.这须要在请求的 js 中加入一个“外壳函数”,在外壳函数中调用 js 所提供的方法
以 jquery 为例:
外壳函数:
2.defineJS 的申明函数换成这个外壳函数
3.实际调用时的代码也需放到外壳函数中
能够查看 eg.http://zzb.pcauto.com.cn/power/js/jsProblem/defineJS/defineJSty.html
中的 yb3
问题引伸
defineJS 存在的弊端:
1.对于同步模式请求的 js 需使用相对路径,一些动态应用使用 defineJS 须要在动态本地
存放 js,而不能整站公用。
2.defineJS 需在头部预先定义,且嵌入到 html 页面上,若是后期须要更新维护,须要逐
个修改页面。
3.defineJS 同步模式因为使用 xmlhttprequest 发出请求,而 xmlhttprequest 对于不能指定文
档格式的文件,如:js,默认使用 utf-8 编码。而咱们的 js 通常都使用 gb2312 格式,且中文
在 utf-8 和 gb2312 中编码不一样,(英文和特殊字符相同),因此,同步加载的 js 中不该含有中
文,若是必定要加入中文,则需将 js 转为 utf-8 编码。这也是 pc 的 jquery 中含有下面这句话
的缘由:
4.延迟加载的时间没法精确控制。defineJS 请求的时机是由浏览器的解析决定的,当解析
usejquery(function($){
$(xx).xxx;
})
defineJS(“usejquery=jquery.js”);
function usejquery(fn){
fn(jQuery);
}
defineJS(“slidebox=slide.js”);
var a=new slidebox();//异步调用时,执行到 slidebox()时会发出请求,此时
js 还未返回,a 的值已经不是指望获得的对象了
a.slide();//调用 a 的 slide 方法,而 a 并未成为 slidebox 的对象,slide 方法
不存在,报错”速度很快,请求有可能会在某些请求前发出,这主要是因为浏览器的解析和渲染没法绝对一
致所致使。当浏览器解析完首屏的代码时,每每首屏还未渲染完毕。
defineJS 语句分析:
1.
function(fn){
if(fn)return callback(false);
}//fn 是否认义,fn 需做为参数,若是未定义,参数会为 undefined,而在函数外若是未定
义,会报错
2.scripts[src]={
onload:false,
callbacks:[]
}//把地址做为子对象名
3.if(arr.push(a)==1)//数组 push 后返回数组长度
4.初始值、初始表达式
var a=b||"default";
var a=b||(b="default");//b 的值会做为表达式的值返回给 a
5.function alert(s){
window.defineJSlog+=”\n”+s;
}//记录调试信息,不干扰用户,可在地址栏用 javascript:alert(defineJSlog)查看
6.fn.toString().indexOf(“defineJSlog”)//查找 fn 的定义代码中是否含有“defineJSlog”字样
7.eval.call(window,”fn”)//指定在 window 做用域下执行 fn,在 js 中 eval 和 with 能够动态
修改做用域
问题总结:
defineJS 是结合了无阻塞和按需加载两种优化思路的 js 加载方案,虽然还存在同步、预
先定义、相对路径等问题,但在实际的使用过程当中,defineJS 确实发挥了不错的效果,使得
页面在 js 加载上可以更加合理和规范,这也是对前端性能以及资源利用的一次大胆尝试,而
其中涉及到的不少方法和思路,也是了解 javascript 底层原理的很好的实例。5.5 页面内容的浪费加载
现状分析
随着互联网的高速发展和全民上网环境的不断提速,web 网站的数量和复杂程度都呈现
出爆炸式增加的趋势。咱们在愈来愈多的网站上看到,Web 页面承载着难以置信的信息量。
在门户网站首页,多达4、五屏的页面已经习觉得常,滚动滚轮彷佛成了打开每一个页面
都必作的操做。而这仅仅是可见的信息量,愈来愈多的页面将信息隐藏、堆叠起来,即便你
看不到它,即便你不须要它,可是它的确在那里,它的确从服务器传输到了客户端,更重要
的是,它的确占用了带宽,换句话说,它是有成本的。
如何在不影响用户的操做前提下,最大化的节约消耗和成本,已经成为另外一个值得研究
的前端领域。
问题分析:
何为浪费加载:
首先,目前的 web 页面都基于浏览器,而浏览器可视区域的大小受到显示设备的限制,
因此,超出视窗的内容只有经过滚动条才可以看获得。换句话说,若是一个页面有五屏,而
你的用户在开完第一屏后就关闭了浏览器,那么,第二屏到第五屏的数据就属于浪费加载的
范畴。
其次,如今的页面有不少隐藏的内容,默认状况下是不显示出来的,除非用户进行了某
些操做,好比点击或移上某个元素,才会触发显示。一样,若是用户并未进行这样的操做,
这些内容也属于浪费加载的范畴。
图 5.5.1 用户未切换的选项卡属于浪费加载的范畴
原则“可见的才是须要的”
面对页面上的众多信息,最大化信息利用率的原则就是“可见的才是须要的”,换句话说,
当前不可见的就不须要加载、不须要显示,这也是近来说起比较多的“按需加载”。从传统的
将信息所有推送给用户,到让用户本身选择须要的信息,“按需加载”事实上是把更多的权利
交给了用户。不过,按需加载也是有利有弊的:
利:
1. 减小页面的浪费加载,节省没必要要的带宽
2. 加快页面的载入时间,减小用户的等待
弊:
1. 按需加载的内容基本丧失了搜索引擎抓取的可能,会带来必定流量的减小
2. 按需加载在触发时才加载数据,用户会感受有必定延迟
3. 若是没有合理的选择按需加载触发的时机,每每会带来很差的体验
有谁在使用“按需加载”
1.yahoo
做为世界级的门户网站,yahoo 对于前端的关注在业内是众所周知的,YUI 也一直是前
端行业的风向标。Yahoo 首页对于按需加载的使用可谓发挥到了极致,总的页面只有一屏半
的高度,庞大的各种内容都放在了左侧的导航上进行按需加载。这样的处理,使得 yahoo 首
页的加载速度极快,左侧还提供了定制功能,方便用户定制感兴趣的信息。
图 5.5.2 Yahoo 首页左侧大量用了“按需加载”
加载方式:发出请求加载,appendChild 插入
2.淘宝
淘宝不只是国内最成功的 C2C 平台,其在前端领域的实力也在国内名列前茅。
淘宝对于按需加载的使用主要在首页切换卡和产品列表页上:
首页的切换卡中的内容用<textarea>包住,这样能够避免浏览器解析其中的代码;图 5.5.3 淘宝首页选项卡内容用了 textarea
而产品列表页则没有给视窗外的图片设置 src值,而是把图片的地址写在 data-lazyload-src
上,避免请求图片。
图 5.5.4 列表页图片用 data-lazyload-src 代替 src
3.QQ
首页采起的是和 yahoo 相同的形式,即异步请求加载,不一样的是,qq 请求返回的是一个
html 页面,而 yahoo 则是返回一个 json 数据。从这点来看,qq 的方式更加直观,便于维护。
而在图片方面,qq 的作法和淘宝相似,也是用自定义属性代替图片 src 的方法:
图 5.5.3 qq 图片延迟方式
4.sina
做为国内门户三强之一,sina 的信息量可谓巨大,不过实际发现其并未在首页使用按需
加载,而发如今博客的文章页中对图片进行了按需加载的处理:
Sina 的作法是给全部图片默认设置了 src 为一张 1x1 的小图,真实的地址则用自定义属
性,究其这样作的缘由,应该是博客内页的图片大多由网友上传,大小不一,先用小图占大
图的位,这样作可让按需加载的图片,在加载完成时不改变文章内容的布局,避免给用户
带来不便。而前面提到的淘宝和 sina 的图片都是尺寸统一固定的,用样式进行统一控制就可
以了。
图 5.5.4 sina 博客文章页图片延迟(图片有设宽高)
按需加载的分类
按加载的内容来分能够分为两类:
1.代码
按需加载代码包括 html 的全部代码,在未加载以前,html 代码不会被解析,其中的图片
也不会请求,主要有如下几种实现方式:textarea、异步请求、iframe
①<textarea>:用 textarea 标签包住须要按需加载的 html 代码,当加载事件触发时,就
将 html 的代码传给指定元素的 innerHTML 便可,此时,浏览器才会解析这一段 html 代码,
若是这段 html 中含有图片,此时才会发出图片请求。优势:操做简单,容易维护,响应迅速
缺点:textarea 须要绑定在页面上,需改动原页面代码,textarea 中的内容不会被搜索引
擎抓取到。
②异步请求:此方式是指当触发事件后,经过 httprequest 或者 DOMScript 请求数据,服
务器返回 json 格式或者 html 片断,再利用回调函数更新页面。Json 格式的数据小巧灵活,可
根据页面的需求生成不一样的 html,适合各类动态接口使用;html 片断的方式看起来更加直观,
便于理解,也免去了在客户端从新组装 html 的过程,适合代码量较大、形式较为固定的状况。
优势:可在多个页面使用,加载的内容和页面分离,易于管理
缺点:请求返回会花费必定时间,没法作到当即刷新内容
③iframe:须要加载的内容放在另外一个页面中,触发事件时,用 js 动态插入 iframe,
此时,浏览器才开始请求 iframe 中的内容,以此达到按需加载。
优势:iframe 的页面相对独立,主页面不受 iframe 的样式影响。
缺点:会受到 iframe 的各类限制
2.图片
图片是网页中的重要组成部分,其占用的带宽相对于文本也要大得多,因此在节约带宽
上,图片按需加载每每起到更为重要的做用。
针对图片的按需加载主要应用在图片列表页和文章页中,主要的方式是用自定义属性来
保存真实的图片地址,当须要加载时才将图片地址赋值给 src 属性。这样作的原理是图片只
有当 src 有值时,浏览器才会发出图片请求。
问题引伸:
事实上,按需加载的难点并不在技术,其难点在于选择使用的时机。何时该使用按
需加载,何时改用什么方式,这才是最难的。想要节约带宽,前提是不能下降用户体验
或者减小访问量。“天下没有白吃的午饭”正是这个道理。
从用户体验上来讲,按需加载是基于 web 的人机交互的一种新的方式,它的核心仍是人。
一切从人的角度出发,基于人的感觉,提供最人性化的服务,这将是按需加载带给咱们的新
的思考。这样人性化的操做还表如今图片浏览的预加载上:预先加载后一页的图片内容,减
少用户翻页时等待的时间,这也是人性化优化的另外一典范。
问题总结:
按需加载在必定程度上能够节约带宽,并减小页面内容的浪费,若是使用得当,还能够
减小用户等待时间,提升用户体验。可是对于 SEO 要求比较高的页面,按需加载就显得力不
从心了;另外这样的作法须要对页面的代码作响应的调整,若是大量使用还需考虑推广实施
的问题,好比用置标将 img 的 src 属性自动替换等等,尽可能减小对正常代码的修改。5.6 Js 外链与内嵌的选择
现状分析:
Javascript 在 html 中的存在形式有两种:一是<script>xxxx</script>脚本块,二是<script
src=”xx.js”></script>外链 js 文件,也便是一般说的内嵌和外链两种形式。
查看各个知名站点的源代码能够发现,这两种形式都经常出现,他们散乱的位于各类 html
标签中间,让本来优雅的 html 代码多了几分“瑕疵”。的确,因为 script 的特殊性,使得它
在 html 中总会是最为显眼的,而 script 在页面中的位置和存在方式也是值得关注和重视的地
方。
问题分析:
Script 的存在方式
内嵌:
用<script>和</script>包裹住的 javascript 代码块。当浏览器解析到这样的代码时,会以
script 块为单位,将其扔给相应的 js 引擎去执行,并暂停等待 js 引擎的结果,待 js 引擎执行
完毕后,才继续后续的 html 的解析。
外链:
在 script 标签中,用 src 指定请求的外链的 js 地址。当浏览器解析到这样的代码时,会
请求指定的 js,此时浏览器暂停继续解析页面,待 js 返回后,开始执行请求的 js,在执行后
才继续页面 html 的解析。
此外,外链 js 也能够经过异步请求的方式加载,此时的执行顺序在不一样浏览器中会存在
差别,具体能够查阅《异步 js 执行时间不肯定》一节
内嵌与外链的多项对比
维护:
内嵌的 js 因为是放在 html 页面上的,因此对于内嵌 js 的维护须要涉及到响应的页面,
在大批量进行 js 修改时,内嵌 js 会很不方便;外链 js 因为从 html 页面独立出来,内容只有
一份,相应的维护也只须要修改一次便可。
效率:
外链 js 因为须要浏览器请求,并等待服务器返回 js 文件(正常 js 的请求还会阻塞其余请
求),因此,外链 js 的实际使用效率要比内嵌 js 差一些。
缓存:内嵌 js 的体积是直接算在 html 页面上的,且通常的 html 不会作缓存的操做,也就至关
于内嵌 js 是没有缓存的;而外链 js 则能够根据其使用特色,进行缓存的设置,以此减小流量
的消耗。
执行顺序:
在执行顺序上,内嵌的 js 在各浏览器中能够保证彻底按照在 html 中出现的顺序来执行;
而外链的 js 若是是异步请求,则没法在全部浏览器中都保证执行顺序。在以前的《异步 js 执
行时间不肯定》一节中有说道,这里看一下另一种外链 js 执行顺序出问题的例子。
Eg: http://zzb.pcauto.com.cn/power/js/jsProblem/out/script2.html
FF 下的结果为:
第一行
第二行
Ie 下的结果为:
第二行
第一行
目前分析是因为 ie 在执行一段 js 的过程当中是不可以中途中断去请求其余 js 的,只有等
到执行完了或者说 js 引擎空闲了,js 的请求才会发出;而 ff 一旦有 js 请求,就会中断 js 执
行。(游戏网头部的广告也是这种状况)
问题引伸:
事实上,js 的外链和内嵌的选择和 css 的外链和内嵌的选择有类似的地方,好比维护成
本、效率、缓存方面的考虑,不太同样的是, 外链的 css 并不是阻塞元素,因此通常不会对 css
作异步请求的处理,也就不会出如今解析顺序上不一致的问题。此外,css 自己就有优先级的
控制机制,例如 id 优于 class 等,使得 css 的解析顺序可以获得很好的控制。
问题总结:
Js 的内嵌和外链没有绝对的优劣,如何选择须要按照实际状况来分析,并综合考虑维护、
效率、缓存、执行顺序等多方面因素。javascript