54

web性能优化的15条实用技巧

 4 years ago
source link: https://www.tuicool.com/articles/zqye2yn
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

加载和执行

一.提高加载性能

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

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

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

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. 无阻塞加载类库——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();

})

二. 数据存取与JS性能

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

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

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

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

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

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

三. DOM编程

1.访问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. 遍历DOM

① 使用只返回元素节点的API遍历DOM,因为这些API的执行效率比自己实现的效率更高:

属性名 被替代属性 children childNodes childElementCount childNodes.length firstElementChild firstChild lastElementChild lastChild nextElementSibling nextSibling previousElementSibling previousSibling

② 选择器API——querySelectorAll()

querySelectorAll()方法使用css选择器作为参数并返回一个NodeList——包含着匹配节点的类数组对象,该方法不会返回HTML集合,因此返回的节点不会对应实时文档结构,着也避免了HTML集合引起的性能问题。

let arr = document.querySelectorAll('div.warning, div.notice > p')

4.重绘和重排

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

① 重排发生的条件:

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

重排必定重绘

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

}

6.最小化重绘和重排

①.批量改变样式

/* 使用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()

}

四.算法和流程控制

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

// for 循环

for(var i=item.length; i--){

process(item[i]);

}

// while循环

var j = item.length;

while(j--){

process(item[i]);

}

2.使用Duff装置来优化循环(该方法在后面的文章中会详细介绍)

3.基于函数的迭代(比基于循环的迭代慢)

items.forEach(function(value,index,array){

process(value);

})

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

五.字符串和正则表达式

1.除了IE外,其他浏览器会尝试为表达式左侧的字符串分配更多的内存,然后简单的将第二个字符串拷贝到他的末尾,如果在一个循环中,基础字符串位于最左侧,就可以避免重复拷贝一个逐渐变大的基础字符串。2.使用[\s\S]来匹配任意字符串 3.去除尾部空白的常用做法:

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.使用Web Worker: 它引入了一个接口,能使代码运行且不占用浏览器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.避免重复工作

// 1.延迟加载

var a = (x,y) =>{

if(x > 4){

a = 0;

}else{

a = 1;

}

}

// 需要使用时调用

a();


// 2.条件预加载(适用于函数马上执行并频繁操作的场景)

var b = a > 0 ? '4' : '0';

2.使用Object/Array字面量

3.多用原生方法

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

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

2.使用H5离线缓存

3.使用内容分发网络CDN

4.对页面进行性能分析

// 检测代码运行时间

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]

}

};

十. 浏览器缓存

1.添加Expires头

2.使用cache-control cache-ontrol详解 浏览器缓存机制

十一. 压缩组件

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

// 浏览器请求时对http header设置

Accept-Encoding: gzip

// Web服务器响应时对http header设置

Content-Encoding: gzip

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

十二. 白屏现象的原因

浏览器(如IE)在样式表没有完全下载完成之前不会呈现页面,导致页面白屏。如果样式表放在页面底部,那么浏览器会花费更长的时间下载样式表,因此会出现白屏,所以最好把样式表放在head内。白屏是浏览器对“无样式闪烁”的修缮。如果浏览器不采用“白屏”机制,将页面内容逐步显示(如Firefox),则后加载的样式表将导致页面重绘重排,将会承担页面闪烁的风险。

十三. css表达式使用一次性表达式(但最好避免css表达式)

使用css表达式时执行函数重写自身

// css

p{

background-color: expression(altBgcolor(this))

}

// js

function altBgcolor(el){

el.style.backgroundColor = (new Date()).getHours() % 2 ? "#fff" : "#06c";

}

十四. 减少DNS查找

DNS缓存和TTL

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个主机名下
复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK