你好,IT知识点前端知识

前端性能优化

2015-11-14  本文已影响5332人  KeKeMars

AJax 优化

Cookie 优化

DOM 优化

优化节点修改(使用cloneNode在外部更新节点后在通过replace与原始节点互换)

var orig = document.getElementById('container');
var clone = orig.cloneNode(true);
var list = ['foo', 'bar', 'baz'];
var content;
for (var i = 0; i < list.length; i++) {
  content = document.createTextNode(list[i]);
  clone.appendChild(content);
}
orig.parentNode.replaceChild(clone, orig);

优化节点添加(创建DocumentFragment, 在其中插入节点后再添加到页面)

createSafeFragment(document) {
  var list = nodeNames.split( "|" ),
  safeFrag = document.createDocumentFragment();

  if (safeFrag.createElement) {
    while (list.length) {
      safeFrag.createElement(
        list.pop();
      );
    };
  };
  return safeFrag;
};

优化CSS样式转换(尽量采用触发reflow次数少的方式, 使用直接设置元素的className来代替逐条更改元素样式)

// Not Recommended
element.style.fontWeight = 'bold' ;
element.style.marginLeft= '30px' ;
element.style.marginRight = '30px' ;
// Recommended
element.className = 'selectedAnchor' ;

减少DOM元素数量

document.getElementsByTagName( '*' ).length <= 1000

DOM操作优化

DOM操作性能原因

优化DOM操作

优化DOM交互

最小化现场更新
多使用innerHTML替代createElement()appendChild():

reflow回流

发生场景

解决关键: 限制DOM操作所引发的回流

repaint重绘

HTML 优化

动画优化

CSS 优化

慎重选择高消耗的样式

高消耗属性在绘制前需要浏览器进行大量计算: box-shadows border-radius transparency transforms CSS filters(性能杀手)

避免过分重排

当发生重排的时候,浏览器需要重新计算布局位置与大小,更多详情

常见的重排元素: width height padding margin display border-width position top left right bottom font-size float text-align overflow-y font-weight overflow font-family line-height vertical-align clear white-space min-height

正确使用 Display 的属性

Display 属性会影响页面的渲染,请合理使用。

不滥用 Float

Float在渲染时计算量比较大,尽量减少使用。

动画性能优化

动画的基本概念:

一般浏览器的渲染刷新频率是 60 fps,所以在网页当中,帧率如果达到 50-60 fps 的动画将会相当流畅,让人感到舒适。

�高性能动画

多利用硬件能力,如通过 3D 变形开启 GPU 加速(3D 变形会消耗更多的内存和功耗)

一般在 Chrome 中,3D或透视变换(perspective transformCSS属性和对 opacity 进行 CSS 动画会创建新的图层,在硬件加速渲染通道的优化下,GPU 完成 3D 变形等操作后,将图层进行复合操作(Compesite Layers),从而避免触发浏览器大面积重绘和重排。

使用 translate3d 右移 500px 的动画流畅度要明显优于直接使用 left

.ball-1 {
  transition: -webkit-transform .5s ease;
  -webkit-transform: translate3d(0, 0, 0);
}
.ball-1.slidein{
  -webkit-transform: translate3d(500px, 0, 0);
}
.ball-2 {
  transition: left .5s ease; left:0;
}
.ball-2.slidein {
  left:500px;
}

提升 CSS 选择器性能

CSS 选择器对性能的影响源于浏览器匹配选择器和文档元素时所消耗的时间,所以优化选择器的原则是应尽量避免使用消耗更多匹配时间的选择器。CSS 选择器匹配的机制, 如子选择器规则:

#header > a {font-weight:blod;}

CSS 选择器是从右到左进行规则匹配。
最右边选择符为关键选择器。——更多详情

/* Not recommended */
#bookmarkMenuItem > .menu-left { list-style-image: url(blah) }
/* Recommended */
#bookmarkMenuItem { list-style-image: url(blah) }

JS 载入优化

(function() {
  var script,
      scripts = document.getElementsByTagName('script')[0];
  function load(url) {
    script = document.createElement('script');
    script.async = true;
    script.src = url;
    scripts.parentNode.insertBefore(script, scripts);
  }

  load('//apis.google.com/js/plusone.js');
  load('//platform.twitter.com/widgets.js');
  load('//s.widgetsite.com/widget.js');
}());

代码压缩

Javascript优化

优化原则

// 显式判断处理优化
function sum() {  
  var r = 0;  
  for (var i = 0; i < arguments.length; i++) {  
    r += arguments[i];  
  }  
  return r;  
}
// 参数较少时优化
function sum() {  
  switch (arguments.length) {  
    case 1: return arguments[0];  
    case 2: return arguments[0] + arguments[1];  
    case 3: return arguments[0] + arguments[1] + arguments[2];  
    case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3];  
    default:  
      var r = 0;  
      for (var i = 0; i < arguments.length; i++) {  
        r += arguments[i];  
      }  
      return r;  
  }  
}
// 显式调用优化 (速度至少快1倍)
function sum(a, b, c, d, e, f, g) {  
  var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0;  
  if (g === undefined) return r;  
  for (var i = 6; i < arguments.length; i++) {  
    r += arguments[i];  
  }  
  return r;  
}

