Skip to content

Latest commit

 

History

History
272 lines (260 loc) · 13 KB

对象和数组专题.md

File metadata and controls

272 lines (260 loc) · 13 KB

###对象专题

  • 减少不必要的对象创建:
    • 创建对象本身对性能影响并不大,但由于JAVASCRIPT的垃圾回收调度算法,导致随着对象个数的增加,性能会开始严重下降(复杂度O(n^2))。
      • 如常见的字符串拼接问题,单纯的多次创建字符串对象其实根本不是降低性能的主要原因,而是是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了垃圾回收的开销。
    • 复杂的JAVASCRIPT对象,其创建时时间和空间的开销都很大,应该尽量考虑采用缓存。
    • 尽量作用JSON格式来创建对象,而不是var obj=new Object()方法。前者是直接复制,而后者需要调用构造器。
  • 对象查找
    • 避免对象的嵌套查询,因为JAVASCRIPT的解释性,a.b.c.d.e嵌套对象,需要进行4次查询,嵌套的对象成员会明显影响性能。
    • 如果出现嵌套对象,可以利用局部变量,把它放入一个临时的地方进行查询。
  • 对象属性
    • 访问对象属性消耗性能过程(JAVASCRIPT对象存储)。
      • 先从本地变量表找到对象
      • 然后遍历属性
      • 如果在当前对象属性列表里没找到。
      • 继续从prototype向上查找。
      • 且不能直接索引,只能遍历。
			function f(obj) { 
				return obj.a + 1; 
			}

###数组专题

  • 当需要使用数组时,可使用JSON格式的语法
    • 即直接使用如下语法定义数组:[parrm,param,param...],而不是采用new Array(parrm,param,param...)这种语法。使用JSON格式的语法是引擎直接解释。而后者则需要调用Array的构造器。
  • 如果需要遍历数组,应该先缓存数组长度,将数组长度放入局部变量中,避免多次查询数组长度。
    • 根据字符串、数组的长度进行循环,而通常这个长度是不变的,比如每次查询a.length,就要额外进行一个操作,而预先把var len=a.length,则每次循环就少了一次查询。

###运算符专题

  • 使用运算符时,尽量使用+=-=*=\=等运算符号,而不是直接进行赋值运算。
  • 位运算
    • 当进行数学运算时位运算较快,位运算操作要比任何布尔运算算数运算快,如取模逻辑与逻辑或也可以考虑用位运算来替换。

###字符串专题

  • 对字符串进行循环操作。
    • 替换、查找等操作,使用正则表达式。
      • 因为JAVASCRIPT的循环速度较慢,而正则表达式的操作是用C写成的API,性能比较好。
  • 字符串的拼接。
    • 字符串的拼接在我们开发中会经常遇到,所以我把其放在首位,我们往往习惯的直接用+=的方式来拼接字符串,其实这种拼接的方式效率非常的低,我们可以用一种巧妙的方法来实现字符串的拼接,那就是利用数组的join方法,具体请看我整理的:Web前端开发规范文档中的javaScript书写规范倒数第三条目。
    • 不过也有另一种说法,通常认为需要用Array.join的方式,但是由于SpiderMonkey等引擎对字符串的“+”运算做了优化,结果使用Array.join的效率反而不如直接用“+”,但是如果考虑IE6,则其他浏览器上的这种效率的差别根本不值一提。具体怎么取舍,诸君自定。

###循环专题

  • 循环是一种常用的流程控制。
    • JAVASCRIPT提供了三种循环。
      • for(;;)
        • 推荐使用for循环,如果循环变量递增或递减,不要单独对循环变量赋值,而应该使用嵌套的++–-运算符。
        • 代码的可读性对于for循环的优化。
        • -=1
        • 从大到小的方式循环(这样缺点是降低代码的可读性)。
				/**效率低**/   
			    var divs = document.getElementsByTagName("div");    
			    for(var i = 0; i < divs.length; i++){   
			        ...  
			    }    
				/**效率高,适用于获取DOM集合,如果纯数组则两种情况区别不到**/  
			    var divs = document.getElementsByTagName("div");   
			    for(var i = 0, len = divs.length; i < len; i++){   
			        ... 
			    }
				/**在`IE6.0`下,`for(;;)`循环在执行中,第一种情况会每次都计算一下长度,而第二种情况却是在开始的时候计算长度,并把其保存到一个变量中,所以其执行效率要高点,所以在我们使用`for(;;)`循环的时候,特别是需要计算长度的情况,我们应该开始将其保存到一个变量中。**/
	- `while()`。
		- `for(;;)`、`while()`循环的性能基本持平。
	- `for(in)`。
		- 在这三种循环中`for(in)`内部实现是构造一个所有元素的列表,包括`array`继承的属性,然后再开始循环,并且需要查询hasOwnProperty。所以`for(in)`相对`for(;;)`循环性能要慢。
  • 选择正确的方法 - 避免不必要的属性查找。 - 访问变量数组O(1)操作。 - 访问对象上的属性是一个O(n)操作。
	对象上的任何属性查找都要比访问变量或数组花费更长时间,因为必须在原型链中对拥有该名称的属性进行一次搜索,即属性查找越多,执行时间越长。所以针对需要多次用到对象属性,应将其存储在局部变量。
