JavaScript性能优化

前端性能优化

前言

javascript在浏览器中运行的性能,可以认为是开发者所面临的最严重的可用性问题。为了帮助大家有效的解决这个问题,今天给大家分享下这篇文章。

这个问题因为javascript的阻塞性而变得复杂,事实上,多数浏览器使用单一进程来处理用户界面和js脚本执行,所以同一时刻只能做一件事。js执行过程耗时越久,浏览器等待响应的时间越长 。

一、加载和执行

1. JS文件放置位置

IE8,FF,3.5,Safari 4Chrome都允许并行下载js文件,当script下载资源时不会阻塞其他script的下载。但是js下载仍然会阻塞其他资源的下载,如图片。尽管脚本下载不会互相影响,但页面仍然必须等待所有js代码下载并执行完才能继续。因此仍然存在脚本阻塞问题,推荐将所有js文件放在body标签底部以减少对整个页面的影响。

2. 减少页面外链脚本文件的数量将会提高页面性能

http请求会带来额外的开销,因此下载单个100k的文件将比下载4个25k的文件效率更高。

3. 动态脚本加载技术

无论何时启动下载,文件的下载和执行都不会阻塞页面其他进程 。

function laodScript(url,callback){
var script = document.createElement('script');
script.type = 'text/javascript';

if(script.readyState){ // ie
script.onreadystatechange = function(){
if(script.readyState == 'loaded' || script.readyState == 'complete'){
script.onreadystatechange = null;
callback()
}
}
}else{ //其他浏览器
script.onload = function(){
callback()
}
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}

// 使用
loadScript('./a.js',function(){
loadScript('./b.js',function(){
loadScript('./c.js',function(){
console.log('加载完成')
})
})
})

4. 使用loadLazyjs,LABjs等这样的无阻塞加载类库

LABjs使用方法如下:

<script src="lab.js"></script>
// 链式调用时文件逐个下载,.wait()用来指定文件下载并执行完毕后所调用的函数
$LAB.script('./a.js')
.script('./b.js')
.wait(function(){
App.init();
})

// 为了保证执行顺序,可以这么做,此时a必定在b前执行
$LAB.script('./a.js').wait()
.script('./b.js')
.wait(function(){
App.init();
})

二、数据存取

1. 数据存储

在js中,数据存储的位置会对代码整体性能产生重大影响。数据存储共有4种方式:字面量,变量,数组项,对象成员。他们有着各自的性能特点。

2. 数据访问

访问字面量和局部变量的速度最快,相反,访问数组和对象相对较慢。

由于局部变量存在于作用域链的起始位置,因此访问局部变量的比访问跨域作用变量更快。

3. 避免嵌套对象

嵌套的对象成员会明显影响性能,应尽量避免。

4. 避免深度原型链访问属性和方法

属性和方法在原型链位置越深,访问他的速度越慢。

5. 常用数据存储

通常我们可以把需要多次使用的对象成员,数组元素,跨域变量保存在局部变量中来改善js性能。

三、DOM编程

1. DOM操作

访问DOM会影响浏览器性能,修改DOM则更耗费性能,因为他会导致浏览器重新计算页面的几何变化。通常的做法是减少访问DOM的次数,把运算尽量留在JS这一端。

注:如过在一个对性能要求比较高的操作中更新一段HTML,推荐使用innerHTML,因为它在绝大多数浏览器中运行的都很快。但对于大多数日常操作而言,并没有太大区别,所以你更应该根据可读性,稳定性,团队习惯,代码风格来综合决定使用innerHTML还是createElement()。

2.HTML集合优化

HTML集合包含了DOM节点引用的类数组对象,一直与文档保持连接,每次你需要最新的信息时,都会重复执行查询操作,哪怕只是获取集合里元素的个数。

**集合转数组collToArr**:

function collToArr(coll){
for(var i=0, a=[], len=coll.length; i<len; i++){
a.push(coll[i]);
}
return a
}

缓存集合length

访问集合元素时使用局部变量(即将重复的集合访问缓存到局部变量中,用局部变量来操作)

3.使用高效API访问DOM

使用高效的API属性和querySelectorAll()

属性名替代属性
childrenchildNodes
childElementCountchildNodes.length
firstElementChildfirstChild
lastElementChildlastChild
nextElementSiblingnextSibling
previousElementSiblingpreviousSibling

4.减少重绘和重排

浏览器在下载完页面的所有组件——html,js,css,图片等之后,会解析并生成两个内部数据结构—— DOM树,渲染树.一旦DOM树和渲染树构建完成,浏览器就开始绘制页面元素(paint)

4.1 重排触发条件

总的来说就是改变元素的形态就会触发重排。重排必定重绘。

  1. 添加或删除可见的DOM
  2. 元素位置变化
  3. 元素尺寸改变
  4. 内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口尺寸变化
  7. 出现滚动条时会触发整个页面的重排
4.2 重绘触发条件

不改变元素形态,比如修改背景色这样的形式就会触发重绘。

4.3 最小化重绘和重排

批量改变样式:

/* 使用cssText */
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 20px';

批量修改dom的优化方案——使元素脱离文档流-对其应用多重改变-把元素带回文档


function appendDataToEl(option){
var targetEl = option.target || document.body,
createEl,
data = option.data || [];
// 让容器脱离文档流,减少重绘重排
var targetEl_display = targetEl.style.display;
targetEl.style.display = 'none';

// *****创建文档片段来优化Dom操作****
var fragment = document.createDocumentFragment();
// 给元素填充数据
for(var i=0, max = data.length; i< max; i++){
createEl = document.createElement(option.createEl);
for(var item in data[i]){
if(item.toString() === 'text'){
createEl.appendChild(document.createTextNode(data[i][item]));
continue;
}
if(item.toString() === 'html'){
createEl.innerHTML = item,data[i][item];
continue;
}
createEl.setAttribute(item,data[i][item]);
}
// ****将填充好的node插入文档片段****
fragment.appendChild(createEl);
}
// ****将文档片段统一插入目标容器****
targetEl.appendChild(fragment);
// 显示容器,完成数据填充
targetEl.style.display = targetEl_display;
}

// 使用
var wrap = document.querySelectorAll('.wrap')[0];
var data = [
{name: 'xujaing',text: '选景', title: 'xuanfij'},
{name: 'xujaing',text: '选景', title: 'xuanfij'},
{name: 'xujaing',text: '选景', title: 'xuanfij'}
];
appendDataToEl({
target: wrap,
createEl: 'div',
data: data
});

注释:上面的优化方法使用了文档片段: 当我们把文档片段插入到节点中时,实际上被添加的只是该片段的子节点,而不是片段本身。可以使得dom操作更有效率。

缓存布局信息

// 缓存布局信息
let current = el.offsetLeft;
current++;
el.style.left = current + 'px';
if(current > 300){
stop();
}

慎用:hover

​ 如果有大量元素使用:hover,那么会降低相应速度,导致CPU升高。

使用事件委托(通过事件冒泡实现)来减少事件处理器的数量,减少内存和处理时间

function delegation(e,selector,callback){
e = e || window.event;
var target = e.target || e.srcElement;

if(target.nodeName !== selector || target.className !== selector || target.id !== selector){
return;
}
if(typeof e.preventDefault === 'function'){
e.preventDefault();
e.stopPropagation();
}else{
e.returnValue = false;
e.cancelBubble = true;
}

callback()
}

5. .渲染树变化的排列和刷新

大多数浏览器通过队列化修改并批量执行来优化重排过程,然而获取布局信息的操作会导致队列强制刷新。

offsetTop,offsetWidth...
scrollTop,scrollHeight...
clientTop,clientHeight...
getComputedStyle()

优化建议:将设置样式的操作和获取样式的操作分开

// 设置样式
body.style.color = 'red';
body.style.fontSize = '24px'
// 读取样式
let color = body.style.color
let fontSize = body.style.fontSize

获取计算属性的兼容写法:

function getComputedStyle(el){
var computed = (document.body.currentStyle ? el.currentStyle : document.defaultView.getComputedStyle(el,'');
return computed
}

四、算法和流程控制

1. 循环优化

减少属性查找并反转(可以提升50%-60%的性能)


// for 循环
for(var i=item.length; i--){
process(item[i]);
}
// while循环
var j = item.length;
while(j--){
process(item[i]);
}

使用Duff装置来优化循环

2. 基于函数的迭代

比基于循环的迭代慢

items.forEach(function(value,index,array){
process(value);
})

3. 条件语句

通常情况下switch总比if-else快,但是不是最佳方案。

五、字符串和正则表达式

除了IE外,其他浏览器会尝试为表达式左侧的字符串分配更多的内存,然后简单的将第二个字符串拷贝到他的末尾,如果在一个循环中,基础字符串位于最左侧,就可以避免重复拷贝一个逐渐变大的基础字符串。

使用[\s\S]来匹配任意字符串。

去除尾部空白的常用做法:

if(!String.prototype.trim){
String.prototype.trim = function(){
return this.replace(/^\s+/,'').replace(/\s\s*$/, '')
}
}

六、快速响应的用户界面

1. 浏览器的UI线程

用于执行javascript和更新用户界面的进程。

2. 定时器分辨率

在windows系统中定时器分辨率为15毫秒,因此设置小于15毫秒将会使IE锁定,延时的最小值建议为25ms。

3. 用延时数组分割耗时任务

function multistep(steps,args,callback){
var tasks = steps.concat();

setTimeout(function(){
var task = tasks.shift();
task.apply(null, args || []); //调用Apply参数必须是数组

if(tasks.length > 0){
setTimeout(arguments.callee, 25);
}else{
callback();
}
},25);
}

4. 记录代码运行时间批处理任务

function timeProcessArray(items,process,callback){
var todo = item.concat();

setTimeout(function(){
var start = +new Date();

do{
process(todo.shift());
}while(todo.length > 0 && (+new Date() - start < 50));

if(todo.length > 0){
setTimeout(arguments.callee, 25);
}else{
callback(items);
}
},25)
}

5. 使用WebWorker

它引入了一个接口,能使代码运行且不占用浏览器UI线程的时间。

一个Worker由如下部分组成:

① 一个navigator对象,包括appName,appVersion,user Agent和platform;

② 一个location对象,只读;

③ 一个self对象,指向全局worker对象;

④ 一个importScripts()方法,用来加载worker所用到的外部js文件;

⑤ 所有的ECMAScript对象。如object,Array,Date等;

⑥ XMLHttpRequest构造器;

⑦ setTimeout(),setInterval();

⑧ 一个close()方法,它能立刻停止worker运行。

应用场景

  1. 编码/解码大字符串;

  2. 复杂数学运算(包括图像,视屏处理);

  3. 大数组排序。

// worker.js
var worker = new Worker('code.js');
// 接收信息
worker.onmessage = function(event){
console.log(event.data);
}
// 发送数据
worker.postMessage('hello');

// code.js

// 导入其他计算代码
importScripts('a.js','b.js');

self.onmessage = function(event){
self.postMessage('hello' + event.data);
}

七、ajax优化

1. 向服务器请求数据的五种方式

① XMLHttpRequest

② Dynamic script tag insertion 动态脚本注入

③ iframes

④ Comet(基于http长连接的服务端推送技术)

⑤ Multipart XHR(允许客户端只用一个http请求就可以从服务器向客户端传送多个资源)

2.单纯向服务端发送数据(beacons方法)—信标

// 唯一缺点是接收到的响应类型是有限的
var url = '/req.php';
var params = ['step=2','time=123'];

(new Image()).src = url + '?' + params.join('&');

// 如果向监听服务端发送回的数据,可以在onload中实现
var beacon = new Image();
beacon.src = ...;
beacon.onload = function(){
...
}
beacon.onerror = function(){
...
}

3. ajax性能的一些建议

缓存数据

1.在服务端设置Expires头信息确保浏览器缓存多久响应(必须GET请求)

2.客户端把获取到的信息缓存到本地,避免再次请求

八、编程实践

1.避免重复性工作

2.使用Object/Array字面量

3. 多用原生方法

九.、构建与部署高性能的js应用

js的http压缩 当web浏览器请求一个资源时,它通常会发送一个Accept-Encoding HTTP头来告诉Web服务器它支持那种编码转换类型。这个信息主要用来压缩文档以获取更快的下载,从而改善用户体验。Accept-Encoding可用的值包括:gzip,compress,deflate,identity。 如果Web服务器在请求中看到这些信息头,他会选择最合适的编码方式,并通过Content-Encoding HTTP头通知WEB浏览器它的决定。

使用H5离线缓存。

使用内容分发网络CDN。

对页面进行性能分析。

// 检测代码运行时间
var Timer = {
_data: {},
start: function(key){
Timer._data[key] = new Date();
},
stop: function(key){
var time = Timer._data[key];
if(time){
Timer._data[key] = new Date() - time;
};
console.log(Timer._data[key]);
return Timer._data[key]
}
};

十、压缩组件

web客户端可以通过http请求中的Accept-Encoding头来表示对压缩的支持

// 浏览器请求时对http header设置
Accept-Encoding: gzip
// Web服务器响应时对http header设置
Content-Encoding: gzip

压缩能将响应的数据量减少将近70%,因此可考虑对html,脚本,样式,图片进行压缩。

十一、减少DNS查找

1.DNS查找可以被缓存起来以提高性能:DNS信息会留在操作系统的DNS缓存中(Microsoft Windows上的“DNS Client服务”,之后对该主机名的请求无需进行过多的查找

2.TTL(time to live): 该值告诉客户端可以对记录缓存多久。建议将TTL值设置为一天。(客户端收到DNS记录的平均TTL只有最大TTL值的一半因为DNS解析器返回的时间是其记录的TTL的剩余时间,对于给定的主机名,每次执行DNS查找时接收的TTL值都会变化)。

3.通过使用Keep-Alive和较少的域名来减少DNS查找 。

4.一般建议将页面组件分别放到至少2个,但不要超过4个主机名下。

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一心就想回农村

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值