常规优化

字符串优化

变量优化

function getData(callback) {
  var data = 'some big data';
  callback(null, data);
}
getData(function(err, data) {
  console.log(data);
});

运算符优化

逻辑判断优化

类型转换优化

对象优化

数组优化

循环优化

for(var i = 0; i < values.length; i++) { 
  process(values[i]); 
} 
// 优化1:简化终止条件 
for(var i = 0, len = values.length; i < len; i++) { 
  process(values[i]); 
} 
// 优化2:使用后测试循环(注意:使用后测试循环需要确保要处理的值至少有一个) 
var i values.length - 1; 
if(i > -1) { 
  do { 
    process(values[i]); 
  } while(--i >= 0); 
}
// Jeff Greenberg for JS implementation of Duff's Device 
// 如上展开循环可以提升大数据集的处理速度。
// 假设:
values.length > 0 
function process(v) { 
  alert(v); 
} 
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]; 
var iterations = Math.ceil(values.length / 8); 
var startAt = values.length % 8; var i = 0; 
do { 
  switch(startAt) { 
    case 0 : 
      process(values[i++]); 
    case 7 : 
      process(values[i++]); 
    case 6 : 
      process(values[i++]); 
    case 5 : 
      process(values[i++]); 
    case 4 : 
      process(values[i++]); 
    case 3 : 
      process(values[i++]); 
    case 2 : 
      process(values[i++]); 
    case 1 : 
      process(values[i++]); 
  }
  startAt = 0; 
}
while(--iterations > 0); 
// 接下来给出更快的Duff装置技术,
// 将do-while循环分成2个单独的循环。(注:这种方法几乎比原始的Duff装置实现快上40%。) 
function process(v) {
  alert(v);
}
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]; 
var iterations = Math.floor(values.length / 8); 
var leftover = values.length % 8; 
var i = 0; 
if(leftover > 0) {
  do {
    process(values[i++]);
  }while(--leftover > 0); 
}
do {
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
  process(values[i++]); 
}while(--iterations > 0);
// 针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。
var elements = document.getElementsByTagName( '*' );
for (i = 0; i < elements.length; i++) {
  if (elements[i].hasAttribute( 'selected' )) {}
}
// 如果已知元素存在于一个较小的范围内,
var elements = document.getElementById( 'canvas' ).getElementsByTagName ( '*' );
for (i = 0; i < elements.length; i++) {
  if (elements[i].hasAttribute( 'selected' )) {}
}
// Not recommended
for ( var i = 0; i < 200; i++) {
  try {} catch (e) {}
}
// Recommended
try {
  for ( var i = 0; i < 200; i++) {}
} catch (e) {}

原型优化

JAVASCRIPT中原型的概念,构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承

通过原型优化方法定义

可以把那些不变的属性和方法,直接定义在prototype对象上

作用域链和闭包优化

作用域

作用域(scope) JAVASCRIPT编程中一个重要的运行机制,在JAVASCRIPT同步和异步编程以及JAVASCRIPT内存管理中起着至关重要的作用。
在JAVASCRIPT中,能形成作用域的有如下几点

var foo = function() {
  var local = {};
};
foo();
console.log(local); //=> undefined

var bar = function() {
  local = {};
};
bar();
console.log(local); //=> {}