- 优化循环。
	- 减值迭代。
		- 大多数循环使用一个从0开始,增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加有效。
	- 简化终止条件。
		- 由于每次循环过程都会计算终止条件,故必须保证它尽可能快,即避免属性查找或其它O(n)的操作。
	- 简化循环体。
		- 循环体是执行最多的,故要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
	- 使用后测试循环。
		- 最常用的for和while循环都是前测试循环,而如do-while循环可以避免最初终止条件的计算,因些计算更快。
	>
				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);
		}
- 展开循环。
	- 当循环的次数确定时,消除循环并使用多次函数调用往往更快。
	- 当循环的次数不确定时,可以使用Duff装置来优化。
		- Duff装置的基本概念是通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。
	>
		// 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%。)
	> 
		// Speed Up Your Site(New Riders, 2003)
		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);
	>
	针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。
  • 避免在循环中使用try-catch
    • try-catch-finally语句在catch语句被执行的过程中会动态构造变量插入到当前域中,对性能有一定影响。
    • 如果需要异常处理机制,可以将其放在循环外层使用。
      • 循环中使用try-catch
			for ( var i = 0; i < 200; i++) {
			  try {} catch (e) {}
			}
	- 循环外使用try-catch
			try {
			  for ( var i = 0; i < 200; i++) {}
			} catch (e) {}
  • 避免遍历大量元素: - 避免对全局DOM元素进行遍历,如果parent已知可以指定parent在特定范围查询。
		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' )) {}
		}

###eval优化

  • 避免eval
    • eval会在时间方面带来一些效率,但也有很多缺点。
      • eval会导致代码看起来更脏。
      • eval会需要消耗大量时间。
      • eval会逃过大多数压缩工具的压缩。

###with优化

  • 尽可能地少用with语句,因为它会增加with语句以外的数据的访问代价。

  • 避免使用with

    with语句将一个新的可变对象推入作用域链的头部,函数的所有局部变量现在处于第二个作用域链对象中,从而使局部变量的访问代价提高。

      var person = {
           name: “Nicholas",
           age: 30
      }
      function displayInfo() {
           var count = 5;
           with (person) {
               alert(name + ' is ' + age);
               alert( 'count is ' + count);
           }
      }
    

COOKIE 专题

  • 减少 COOKIE 的大小。
  • 使用无 COOKIE 的域。
    • 比如图片 CSS 等静态文件放在静态资源服务器上并配置单独域名,客户端请求静态文件的时候,减少 COOKIE 反复传输时对主域名的影响。

###性能测试工具

  • js性能优化和内存泄露问题及检测分析工具
 - 性能优化ajax工具`diviefirebug`
 - [web性能分析工具YSlow]
	- `performance`性能评估打分,右击箭头可看到改进建议。 
	- `stats`缓存状态分析,传输内容分析。 
	- `components`所有加载内容分析,可以查看传输速度,找出页面访问慢的瓶颈。 
	- `tools`可以查看js和css,并打印页面评估报告。
 - 内存泄露检测工具`sIEve`
	- `sIEve`是基于`IE`的内存泄露检测工具,需要下载运行,可以查看dom孤立节点和内存泄露及内存使用情况。			
		1. 列出当前页面内所有dom节点的基本信息(html id style 等) 			
		1. 页面内所有dom节点的高级信息 (内存占用,数量,节点的引用) 		
		1. 可以查找出页面中的孤立节点 		
		1. 可以查找出页面中的循环引用		
		1. 可以查找出页面中产生内存泄露的节点
 - 内存泄露提示工具`leak monitor`
	- `leak monitor`在安装后,当离开一个页面时,比如关闭窗口,如果页面有内存泄露,会弹出一个文本框进行即时提示。
 - 代码压缩工具
	- YUI压缩工具
	- Dean Edwards Packer
	- JSMin
- `Blink/Webkit`浏览器
	- 在`Blink/Webkit`浏览器中(`Chrome`, `Safari`, `Opera`),我们可以借助其中的`Developer Tools`的`Profiles`工具来对我们的程序进行内存检查。
			Developer Tools - Profiles
  • Node.js中的内存检查
    • Node.js中,我们可以使用node-heapdumpnode-memwatch模块进​​行内存检查。
		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。