(1)减少HTTP请求次数
- 尽量合并图片、CSS、JS。
举例:
如有5个css文件,则需要发出5次http请求,第一次访问页面需要等待很长世间。
如合并成1个css文件,则发出1次http请求,节省网络请求时间,加快页面的加载。
- Expires和Cache-Control (多由服务器端去设置)
浏览器在第一次访问页面时向服务器请求资源,并缓存起来,下次再访问时会判断在缓存中是否已有该资源且有没有更新过,如果已有该资源且未更新过,则直接从浏览器缓存中读取。
(1)Expires 表示存在时间:
允许客户端在这个时间之前不去检查(发请求),等同max-age的效果。但是如果同时存在
则被Cache-Control的max-age覆盖。
(2)Cache-control用于控制HTTP缓存:
1.原理
通过HTTP的Expires和Cache-Control,使已缓存资源不再发起http请求。
(将静态内容设为永不过期,或者很长时间后才过期。)
2.应用
通过HTTP的META设置expires和cache-control
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="Expires" content="Mon, 20 Jul 2009 23:00:00 GMT" />
上述设置仅为举例,实际使用其一即可。
这样写的话仅对该网页有效,对网页中的图片或其他请求无效,并不会做任cache。
(2)对HTTP传输进行压缩 (gzip压缩)(多由服务端去设置)
即在js,css、图片等资源已经压缩的基础上,在HTTP传输过程中的再次压缩。
1.原理
客户端:通过Accept-Encoding头来声明浏览器支持的压缩方式,
服务端:通过content-Encoding来启用压缩,配置压缩的文件类型,压缩方式。
(当客户端的请求到达服务器,服务器对资源进行压缩后,返回给客户端,客户端按照相应的方式进行解析。)
客户端(HTTP请求头)——accept-encoding: gzip, deflate, sdch, br
服务器(HTTP响应头)——content-encoding:gzip
2.应用
以tomcat服务器的配置为例:
找到tomcat安装目录下的conf文件夹下的server.xml文件,进行如下配置,重启tomcat即可:
① compress="on" :表示开启压缩。
② compressionMinSize="2048":表示对大于2KB的文件进行压缩
③ compressableMimeType="text/html,text/xml,application/
JavaScript,text/css,text/plain,image/png,image/jpeg,image/gif" //表示将进行压缩的文件类型
注意:不应该对图片进行再压缩,因为图片本身已经被压缩过
(如果再进行gzip压缩,可能得到的结果是和图片本身大小相差不大,纯粹是浪费服务器的CPU资源来做无用功。)
3.优缺点
对HTTP传输内容进行压缩的优、缺点:
① 优点:减少HTTP响应时间,提升传输效率。
② 缺点:压缩过程占用服务器额外的CPU周期,客户端也要对压缩文件进行解压缩,这也需要占用部分时间。
(3)将CSS文件放在顶部
原理:
将CSS放在底部,页面可以逐步呈现,
但在CSS下载并解析完毕后,已经呈现的文字和图片就要需要根据新的样式重绘,这是一种不好的用户体验。
(4)将javascript放在底部
原理:
script没有async和defer时,JS文件将在下载后立即执行。这种情况下,script放在顶部会阻塞页面呈现,
在网速慢的情况下会导致“白屏”,直到脚本下载完毕才继续呈现页面。因此,script放在底部可以让页面尽快呈现。
script全部放在head中会出现的问题:
在需要操作body中的某元素时,可能找不到该元素,因此,若要放在head中,
一般需要绑定一个监听windows.onload=function(){ ... },
当文档全部解析完之后再执行script代码。
(5)cookie优化
cookie原理:
1、去除没有必要的cookie,如果网页不需要cookie就完全禁掉。
2、将cookie的大小减到最小。
由于cookie在访问对应域名下的资源时都会通过HTTP请求发送到服务器,
因此,减小cookie的大小,能减小HTTP请求报文的大小,提高响应速度。
3、设置合适的过期时间,较长的过期时间可以提高响应速度。
给cookie添加一个过期时间,则cookie信息将存储到硬盘上,即使浏览器退出Cookie还会存
在。只要Cookie未被清除且还在过期时间内,该Cookie就会在访问对应域名时发送给服务器。
4、通过使用不同的domain减少cookie的使用。
cookie在访问对应域名下的资源时都会通过HTTP请求发送到服务器,但在访问一些资源,如js,css和图片时,
大多数情况下cookie是多余的,可以使用不同的domain来存储这些静态资源,这样访问这些资源时就不会发送
多余的cookie,从而提高响应速度。
(6)可缓存的AJAX
异步请求同样的造成用户等待,所以使用ajax请求时,要主动告诉浏览器如果该请求有缓存就去请求缓存内容。
如下代码片段: cache:true就是显式的要求如果当前请求有缓存的话,直接使用缓存
$.ajax({
url : 'url',
dataType : "json",
cache: true, //如果有缓存,就直接使用缓存
success : function(son, status){
}
(7)使用GET来完成AJAX请求
当使用XMLHttpRequest时,浏览器中的POST方法是一个“两步走”的过程:首先发送文件头,然后才发送数据。
因此使用GET获取数据时更加有意义。
(8) 避免404
比如外链的css或者js文件出现问题返回404时,会破坏浏览器对文件的并行加载。
并且浏览器会把试图在返回的404响应内容中找到可能有用的部分当作JavaScript代码来执行。
(9)避免使用CSS表达式
例如:
font-color: expression( (new Date()).getHours()%3 ? “#FFFFFF" : “#AAAAAA" );
这个表达式会持续的在页面上计算样式,影响页面的性能。并且css表达式只被IE支持。
(10)避免空的src和href
当link标签的href属性为空、script标签的src属性为空的时候,浏览器渲染的时候会把当前页面的URL
作为它们的属性值,从而把页面的内容加载进来作为它们的值。所以要避免犯这样的疏忽
(11)缩小favicon.ico并缓存
https://www.ico.la/ 提供的在线免费创建favicon.ico文件服务.
(1)使它尽量在1KB左右。使用ico格式,不要使用png,jpg等其他格式。
(2)将该文件放在单独的主机中,例如 images.mydomain.com . 这样可以避免在请求该文件时发送cookie.
(3)缓存
(12)不要在HTML中缩放图片
需要的图片尺寸是50* 50,不要用500*500的图片在img中设置成50*50
<img width=”50″ height=”50″ src=“hahah.jpg” alt=”hahaha” />
(13)使用CDN
网站上静态资源即css、js全都使用cdn分发,图片亦然。
(14)减少dom元素数量
原理:
减少DOM数量,就会减少浏览器的解析负担
(15)不要使用滤镜
IE独有属性AlphaImageLoader用于修正7.0以下版本中显示PNG图片的半透明效果。
这个滤镜的问题在于浏览器加载图片时它会终止内容的呈现并且冻结浏览器。在每一个元素(不仅仅是图片)
它都会运算一次,增加了内存开支,因此它的问题是多方面的。完全避免使用AlphaImageLoader的最好方法
就是使用PNG8格式来代替,这种格式能在IE中很好地工作。如果你确实需要使用AlphaImageLoader,
请使用下划线_filter又使之对IE7以上版本的用户无效。
Javascript优化篇
- 避免全局查找
在一个函数中会用到全局对象存储为局部变量来减少全局查找,因为访问局部变量的速度要比访问全局变量的速度更快些
function search() {
//当我要使用当前页面地址和主机域名
alert(window.location.href + window.location.host);
}
//最好的方式是如下这样 先用一个简单变量保存起来
function search() {
var location = window.location;
alert(location.href + location.host);
}
- 定时器
如果针对的是不断运行的代码,不应该使用setTimeout,而应该是用setInterval,因为setTimeout每一次都会初始化一个定时器,而setInterval只会在开始的时候初始化一个定时器
var timeoutTimes = 0;
function timeout() {
timeoutTimes++;
if (timeoutTimes < 10) {
setTimeout(timeout, 10);
}
}
timeout();
//可以替换为:
var intervalTimes = 0;
function interval() {
intervalTimes++;
if (intervalTimes >= 10) {
clearInterval(interv);
}
}
var interv = setInterval(interval, 10);
- 字符串连接
如果要连接多个字符串,应该少使用+=,如
s+=a;
s+=b;
s+=c;
应该写成s+=a + b + c;
而如果是收集字符串,比如多次对同一个字符串进行+=操作的话,最好使用一个缓存,使用JavaScript数组来收集,最后使用join方法连接起来
var buf = [];
for (var i = 0; i < 100; i++) {
buf.push(i.toString());
}
var all = buf.join("");
- 避免with语句
和函数类似 ,with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度,由于额外的作用域链的查找,在with语句中执行的代码肯定会比外面执行的代码要慢,在能不使用with语句的时候尽量不要使用with语句。
with (a.b.c.d) {
property1 = 1;
property2 = 2;
}
//可以替换为:
var obj = a.b.c.d;
obj.property1 = 1;
obj.property2 = 2;
数字转换成字符串
般最好用”" + 1来将数字转换成字符串,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:
(“” +) > String() > .toString() > new String()重复使用的调用结果,事先保存到局部变量
//避免多次取值的调用开销
var h1 = element1.clientHeight + num1;
var h2 = element1.clientHeight + num2;
//可以替换为:
var eleHeight = element1.clientHeight;
var h1 = eleHeight + num1;
var h2 = eleHeight + num2;
- 条件分支
(1)将条件分支,按可能性顺序从高到低排列:可以减少解释器对条件的探测次数
(2)在同一条件子的多(>2)条件分支时,使用switch优于if:switch分支选择的效率高于if,在IE下尤为明显。4分支的测试,IE下switch的执行时间约为if的一半。
(3)使用三目运算符替代条件分支
if (a > b) {
num = a;
} else {
num = b;
}
//可以替换为:
num = a > b ? a : b;
- 避免双重解释
如果要提高代码性能,尽可能避免出现需要按照JavaScript解释的字符串,也就是
(1)尽量少使用eval函数
使用eval相当于在运行时再次调用解释引擎对内容进行运行,需要消耗大量时间,而且使用Eval带来的安全性问题也是不容忽视的。
(2)不要使用Function构造器
不要给setTimeout或者setInterval传递字符串参数
var num = 0;
setTimeout('num++', 10);
//可以替换为:
var num = 0;
function addNum() {
num++;
}
setTimeout(addNum, 10);
- 使用一次innerHTML赋值代替构建dom元素
对于大的DOM更改,使用innerHTML要比使用标准的DOM方法创建同样的DOM结构快得多。
var frag = document.createDocumentFragment();
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p');
el.innerHTML = i;
frag.appendChild(el);
}
document.body.appendChild(frag);
//可以替换为:
var html = [];
for (var i = 0; i < 1000; i++) {
html.push('<p>' + i + '</p>');
}
document.body.innerHTML = html.join('');
- 使用DocumentFragment优化多次append
一旦需要更新DOM,请考虑使用文档碎片来构建DOM结构,然后再将其添加到现存的文档中。
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p');
el.innerHTML = i;
document.body.appendChild(el);
}
//可以替换为:
var frag = document.createDocumentFragment();
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p');
el.innerHTML = i;
frag.appendChild(el);
}
document.body.appendChild(frag);
浮点数转换成整型
很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()使用firstChild和nextSibling代替childNodes遍历dom元素
==和===的区别
避免在if和while语句的条件部分进行赋值,如if (a = b),应该写成if (a == b),但是在比较是否相等的情况下,最好使用全等运行符,也就是使用===和!==操作符会相对于==和!=会好点。==和!=操作符会进行类型强制转换
var valueA = "1";
var valueB = 1;
if (valueA == valueB) {
alert("Equal");
}
else {
alert("Not equal");
}
//output: "Equal"
if (valueA === valueB) {
alert("Equal");
}
else {
alert("Not equal");
}
//output: "Not equal"
何时用单引号,何时用双引号
虽然在JavaScript当中,双引号和单引号都可以表示字符串, 为了避免混乱,我们建议在HTML中使用双引号,在JavaScript中使用单引号,但为了兼容各个浏览器,也为了解析时不会出错,定义JSON对象时,最好使用双引号使用return语句需要注意
一条有返回值的return语句不要用()括号来括住返回值,如果返回表达式,则表达式应与return关键字在同一行,以避免压缩时,压缩工具自动加分号而造成返回与开发人员不一致的结果
function F1() {
var valueA = 1;
var valueB = 2;
return valueA + valueB;
}
function F2() {
var valueA = 1;
var valueB = 2;
return
valueA + valueB;
}
alert(F1()); //output: 3
alert(F2()); //ouput: undefined
每条语句末尾须加分号
在if语句中,即使条件表达式只有一条语句也要用{}把它括起来,以免后续如果添加了语句之后造成逻辑错使用+号时需谨慎
JavaScript 和其他编程语言不同的是,在 JavaScript 中,’+'除了表示数字值相加,字符串相连接以外,还可以作一元运算符用,把字符串转换为数字。因而如果使用不当,则可能与自增符’++’混淆而引起计算错误
var valueA = 20;
var valueB = "10";
alert(valueA + valueB); //ouput: 2010
alert(valueA + (+valueB)); //output: 30
alert(valueA + +valueB); //output:30
alert(valueA ++ valueB); //Compile error
switch语句相对if较快
通过将case语句按照最可能到最不可能的顺序进行组织位运算较快
当进行数字运算时,位运算操作要比任何布尔运算或者算数运算快解耦CSS/JavaScript
显示问题的唯一来源应该是CSS,行为问题的唯一来源应该是JavaScript,层次之间保持松散耦合才可以让你的应用程序更加易于维护,所以像以下的代码element.style.color=”red”尽量改为element.className=”edit”,而且不要在css中通过表达式嵌入JavaScript解耦HTML/JavaScript
JavaScript和HTML的紧密耦合:直接写在HTML中的JavaScript、使用包含内联代码的<script>元素、使用HTML属性来分配事件处理程序等
HTML和JavaScript的紧密耦合:JavaScript中包含HTML,然后使用innerHTML来插入一段html文本到页面
其实应该是保持层次的分离,这样可以很容易的确定错误的来源,所以我们应确保HTML呈现应该尽可能与JavaScript保持分离解耦应用程序/事件处理程序
将应用逻辑和事件处理程序相分离:一个事件处理程序应该从事件对象中提取,并将这些信息传送给处理应用逻辑的某个方法中。这样做的好处首先可以让你更容易更改触发特定过程的事件,其次可以在不附加事件的情况下测试代码,使其更易创建单元测试释放javascript对象
在rich应用中,随着实例化对象数量的增加,内存消耗会越来越大。所以应当及时释放对对象的引用,让GC能够回收这些内存控件。
对象:obj = null
对象属性:delete obj.myproperty
数组item:使用数组的splice方法释放数组中不用的item释放dom元素占用的内存
将dom元素的innerHTML设置为空字符串,可以释放其子元素占用的内存。
在rich应用中,用户也许会在一个页面上停留很长时间,可以使用该方法释放积累得越来越多的dom元素使用的内存。避免string的隐式装箱
对string的方法调用,比如’xxx’.length,浏览器会进行一个隐式的装箱操作,将字符串先转换成一个String对象。推荐对声明有可能使用String实例方法的字符串时,采用如下写法:
var myString = new String(‘Hello World’);通过javascript创建的dom对象,必须append到页面中
IE下,脚本创建的dom对象,如果没有append到页面中,刷新页面,这部分内存是不会回收的!
function create() {
var gc = document.getElementById('GC');
for (var i = 0; i < 5000; i++) {
var el = document.createElement('div');
el.innerHTML = "test";
//下面这句可以注释掉,看看浏览器在任务管理器中,点击按钮然后刷新后的内存变化
gc.appendChild(el);
}
}
- 避免全局量(使用闭包函数)
全局变量应该全部字母大写,各单词之间用_下划线来连接。尽可能避免全局变量和函数, 尽量减少全局变量的使用,因为在一个页面中包含的所有JavaScript都在同一个域中运行。所以如果你的代码中声明了全局变量或者全局函数的话,后面的代码中载入的脚本文件中的同名变量和函数会覆盖掉(overwrite)你的。
//糟糕的全局变量和全局函数
var current = null;
function init(){
//...
}
function change() {
//...
}
function verify() {
//...
}
--
--
//解决办法有很多,Christian Heilmann建议的方法是:
//如果变量和函数不需要在“外面”引用,那么就可以使用一个没有名字的方法将他们全都包起来。(闭包)
(function(){
var current = null;
function init() {
//...
}
function change() {
//...
}
function verify() {
//...
}
})();
--
--
//如果变量和函数需要在“外面”引用,需要把你的变量和函数放在一个“命名空间”中
//我们这里用一个function做命名空间而不是一个var,因为在前者中声明function更简单,而且能保护隐私数据
myNameSpace = function() {
var current = null;
function init() {
//...
}
function change() {
//...
}
function verify() {
//...
}
//所有需要在命名空间外调用的函数和属性都要写在return里面
return {
init: init,
//甚至你可以为函数和属性命名一个别名
set: change
};
};
- 尊重对象的所有权
(1)不要为实例或原型添加属性
(2)不要为实例或者原型添加方法
(3)不要重定义已经存在的方法
(4)不要重复定义其它团队成员已经实现的方法,永远不要修改不是由你所有的对象,你可以通过以下方式为对象创建新的功能:
(5)创建包含所需功能的新对象,并用它与相关对象进行交互
(6)创建自定义类型,继承需要进行修改的类型,然后可以为自定义类型添加额外功能
-循环引用
如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。内存泄露的后果是在浏览器关闭前,即使是刷新页面,这部分内存不会被浏览器释放。
简单的循环引用: var el = document.getElementById('MyElement');
var func = function () {
//…
}
el.func = func;
func.element = el;
但是通常不会出现这种情况。通常循环引用发生在为dom元素添加闭包作为expendo的时候。
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
}
init();
init在执行的时候,当前上下文我们叫做context。
这个时候,context引用了el,el引用了function,function引用了context。这时候形成了一个循环引用。
//解决方如下
//解决方如下
//解决方如下
1) 置空dom对象
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
}
init();
//可以替换为:
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
el = null;
}
init();
将el置空,context中不包含对dom对象的引用,从而打断循环应用。
如果我们需要将dom对象返回,可以用如下方法:
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
return el;
}
init();
//可以替换为:
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
try {
return el;
} finally {
el = null;
}
}
init();
//解决方如下
//解决方如下
//解决方如下
2) 构造新的context
function init() {
var el = document.getElementById('MyElement');
el.onclick = function () {
//……
}
}
init();
//可以替换为:
function elClickHandler() {
//……
}
function init() {
var el = document.getElementById('MyElement');
el.onclick = elClickHandler;
}
init();
把function抽到新的context中,这样,function的context就不包含对el的引用,从而打断循环引用。