/**这里我们定义了foo()函数和bar()函数,他们的意图都是为了定义一个名为local的变量。在foo()函数中,我们使用var语句来声明定义了一个local变量,而因为函数体内部会形成一个作用域,所以这个变量便被定义到该作用域中。而且foo()函数体内并没有做任何作用域延伸的处理,所以在该函数执行完毕后,这个local变量也随之被销毁。而在外层作用域中则无法访问到该变量。而在bar()函数内,local变量并没有使用var语句进行声明,取而代之的是直接把local作为全局变量来定义。故外层作用域可以访问到这个变量。**/

local = {};
// 这里的定义等效于
global.local = {};
作用域链

在JAVASCRIPT编程中,会遇到多层函数嵌套的场景,这就是典型的作用域链的表示。

function foo() {
  var val = 'hello';
  function bar() {
    function baz() {
      global.val = 'world;'
    };
    baz();
    console.log(val); //=> hello
  };
  bar();
};
foo();

/**在`JAVASCRIPT`中,变量标识符的查找是从当前作用域开始向外查找,直到全局作用域为止。所以`JAVASCRIPT`代码中对变量的访问只能向外进行,而不能逆而行之。baz()函数的执行在全局作用域中定义了一个全局变量val。而在bar()函数中,对val这一标识符进行访问时,按照从内到外的查找原则:在bar函数的作用域中没有找到,便到上一层,即foo()函数的作用域中查找。然而,使大家产生疑惑的关键就在这里:本次标识符访问在foo()函数的作用域中找到了符合的变量,便不会继续向外查找,故在baz()函数中定义的全局变量val并没有在本次变量访问中产生影响。**/

减少作用域链上的查找次数
JAVASCRIPT代码在执行的时候,如果需要访问一个变量或者一个函数的时候,它需要遍历当前执行环境的作用域链,而遍历是从这个作用域链的前端一级一级的向后遍历,直到全局执行环境。

/**效率低**/
for(var i = 0; i < 10000; i++){
    var but1 = document.getElementById("but1");
}
/**效率高**/
/**避免全局查找**/
var doc = document;
for(var i = 0; i < 10000; i++){
    var but1 = doc.getElementById("but1");
}
/**上面代码中,第二种情况是先把全局对象的变量放到函数里面先保存下来,然后直接访问这个变量,而第一种情况是每次都遍历作用域链,直到全局环境,我们看到第二种情况实际上只遍历了一次,而第一种情况却是每次都遍历了,而且这种差别在多级作用域链和多个全局变量的情况下还会表现的非常明显。在作用域链查找的次数是`O(n)`。通过创建一个指向`document`的局部变量,就可以通过限制一次全局查找来改进这个函数的性能。**/
闭包

JAVASCRIPT中的标识符查找遵循从内到外的原则。

function foo() {
  var local = 'Hello';
  return function() {
    return local;
  };
}
var bar = foo();
console.log(bar()); //=> Hello

/**这里所展示的让外层作用域访问内层作用域的技术便是闭包(Closure)。得益于高阶函数的应用,使foo()函数的作用域得到`延伸`。foo()函数返回了一个匿名函数,该函数存在于foo()函数的作用域内,所以可以访问到foo()函数作用域内的local变量,并保存其引用。而因这个函数直接返回了local变量,所以在外层作用域中便可直接执行bar()函数以获得local变量。**/

闭包是JAVASCRIPT的高级特性,因为把带有​​内部变量引用的函数带出了函数外部,所以该作用域内的变量在函数执行完毕后的并不一定会被销毁,直到内部变量的引用被全部解除。所以闭包的应用很容易造成内存无法释放的情况。

良好的闭包管理

循环事件绑定、私有属性、含参回调等一定要使用闭包时,并谨慎对待其中的细节。
循环绑定事件,我们假设一个场景:有六个按钮,分别对应六种事件,当用户点击按钮时,在指定的地方输出相应的事件。

var btns = document.querySelectorAll('.btn'); // 6 elements
var output = document.querySelector('#output');
var events = [1, 2, 3, 4, 5, 6];
// Case 1
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function(evt) {
    output.innerText += 'Clicked ' + events[i];
  };
}
/**这里第一个解决方案显然是典型的循环绑定事件错误**/
// Case 2
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = (function(index) {
    return function(evt) {
      output.innerText += 'Clicked ' + events[index];
    };
  })(i);
}
/**第二个方案传入的参数是当前循环下标,而后者是直接传入相应的事件对象。事实上,后者更适合在大量数据应用的时候,因为在JavaScript的函数式编程中,函数调用时传入的参数是基本类型对象,那么在函数体内得到的形参会是一个复制值,这样这个值就被当作一个局部变量定义在函数体的作用域内,在完成事件绑定之后就可以对events变量进行手工解除引用,以减轻外层作用域中的内存占用了。而且当某个元素被删除时,相应的事件监听函数、事件对象、闭包函数也随之被销毁回收。**/
// Case 3
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = (function(event) {
    return function(evt) {
      output.innerText += 'Clicked ' + event;
    };
  })(events[i]);
}

