不知道有多少人和我同样,在之前的开发过程当中不多在意本身编写的网页的性能。或者说一直以来我是缺少开发高性能网页的意识的,可是想作一个好的前端开发者,是须要在当本身编写的程序慢慢复杂之后还能继续保持网页的高性能的。这须要咱们对JavaScript语句,对其运行的宿主环境(浏览器等),对它的操做对象(DOM等)有更深刻的理解。javascript
什么样的网页是高性能的网页?
我想一个网页是否高性能主要体如今两个方面,一是网页中代码真实的运行速度,二是用户在使用时感觉到的速度,咱们一项项的来讨论。php
想要提升代码的执行效率,咱们首先得知道咱们使用JS作不一样的事情时,其执行效率各是如何。通常说来,web前端开发中咱们常作的操做主要是数据获取和存储,操做DOM,除此以外,咱们知道JS中达到同一目的可能会有多种途径,但其实各类途径执行效率并不相同,咱们应该选择最合适的途径。css
先说数据存储,计算机中,数据确定是存在于内存之中,可是访问到具体内存所在的位置却有不一样的方法,从这个角度看JS中有四种基本的数据存取位置:html
- 字面量:只表明自身,不存储在特定位置 * 本地变量:使用关键字var 存储的数据存储单元 * 数组元素:存储在JavaScript的数组对象内部,以数字为索引 * 对象成员:存储在JavaScript对象内部,以字符串为索引
不一样存储方式的访问速度
其实很容易就能够理解,访问一个数据所经历的层级越少,其访问速度越快,这样看来访问字面量和局部变量速度最快,而访问数组元素和对象成员相对较慢;前端
从数据存储和访问角度来看,提高效率的核心在于存储和访问要直接,不要拐弯抹角。咱们以原型链和做用域为例来讲明如何优化。java
原型链
对象的原型决定了实例的类型,原型链能够很长,实例所调用的方法在原型链层级中越深则效率越低。所以也许须要咱们保证原型链不要太长,对于常常须要使用到的方法或属性,尽可能保证它在原型链的浅层。node
做用域(链)
做用域也是JavaScript中一个重要的概念,通常说来变量在做用域中的位置越深,访问所需的时间就越长。
局部变量存在于做用域链的起始位置,其访问速度比访问跨做用域变量快,而全局变量处于做用域链的最末端,其访问速度也是最慢的。
通常说来JavaScript的词法做用域在代码编译阶段就已经肯定,这种肯定性带来的好处是,代码在执行过程当中,可以预测如何对变量进行查找,从而提升代码运行阶段的执行效率。咱们也知道JavaScript中有一些语句是会临时改变做用域的,好比说with
,eval
,try...catch...
中的catch
子句,使用它们会破坏已有的肯定性,从而下降代码执行效率,所以这些语句应该当心使用。webpack
从数据的存储和访问角度,能够从如下角度进行优化:程序员
- 咱们应该尽可能少用嵌套的对象成员,多层嵌套时会明显影响性能,若是实在要使用(尤为是屡次使用); - 咱们能够经过把经常使用的对象成员,数组元素,跨域变量保存在局部变量中,再使用来改善JavaScript性能。
经过DOM API,利用JavaScript咱们有了操做网页上的元素的能力,这也使得咱们的网页活灵活现。但是遗憾的是DOM编程是性能消耗大户,它天生就慢,究其缘由,是由于在浏览器中DOM渲染和JavaScript引擎是独立实现的,不一样的浏览器实现机制不一样,具体可见下表。web
类别 | IE | Safari | Chrome | Firefox |
---|---|---|---|---|
JS引擎 | jscript.dll | JavaScriptCore | V8 | SpiderMonkey(TraceMonkey) |
DOM和渲染 | mshtml.dll (Trident) | Webkit中的WebCore | Webkit中的WebCore | Gecko |
DOM渲染和JavaScript引擎的独立实现意味着这两个功能好像位于一条大河的两岸,两者的每次交互都要渡过这条河,这很明显会产生很多额外的性能消耗,这也是咱们常说操做DOM是很是消耗性能的缘由。
从这个角度咱们想到,想要减小DOM操做带来的性能的消耗,核心咱们要作的是减小访问和修改等DOM操做。
咱们能够从如下几方面着手DOM编程的优化:
把运算尽可能留在ECMAScript这一端处理,在实际开发过程当中咱们应该对DOM不作非必要的操做,在得到必要的值之后,能够把这些值存储在局部变量之中,纯使用JS对该值进行相关运算,直接用最后的结果来修改DOM元素,可能这么说来并非很直观,可是回头看看咱们的项目,咱们有没有过在循环或者会屡次使用的函数中,每次都从新获取某个不变的DOM相关的值;
当心处理HTML集合:
HTML集合是包含了 DOM节点引用的类数组对象,相似于如下方法,获取的都是这种HTML集合:
document.getElementsByName()
;document.getElementByClassName()
;document.getElementByTagName()
;
这种类数组对象具有普通数组的一些特色,好比拥有length
属性,能够经过数字索引的方式访问到对应的对象,可是却并无数组的push
和slice
等方法,重点在于在使用过程当中这个集合实时连系着底层的文档,每次访问这种HTML集合,都会重复执行查询的过程,形成较大的性能消耗;下面是一个典型的屡次访问的例子:
var alldivs = document.getElementsByTagName('div'); for(var i = 0;i<alldivs.length;i++){ document.body.appendChild(document.createElement('div')); }
上述代码是一个死循环,在每次循环的时候alldivs
都会从新遍历DOM,得到更新,产生了没必要要的性能消耗;
合理的使用方式应该是,把集合的长度缓存到一个变量中,在迭代中使用这个局部变量。若是须要常常操做集合元素,咱们能够把这个集合拷贝到一个数组中,操做数组比操做HTML集合性能高;使用下述方法能够把一个HTML集合拷贝为普通的数组:
// 拷贝函数 function toArray(coll){ for(var i=0,a=[],len=coll.length;i<len;i++){ a[i]=coll[i] } return a; } // 使用方法 var coll = document.getElementByTagName('div'); var ar = toArray(coll);
JS代码的运行须要宿主环境,就web前端来讲,这个环境就是咱们的浏览器,通常说来浏览器会对一些常见操做进行必定的优化,使用优化后的API,性能更高,咱们应该尽可能使用那这些优化过的API,对现代浏览器中的DOM操做来讲,有如下一些优化后的API:
优化后 | 原始 |
---|---|
children |
childnodes |
childElementCount |
children.length |
firstElementChild |
firstChild |
lastElementChild |
lastChild |
nextElementSibling |
nextSibling |
previousElementSibling |
previousSibling |
document.querySelector() |
document.getElemnetByClassName… |
减小重绘与重排
重排和重绘也是你们常常听到的概念:
重排:DOM变化影响了元素的几何属性(好比改变边框宽度),引发其它元素的位置也所以受到影响,浏览器会使得渲染树中受到影响的部分失效,并从新构建渲染树;
重绘:重排完成后,浏览器会从新绘制受影响的部分到屏幕中(并非全部的DOM变化都会影响几何属性,例如改变一个元素的背景色并不会影响它的宽和高);
重排和重绘都是代价很高的操做,会直接致使Web应用程序的UI反应迟钝,应该尽可能减小这类过程的发生。通常说来下面的操做会致使重排:
- 添加删除可见的DOM元素; * 元素位置改变; * 元素尺寸改变(`padding,margin,border,width,height...`) * 内容改变;(文本内容,或图片被另一个不一样尺寸的图片替代); * 页面渲染器初始化; * 浏览器窗口尺寸改变;
每次重排都会产生计算消耗,大多数浏览器会经过队列化修改,批量执行来优化重排过程,减小性能消耗。可是也有部分操做会强制刷新队列,要求重排任务当即执行。以下:
- `offsrtTop`,`offsetLeft`,`offsetWidth`,`offsetHeight`; * `scrollTop`,`scrollLeft`,`scrollWidth`,`scrollHeight`; * `clientTop`,`clientLeft`,`clientWidth`,`clientHeight`; * `getComputedStyle()`
上述操做都要求返回最新的布局信息,浏览器不得不执行渲染队列中待处理的任务,触发重排返回正确的值。经过缓存布局信息能够减小重排次数,这一点是我一直忽略的一点,曾经屡次在循环或屡次调用的函数中直接使用上述操做获取距离;
总的来讲最小化重绘和重排的核心是合并屡次对DOM和样式的修改,而后一次处理掉,主要有如下几种方法:
- 使用cssText属性(适用于动态改变)
var el = document.getElementById(‘mydiv’); // 下面这种写法能够覆盖已存在的样式信息 el.style.cssText = ‘border-left:1px;border-right:2px;padding:5px’; // 下面这种写法能够把新属性附加在cssTest字符串后面 el.style.cssText += ‘;border-left:1px;’;
- 对于不依赖于运行逻辑和计算的状况,直接改变CSS的`class`名称是一种比较好的选择 - 批量修改DOM:操做以下 - 使元素脱离文档流; * 隐藏元素,应用修改,从新显示; * 使用文档片断,在当前DOM外构建一个子树,再把它拷贝到文档; * 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后替换原始元素; * 对其应用多重改变; * 把元素带回文档中; 在这个过程当中只有第一步和第三步会触发重排,可参加下述代码。
var fragment = document.createDocumentFragment();//一个轻量级的document对象 // 组装fragment...(省略) // 加入文档中 document.getElementById(‘mylist’).appendChild(fragment); var old = document.getElementById(‘mylist’); var clone = old.cloneNode(true); // 对clone进行处理 old.parentNode.replaceChild(clone,old);
- 对动画元素可进行以下优化 - 对动画元素使用绝对定位,将其脱离文档流; * 让元素动起来,懂的时候会临时覆盖部分页面(不会产生重排并绘制页面的大部份内容); * 当动画结束时恢复定位,从而只会下移一次文档的其它元素;
此外若是页面中对多个元素绑定事件处理器,也是会影响性能的,事件绑定会占用处理时间,浏览器也须要跟踪每一个事件处理器,会占用更多的内存。针对这种状况咱们能够采用使用事件委托机制:也就是说把事件绑定在顶层父元素,经过e.target获取点击的元素,判断是不是但愿点击的对象,执行对应的操做,幸运的是如今的不少框架已经帮咱们作了这一点(React中的事件系统就用到了事件委托机制)。
异曲同工付出的代价却不同,和任何编程语言同样,代码的写法和算法会影响运行时间。而咱们写的最多的语句是迭代和判断。
JavaScript提供了多种迭代方法:
四种循环类型:
for
;
while
;
do…while
循环;
for…in…
循环;
就四种循环类型而言,前三种循环性能至关,第四种for...in...
用以枚举任意对象的属性名,所枚举的属性包括对象实例属性及从原型链中继承来的属性,因为涉及到了原型链,其执行效率明显慢于前三种循环。就前三种而言影响性能的主要因素不在于选择哪一种循环类型,而在于每次迭代处理的事务和迭代的次数。
减小每次迭代的工做量
很明显,达到一样的目的,每次迭代处理的事务越少,效率越高。举例来讲
// 普通循环体 for(var i=0;i<items.length;i++){ process(item[i]) } // 优化后的循环体 for(var i= items.length;i--;){ process(item[i]) }
普通循环体每次循环都会有如下操做:
1. 在控制条件中查找一次属性(`items.length`); 2. 在控制条件中执行一次数值比较(`i<items.length`); 3. 一次比较操做查看控制条件的计算结果是否为`true`(`i<item.length==true`); 4. 一次自增操做(`i++`); 5. 一次数组查找(`item[i]`); 6. 一次函数调用(`process[items[i]`);
优化后的循环体每次迭代会有如下操做:
1. 一次控制条件中的比较(`i==true`); 2. 一次减法操做(`i—`); 3. 一次数组查找(`item[i]`); 4. 一次函数调用(`process(item[i])`);
明显优化后的迭代操做步骤更少,若是迭代次数不少,屡次这样小的优化就能节省不错的性能,若是process()
函数自己也充满了各类小优化,性能的提高仍是很可观的;
减小迭代次数
每次迭代实际上是须要付出额外的性能消耗的,可使用Duff’s Device方法减小迭代次数,方法以下;
// credit:Jeff Greenberg var i = items.length%8; whild(i){ process(items[i--]); } i = Math.floor(items.length/8); // 若是循环次数超过1000,与常规循环结构相比,其效率会大幅度提升 while(i){ process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); }
‘Duff’s Device’循环体展开技术,使得一次迭代实际上执行了屡次迭代的操做。若是迭代次数大于1000次,与常规循环结构相比,就会有可见的性能提高。
基于函数的迭代
forEach()
;
map()
;
every()
;
等…
基于函数的迭代是一个很是便利的迭代方法,但它比基于循环的迭代要慢一些。每一个数组项都须要调用外部方法所带来的开销是速度慢的主要缘由。经测试基于函数的迭代比基于循环的迭代慢八倍,选择便利仍是性能这就是咱们本身的选择了;
条件表达式决定了JavaScript运行流的走向,常见的条件语句有if…else...
,switch...case...
两种:switch
语句的实现采用了branch table(分支表)索引进行优化,这使得switch...case
是比if-else
快的,可是只有条件数量很大时才快的明显。这两个语句的主要性能区别是,当条件增长时,if...else
性能负担增长的程度比switch
大。具体选用那个,仍是应该依据条件数量来判断。
若是选用if...else
,也有优化方法,一是把最可能出现的条件放在最前面,二是能够经过嵌套的if...else
语句减小查询时间;
在JavaScript中还能够经过查找表的方式来获取知足某条件的结果。当有大量离散值须要测试时,if...else
和switch
比使用查找表慢不少。查找表能够经过数组模拟使用方法以下:
var results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9]; return results[value];
从语言自己的角度来看,作到上述各点,咱们的代码性能就会有比较大的提高了,可是代码性能的提高,只是代码执行自己变快了,并不必定是用户体验到的时间变短了,一个好的web网页,还应该让用户感受到咱们的网页很快。
人类对时间的感受实际上是不许确的,考虑两种场景,
一种场景是,一段代码执行10s,10s后全部内容一会儿所有显示出来;
另一种场景是,总共须要执行12s,可是每秒都在页面上添加一些内容,主体内容先添加,次要内容后添加;
上述两种场景给人的感受彻底不同。大部分人会以为后面那个12s时间更短。
用户感受到的时间的长短取决于用户与网页交互时收到的反馈,通常说来100ms是个时间节点,若是界面没在100ms内对用户的行为做出反馈,用户就会有卡顿的感受,开发过手机版的网页的童鞋都知道,当你触发手机端默认的onClick事件时,有些浏览器出于要确认你是否想要双击,会在单击事件触发300ms后,才执行相应的操做,用户在这时候会有明显卡顿的感受的。为了达到更好的交互体验,咱们能够从脚本加载时机,不阻塞浏览器的UI线程,高效使用Ajax,合理构建和部署JavaScript应用等方面进行优化:
咱们都知道多数浏览器使用单一进程来处理UI刷新和JavaScript脚本执行,当执行较大的JavaScript脚本时会阻塞UI的刷新。这是一种很是很差的体验。全部在页面初始化时期,咱们都会把JavaScript放在</body>
标签以前。咱们也能够采用动态加载的方式,不管在什么时候启动,文件的下载和执行过程都不会阻塞页面其余进程,一种常见的动态加载方式以下,固然咱们也可使用AJAX进行动态加载。
var script = document.createElement("script"); script.type = "text/javascript"; script.src="file1.js"; document.getElementsByTagName("head")[0].appendChild(script); // 不管在什么时候启动下载,文件的下载和执行过程不会阻塞页面其余进程 // 经过检测load事件能够得到脚本加载完成时的状态
咱们已经知道,当JavaScript代码执行时,用户界面实际上是属于锁定状态的,管理好JavaScript的运行时间对Web应用的性能相当重要。
用来执行JavaScript和更新用户界面的进程一般称为“浏览器UI线程”。其工做基于一个简单的任务队列系统,具体原理是全部的前端任务会被保存在任务队列中直到进程空闲,一旦空闲,队列中的下一个任务就会被提取出来并运行。这些任务多是一段待执行的JavaScript代码,UI更新(重排重绘等)。
通常浏览器有本身的JavaScript长时间运行限制,不一样浏览器限制的时间不一样,有的是5~10s,有的是运行超过500万行语句时提醒;达到限制时间后对页面的处理方式也不一样,有的直接致使浏览器崩溃,有的会发出明显可见的警告信息。
前面咱们已经讨论过若是等待时间不超过100ms,通常人就会以为操做是连贯的。这给咱们提供了一个思路,咱们的一段JavaScript代码的执行时间若是不超过100ms,操做起来感受就是连贯的。
经过分隔JavaScript代码的执行,咱们可让JavaScript的执行时间不超过100ms,主要途径主要有两种:
使用定时器让出时间片断,使用web worker。
JavaScript中有两种不一样的定时器setTimeout()
和setInterval()
,它们都接受两个参数,第一个参数是一个函数,第二个参数是一段时间。当定时器被调用时,它们会告诉JavaScript引擎在等待咱们所定的时间后,添加一个JavaScript任务到UI线程中。
定时器的意义在于,每当咱们建立一个定时器,UI线程就会暂停,并从一个任务切换到下一个任务,定时器也会重置全部相关的浏览器限制,包括长时间运行脚本限制,调用栈的限制。
咱们也应该明确,定时器的延迟时间并不是老是精确的,每次相差大约几毫秒,因此定时器不太适合拿来计时(好比说在windows系统中定时器分辨率为15ms),通常建议的延迟的最小值为25ms,以确保至少有15ms的延迟。
咱们经过下面的代码看看如何利用定时器无阻塞的处理大数组
var todo = items.concat();//items是原始的大数组 setTimeout(function(){ // 取出数组的下个元素并进行处理 process(todo.shift()); // 若是还有须要处理的元素,建立另外一个定时器,再25ms执行一次 if(todo.length>0){ // arguments.callee指向当前正在运行的匿名函数 setTimeout(arguments.callee,25); }else{ // 若是全部的数组都被处理了,执行callback()函数 callback(items); } },25);
利用定时器能够按照下述方法分割运行时间过长的函数
//原函数 function saveDocument(id){ //保存文档 openDocument(id); writeText(id); closeDocument(id); //将信息更新到界面 updateUI(id); } // 使用定时器分割任务的方法 function saveDocument(id){ var tasks=[openDocument,writeText,closeDocument,updateUI]; setTimeout(function(){ var task = tasks.shift(); task(id); if(tasks.length>0){ setTimeout(arguments.callee,25); } },25); }
定时器把咱们的任务分解成了一个个的不致于阻塞的片断,虽然总的执行时间多是变长了,可是会让咱们的交互体验发生翻天覆地的变化,使用得当,咱们能够避免大部分彻底卡住的状况了。不过定时器的使用也有一个限制,咱们应该保证同一时间只有一个定时器的存在,防止由于定时器使用过分致使的性能问题。
WebWorker是HTML5新提供的一组API,它引入了一个接口,能使代码运行且不占用浏览器UI线程的时间,这里只作简单介绍,具体使用请查看相关文档:
一个WebWorker实际上是JavaScript特性的一个子集,其运行环境由如下部分组成:
- 一个`navigator`对象,包含四个属性:`appName`,`appVeision`,`userAgent,platform`; * 一个`location`对象(与`window.location`相同,不过全部属性都是只读的); * 一个`self`对象,指向全局`worker`对象; * 一个`importScript()`方法,用来加载`Worker`所用到的外部JavaScript文件; * 全部的ECMAScript对象,诸如:`Object,Array,Date`等; * `XMLHttpRequest`构造器; * `setTimeout()`和`setInterval()`方法; * 一个`close()`方法,它能够当即中止Worker运行;
主网页与Worker相互通讯的方法
Worker与网页代码经过事件接口进行通讯。主网页和Worker都会经过postMessage()
传递数据给对方;使用onmessage
事件处理器来接收来自对方的信息;
// 主页面 var worker = new Worker('code.js'); worker.onmessage = function(event){ alert(event.data); }; worker.postMessage("Nicholas"); //code.js内部的代码 self.onmessage = function(event){ self.postMessage("Hello,"+ event.data+"!"); }
相互传递的原始值能够是字符串,数字,布尔值,null
,undefined
,也能够Object
和Array
的实例。
加载外部文件的方法
在一个web worker中可使用importScript()
方法加载外部JavaScript文件,该方法接收一个或多个url做为参数,调用过程是阻塞式的,全部文件加载并执行完成之后,脚本才会继续运行。但并不会影响UI响应。
加载容许异步发送和接收数据。能够在请求中添加任何头信息和参数,并读取服务器返回的全部头信息,以及响应文本。importScripts(‘file1.js’,’file2.js’);
WebWorker的实际应用
- 适合处理纯数据,或者与UI无关的长时间运行脚本; * 编码,解码大字符串; * 复杂数学运算; * 大数组排序;
总的说来,Web应用越复杂,积极主动管理UI线程越重要,复杂的前端应用更应该合理使用定时器分割任务或使用WebWorker进行额外计算从而不影响用户体验。
Ajax是高性能JavaScript的基础,它经过异步的方式在客户端和服务器端之间传输数据,避免页面资源一窝蜂的下载,从而起到防止阻塞,提升用户体验的效果,在使用时咱们应该选择最合适的传输方式和最有效的数据格式。
数据请求方式
异步从服务器端获取数据有多种方法,经常使用的请求有如下三种:
XHR
这是目前最经常使用的技术,能够在请求中添加任何头信息和参数,读取服务器返回的全部头信息以及响应文本。
这种方法的缺点在于不能使用XHR从外域请求数据,从服务器传回的数据会被看成字符串或者XML对象,处理大量数据时会很慢。经GET请求的数据会被缓存起来,若是须要屡次请求同一数据的话,使用GET请求有助于提高性能,当请求的URL加上参数的长度接近或超过2048个字符时,才应该用POST获取数据。
XHR的使用可见如下示例:
var url = ‘/data.php’; var params = [ ‘id=123123’, ‘limit = 20’, ]; var req = new XMLHttpRequest(); req.onreadystatechange = function(){ if(req.readyState===4){ var responseHeader = req.getAllResponseHeaders(); // 获取响应头信息 var data = req.responseText; // 获取数据 // 数据处理相关程序 } } req.open(‘GET’,url+’?’+params.join(‘&’),true); req.setRequestHeader(‘X-Requested-With’,’XMLHttpRequest’);// 设置请求头信息 req.send(null); //发送一个请求
动态脚本注入
这种技术克服了XHR的最大限制,它能跨域请求数据。可是这种方法也有本身的限制,那就是提供的控制是有限的,
不能设置请求的头信息;
只能使用GET,不能POST;
不能设置请求的超时处理和重试;
不能访问请求的头信息。
这种请求的请求结果必须是可执行的JavaScript代码。因此不管传输来的什么数据,都必须封装在一个回调函数中。尽管限制诸多,可是这项技术的速度很是快。动态脚本注入的使用方法以下:
var scriptElement = document.createElement('script'); scriptElement.src = 'http://.../lib.js'; document.getElementsByTagName('head')[0].appendChild(scriptElement); function jsonCallback(jsonString){ var data = eval('('+jsonString+')') } jsonCallback({"state":1,"colors":["#fff","#000","#ff0000"]});
multipart XHR
这种方法容许客户端只用一个HTTP请求就从服务器端向客户端传送多个资源,他经过在服务器端将资源(CSS文件,HTML片断,JavaScript代码或base64编码的图片)打包为一个由双方约定的字符串分割的长字符串并发送给客户端;而后用JavaScript代码处理这个长字符串,并根据它的mime-type类型和传入的其它“头信息”解析出每一个资源。
基于这种方法,咱们能够经过监听readyState为3的状态来实如今每一个资源收到时就当即处理,而不是等待整个响应消息完成。
此技术最大的缺点是以这种方式得到的资源不能被浏览器缓存。不过在某些状况下MXHR依然能显著提高页面的总体性能:
- 页面中包含了大量其它地方用不到的资源好比图片; * 网站在每一个页面中使用一个独立打包的JavaScript或CSS文件用以减小HTTP请求;
HTTP请求是Ajax中最大的瓶颈之一,所以减小HTTP请求的数量也许明显的提高整个页面的性能。
数据发送方式
主要有两种数据发送方法
- `XHR` - 信标`beacons`
XHR
咱们较熟悉,使用示例以下:
var url = ‘/data/php’; var parms = [ ‘id=934345’, ‘limit=20’ ] var req = new XMLHttpRequest(); req.onerror = function(){ //出错 }; req.onreadystatechange = function(){ if(req.readyState==4){ // 成功 } } req.open(‘POST’,url,true); req.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’); req.setRequestHeader(‘Content-Length’,params.length); req.send(params.join(‘&’));
须要注意的是,使用XHR发送数据时,GET方式更快,对少许数据而言,一个GRT请求往服务器只发送一个数据包。而一个POST请求至少发送两个数据包,一个装载头信息,另一个装载POST正文,POST适合发送大量数据到服务器的状况。
Beacons技术相似于动态脚本注入。
Beacons技术具体作法为使用JavaScript建立一个新的Image对象,并把src属性设置为服务器上脚本的URL,该URL包含了咱们要经过GET传回的键值对数据。实际上并无建立img元素或把它传入DOM。
var url = ‘/status_tracker.php’; var params = [ ‘step=2’, ‘time=1248027314’ ]; (new Image()).src = url + ‘?’ + params.join(‘&’); beacon.onload = function(){ if(this.width==1){ // 成功 }else if(this.width==2){ // 失败,请重试并建立另外一个信标 } }; beacon.onerror = function(){ // 出错,稍后重试并建立另外一个信标 }
这种方法最大的优点是能够发送的数据的长度被限制得很是小。Beacons技术是向服务器回传数据最快且最有效的方式。惟一的缺点是能接收到的响应相似是有限的。
说完传输方式,咱们再讨论一下传输的数据格式:
考虑数据格式时,惟一须要比较的标准是速度。
没有那种数据格式会始终比其它格式更好。优劣取决于要传输的数据以及它在页面上的用途,有的数据格式可能下载速度更快,有的数据格式可能解析更快。常见的数据格式有如下四种:
XML
优点:极佳的通用性(服务端和客户端都能完美支持),格式严格,易于验证;
缺点:极其冗长,每一个单独的数据片断都依赖大量结构,有效数据比例很是低,XML语法有些模糊,解析XML要占用JavaScript程序员至关部分的精力。
JSON
优点:体积更小,在响应信息中结构所占的比例小,JSON有着极好的通用性,大多数服务器端编程语言都提供了编码和解码的类库。对于web开发而言,它是性能表现最好的数据格式;
使用注意:
- 使用`eval()`或尽可能使用JSON.parse()方法解析字符串自己。 - 数组JSON的解析速度最快,可是可识别性最低。 - 使用XHR时,JSON数据被当作字符串返回,该字符串紧接着被eval()装换为原生对象; - JSON-P(JSON with padding):JSON-P由于回调包装的缘由略微增大了文件尺寸,可是其被当作原生js处理,解析速度快了10倍。
HTML
优点:获取后能够直接插入到DOM中,适用于前端CPU是瓶颈而带宽非瓶颈的状况;
缺点:做为数据结构,它即缓慢又臃肿。
自定义格式
通常咱们采用字符分隔的形式。并用split()
对字符串进行分割,split()
对字符串操做其实也是很是快的,一般它能在数毫秒内处理包含10000+个元素的‘分隔符分隔’列表。
对数据格式的总结
- 使用JSON-P数据,经过动态脚本注入使用这种数据,这种方法把数据当作可执行JavaScript而不是字符串,解析速度极快,能跨域使用,可是设计敏感数据时不该该使用它; * 咱们也能够采用经过字符分隔的自定义格式,能够经过使用XHR或动态脚本注入获取对于数据,并用`split()`解析。这项技术解析速度比JSON-P略快,一般文件尺寸更小。
- 在服务器端,设置HTTP头信息以确保响应会被浏览器缓存; - 设置头信息后,浏览器只会在文件不存在缓存时才会向服务器发送Ajax请求; * 在客户端,将获取到的信息存储到本地,从而避免再次请求;
总的来讲,对XHR创造性的使用是一个反应迟钝且平淡无奇的页面与响应快速且高效的页面的区别所在,是一个用户痛恨的站点与用户迷恋的站点的区别所在,合理使用Ajax意义非凡。
上文所述的都是在编码过程当中应该注意的一些事项,除此以外,在代码上线时,咱们也能够作一些相关的优化。
1. 合并多个JavaScript文件用以减小页面渲染所需的HTTP请求数; 2. JavaScript压缩:经过剥离注释和没必要要的空白字符等,能够将文件大小减半,有多种工具能够完成压缩:好比在线的YUI Compressor,在webpack中使用UglifyJsPlugin插件等;完全的压缩常作如下事情: * 将局部变量变为更短的形式; * 尽量用双括号表示法替换点表示法; * 尽量去掉直接量属性名的引号; * 替换字符串的中的转义字符;’aaa\bbb’被替换为”aaa’bbb” * 合并常量; 3. 除了常规的JavaScript压缩,咱们还能够对代码进行Gzip压缩,Gzip是目前最流行的编码方式,它一般能减小70%的下载量,其主要适用于文本包括JS文件,其它类型诸如图片或PDF文件,不该该使用Gzip; 4. 基于不一样的浏览器环境,咱们应该选择发送最合适的代码,好比说目前iPhone版的微信内置浏览器是支持解压Gzip的而安卓端默认不支持,那对iPhone端就能够发送xxx.js,gz文件而对安卓端发送xxx.js文件,这样是能够提升iPhone端的webApp的加载效率的; 5. 合并,预处理,压缩等都既能够在构建时完成,也能够在项目运行时完成,可是推荐能在构建时完成的就尽可能在构建时完成; 6. 合理缓存JavaScript文件也能提升以后打开相同网页的效率: - Web服务器经过“Expires HTTP响应头”来告诉客户端一个资源应当缓存多长时间; * 移动端浏览器大多有缓存限制(iPhone Safari 25k),这种状况下应该权衡HTTP组件数量和它们的可缓存性,考虑将它们分为更小的块; * 合理利用HTML5 离线应用缓存 - 应用更新时有缓存的网页可能会来不及更新(这种状况能够经过更新版本号或开发编号来解决); 7. 使用内容分发网络(CDN); CDN是在互联网上按地理位置分布的计算机网络,它负责传递内容给终端用户。使用CDN的主要缘由是加强Web应用的可靠性,可拓展性,更重要的是提高性能; 8. 值得注意的是,上述不少操做是能够经过自动化处理完成的,学习相关自动化处理工具能够大大提升咱们的开发效率
本文从多方面叙述了web前端的优化思路,谢谢你读到这里,但愿你有所收获,不少知识经过屡次刻意的重复就能成为本身的潜意识,但愿咱们在从此都能在本身的实际开发过程当中,都能以效率更高的方式写JS语句,操做DOM,咱们的应用都很是流畅,UI都不会阻塞,若是你有别的关于优化的具体建议,欢迎一块儿讨论。
本文其实算是我读Nicbolas C.Zakas的《高性能JavaScript》的读书笔记,针对某个话题系统的读书对我来讲,是很是有好处的。系统的读前端方面,计算机方面的经典书籍也是我给本身安排的2017年最主要的任务之一,预计每个月针对某本书或某几本书关于某一个方面,写一篇读书笔记,本文是2017年的第一篇。