避开闭包陷阱
闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会导致内存泄漏。
闭包的性能不如使用内部方法,更不如重用外部方法。
由于IE 9浏览器的DOM节点作为COM对象来实现,COM的内存管理是通过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会造成循环引用从而导致内存泄漏。

善用函数, 避免闭包陷阱

(function(win, doc, $, undefined) {
  // 主业务代码
})(window, document, jQuery);
/**RequireJS**/
define(['jquery'], function($) {
  // 主业务代码
});
/**SeaJS**/
define('m​​odule', ['dep', 'underscore'], function($, _) {
  // 主业务代码
});

被定义在全局作用域的对象,可能是会一直存活到进程退出的,如果是一个很大的对象,那就麻烦了。

比如有的人喜欢在JavaScript中做模版渲染:

<?php
  $db = mysqli_connect(server, user, password, 'myapp');
  $topics = mysqli_query($db, "SELECT * FROM topics;");
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>你是猴子请来的逗比么?</title>
</head>
<body>
  <ul id="topics"></ul>
  <script type="text/tmpl" id="topic-tmpl">
    <li class="topic">
      <h1><%=title%></h1>
      <p><%=content%></p>
    </li>
  </script>
  <script type="text/javascript">
    var data = <?php echo json_encode($topics); ?>;
    var topicTmpl = document.querySelector('#topic-tmpl').innerHTML;
    var render = function(tmlp, view) {
      var complied = tmlp
        .replace(/\n/g, '\\n')
        .replace(/<%=([\s\S]+?)%>/g, function(match, code) {
          return '" + escape(' + code + ') + "';
        });

      complied = [
        'var res = "";',
        'with (view || {}) {',
          'res = "' + complied + '";',
        '}',
        'return res;'
      ].join('\n');

      var fn = new Function('view', complied);
      return fn(view);
    };

    var topics = document.querySelector('#topics');
    function init()
      data.forEach(function(topic) {
        topics.innerHTML += render(topicTmpl, topic);
      });
    }
    init();
  </script>
</body>
</html>

在从数据库中获取到的数据的量是非常大的话,前端完成模板渲染以后,data变量便被闲置在一边。可因为这个变量是被定义在全局作用域中的,所以JAVASCRIPT引擎不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。可是 如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了。当UI渲染完成之后,代码对data的引用也就随之解除,而在最外层函数执行完毕时,JAVASCRIPT引擎就开始对其中的对象进行检查,data也就可以随之被回收

事件优化

// 捕获型事件先发生。
// 两种事件流会触发DOM中的所有对象,从document对象开始,也在document对象结束。
<ul id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click",function(e) {
  // e.target is the clicked element!
  // If it was a list item
  if(e.target && e.target.nodeName == "LI") {
    // List item found!  Output the ID!
    console.log("List item ",e.target.id.replace("post-")," was clicked!");
  }
});

性能测试工具

js性能优化和内存泄露问题及检测分析工具

Node.js中的内存检查

var heapdump = require('heapdump');
var fs = require('fs');
var path = require('path');
fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);

在业务代码中引入node-heapdump之后,我们需要在某个运行时期,向Node.js进程发送SIGUSR2信号,让node-heapdump抓拍一份堆内存的快照。
$ kill -USR2 (cat app.pid)
这样在文件目录下会有一个以heapdump-<sec>.<usec>.heapsnapshot格式命名的快照文件,我们可以使用浏览器的Developer Tools中的Profiles工具将其打开,并进行检查。

分析浏览器提供的Waterfall图片来思考优化入口。

新的测试手段Navigation Resource User timing

Developer Tools - Profiles

JITGC优化(内存优化)

Type-specializing JIT优化

JavaScript的内存回收机制

在V8引擎中所有的JAVASCRIPT对象都是通过堆来进行内存分配的。当我们在代码中声明变量并赋值时,V8引擎就会在堆内存中分配一部分给这个变量。如果已申请的内存不足以存储这个变量时,V8引擎就会继续申请内存,直到堆的大小达到了V8引擎的内存上限为止(默认情况下,V8引擎的堆内存的大小上限在64位系统中为1464MB,在32位系统中则为732MB

V8引擎对堆内存中的JAVASCRIPT对象进行分代管理。

垃圾回收算法

回收对象

// 当代码执行完毕时,对象val和bar()并没有被回收释放,
// JAVASCRIPT代码中,每个变量作为单独一行而不做任何操作,
// JAVASCRIPT引擎都会认为这是对对象的访问行为,存在了对对象的引用
var val = 'hello world';
function foo() {
  return function() {
    return val;
  };
}
global.bar = foo();

内存泄露及处理

给DOM对象添加的属性是一个对象的引用。

var MyObject = {};
document.getElementByIdx_x('myDiv').myProp = MyObject;

解决方法:在window.onunload事件中写上:

document.getElementByIdx_x('myDiv').myProp = null;

DOM对象与JS对象相互引用。

function Encapsulator(element) {
   this.elementReference = element;
   element.myProp = this;
}
new Encapsulator(document.getElementByIdx_x('myDiv'));

解决方法:在window.onunload事件中写上:

document.getElementByIdx_x('myDiv').myProp = null;

给DOM对象用attachEvent绑定事件。

function doClick() {}
element.attachEvent("onclick", doClick);

解决方法:在onunload事件中写上:

element.detachEvent('onclick', doClick);

从外到内执行appendChild。这时即使调用removeChild也无法释放。

var parentDiv = document.createElement_x("div");
var childDiv = document.createElement_x("div");
document.body.appendChild(parentDiv);
parentDiv.appendChild(childDiv);

解决方法:从内到外执行appendChild:

var parentDiv =   document.createElement_x("div");
var childDiv = document.createElement_x("div");
parentDiv.appendChild(childDiv);
document.body.appendChild(parentDiv);

反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)。

for(i = 0; i < 5000; i++) {
  hostElement.text = "asdfasdfasdf";
}
// 这种方式相当于定义了5000个属性

解决方法:无, 避免这样书写代码。
IE下闭包会引起跨页面内存泄露。

内存不是缓存

CollectGarbage
CollectGarbage是IE的一个特有属性,用于释放内存的使用方法,将该变量或引用对象设置为null或delete然后在进行释放动作,
在做CollectGarbage前,要必需清楚的两个必备条件:(引用)。

服务端优化


FastJS 书写最快的JavaScript

try_catch

try_catch捕获错误代码块会造成性能损失

regex_method

正则表达式匹配最快方法

random_int

获取随机整数

is_object_empty

判断对象是否为空

sample_from_array

从数组中抽样

uniq_str_array

数组去重

var _map = Object.create(null);
    for (var i = 0; i < arr.length; i++) {
      _map[arr[i]] = true;
    }
    var newArr = Object.keys(_map);

arguments_to_array

默认参数(类数组)转换成数组

clone_object

克隆对象

for_loop

for循环

hidden_class

函数中初始化对象后函数速度更快

function withHiddenClass() {
  this._timeout = 0;
  this._url = '';
  this._type = '';
}
withHiddenClass.prototype.timeout = timeout;
withHiddenClass.prototype.url = url;
withHiddenClass.prototype.type = type;

function timeout(timeout) {
  this._timeout = timeout;
}

function url(url) {
  this._url = url;
}

function type(type) {
  this._type = type;
}
function withoutHiddenClass() {
}
withoutHiddenClass.prototype.timeout = timeout;
withoutHiddenClass.prototype.url = url;
withoutHiddenClass.prototype.type = type;

function timeout(timeout) {
  this._timeout = timeout;
}

function url(url) {
  this._url = url;
}

function type(type) {
  this._type = type;
}

inner_function

减少函数嵌套,函数嵌套拆分成多函数速度更快

iterate_object

迭代对象

map_loop

数组内部进行遍历求值,先构造好数组后,再把值传进去,速度比较快

new_array

构造新数组,采用字面量形式更快

next_tick

下一步操作

start_with

检测字符串是否以特定字符开头

str_concat

字符串连接

str_to_int_number

字符串转换成整数


参考

上一篇 下一篇

猜你喜欢

热点阅读