javascript性能优化之数据访问

javascript性能优化之数据访问

    有很长一段时间没更新博客了,最近琐事缠身,刚刚算是告一段落,今天终于可以好好静下来,好好写下日志了,好了废话不多说了,直接进入主题吧。    


    在javascript中我们最常操作的就是访问我们自己的数据,例如访问我们声明的变量,访问我们的方法,访问数组等等。在javascript的性能优化中最重要的一个环节应当属于数据访问。javascript中有四种基本的数据访问位置:直接量(Literal values),变量(String),数组(Array)对象Object)这4种js数据。


           可能大家对直接量还不太清楚,直接量其实包括:字符串,数字,布尔值,对象,数组,函数,正则表达式,空值(Null)。


1、性能优化风向标

         网上关于javascript的性能优化文章有非常多,但是我个人觉得大部分的性能都是跟上面4种数据紧密相关的,也就是说JS访问上面4种数据的方法很大程度上就 决定了我们的的JS效率,今天就谈谈我总结的JS数据访问中的一些方法给大家参考一下。我们先来看下一个统计图,在不同浏览器下访问上面四种数据类型的时间,下图中分别在不同浏览器中,对这4中数据进行了20万次的访问后的统计图。

         从上述的统计结果我们可以发现浏览器访问直接量和局部变量的速度是最快的,相对的访问数组和对象的效率是最差的。大家看到这个结果后应该就知道我们在优化JS的过程中,尽量使用直接量还有局部变量,尽可能的少用数组还有对象成员的使用,因为我发现有许多同学喜欢使用对象来存储一些值,其实在这些对象方法中有很多是可以直接用直接量或者局部变量来代替的。


2、作用域管理

      2.1 变量作用域的优化

        对JS作用域的理解是判断一个人是否真正掌握JS这门语言的一个关键,作用域在不仅在性能优化,而且从功能的角度,都有这很大的影响。下面我们来举个简单的例子:

var global_val = "0";
function Fn(){
	var local_val = "5";
	console.log(global_val); // 0
	console.log(local_val); // 5
}

    上面我定义了一个全局变量和一个局部变量,然后在Fn对象方法中去访问我们定义的两个变量,这个方法其实就是一个作用域,单位名运行了Fn方法后它会去调用外头的一个全局变量global_val,然后还会在调用自己内部定一个的一个变量local_val,然后把他们同时输出后,再将自己函数内部的东西都给销毁,因为javascript有自己一套独特的垃圾回收机制。但是他不会销毁我们定义的全局变量,因为他无法判断这个全局变量是否已经在后面的函数中不会被再次调用,只有当浏览器执行完所有的JS后,认为这个全局变量已经不再被需要的时候,就会把它拖到垃圾桶里面然后删除。


    那现在我们就知道了,函数内部调用完后会销毁内部的东西,但是没被销毁的全局变量便会占用我们的内存,如果全局变量数量非常的多的时候,这个时候其实对我们的电脑的内存就形成了很大的压力,特别是在大的项目里,讲到这里,大家应该就知道了,其实我们在日常编程的时候最好是避免使用全局变量,尽量用局部变量,因为局部变量的速度访问会比全局的更快,而且使用完后是不占内存的。全局的变量总是处于上下文作用域中最后的一个位置,所以访问的他距离一定是最远的,耗时最长的,这也是为什么我建议大家最好尽量使用局部变量来访问的原因。


    如果按照上面的思想,虽然我们想尽可能的少的使用全局变量,但是我们还是没办法完全避免不使用到全局变量,所以这个时候最好的方法就是我们用局部变量来存储本地范围外的变量值,举个例子:

function Fn(){
	var btn_txt = document.getElementById("btn").innerText;
	document.getElementById("btn").onclick = function(){
		alert(btn_txt);
		document.body.style.color = "#06c";
	};
}

        上面这个函数中,对document引用了三次,其实document是一个全局对象,浏览器每次在查找此变量的时候,必须要查找整个作用域链,然后在最后的全局变量中找到它,没引用一次都要重复这样自己的操作,其实这对性能的印象就非常的大了,我想许多人都经常会在自己的函数方法中使用document变量,其实在上面的这个方法中,我们虽然无法避免的必须使用到document,因为我们在一个函数中需要多次引用,但是我们可以把他储存在一个局部变量中,这样子我们就可以只访问一次全局变量,但是实现上面一样的功能,改写下以上代码后:

function Fn(){
	var doc = document,
		btn_txt = doc.getElementById("btn").innerText;
	doc.getElementById("btn").onclick = function(){
		alert(btn_txt);
		doc.body.style.color = "#06c";
	};
}

   上面的代码我们其实只需访问一次全局变量,然后把他存储到我们的局部变量中,这样子,再次需要访问的时候我们只需要访问我们定义的局部变量即可,这样子的方法是代价最低的。


      2.2 闭包导至内存泄漏

        闭包应该算是javascript最大一个特色之一,但是它就好比是一把双刃剑,缺陷就是他会导致我们的JS垃圾回收机制对其失效,我们上文说过,一个函数执行完毕后,JS会销毁器内部的内容,但是当涉及闭包的时候,这个函数内部就无法被销毁了,那他只要存在,就一定会占用我们的内存,关于闭

包的详细介绍我建议有兴趣的人看一下我写的这篇文章《javascript闭包》,里面有介绍怎么销毁使用完后的闭包内容。

3、对象(Object)优化

        在上面的统计图中,我们知道对象成员在javascript访问中,是属于最慢的,所以这一对象也是JS性能优化中的重点部分,要想知道为什么对象为什么在浏览器中的访问是最慢的,那我们必须要先了解对象的的一个原理。


        3.1 Object Prototypes 对象原型

        对javascript有一定深入了解的人,都应该知道JS的对象都是基于原型,原型应该算是其他对象的一个基础,我们定一个的每一个对象都具有原型。原型这以概念又完全不同于我们在面向对象编程中“类”的概念,原型对象可以为所有给定类型的对象实例所共享。我们来看一个例子:

var Cat = {
	name : "小花",
	age : "3"
}
console.log(Cat.name);//小花
console.log(Cat.toString());//[object Object]

   在上面这段代码中,我定义了nameage这两个成员,但是我们并没有定义toString()方法,但是这个方法调用了之后却没有出问题,而且正常工作了。那就可以说明toString()方法就是Cat对象继承的原型成员。

        

      浏览器访问对象成员的的过程和我们上诉所说的访问变量非常的相似,  上面的代码中我们调用了Cat.toString()函数,这个时候浏览器会先搜索Cat实例中是否存在toString()方法,如果不存在,他变会再次向上在进行搜索,直到找到这个方法为止。当然上面的toString()只是javascript原型方法中的一个,他自身还有非常多的方法例如:toUpperCase()valueOf()toPrecision()toExponential()等等一些方法,我想许多人在工作中肯定会经常调用这些原型方法来操作的。


    我们的对象其实都是继承了Object的实例,并继承了他的所有方法,例如上面所说的toString()等方法,JS中有一个方法可以检测一个对象是否具有特定名称的实例成员。例如:

console.log(Cat.hasOwnProperty("name")); //true
console.log(Cat.hasOwnProperty("toString")); //false


  我们现在来谈谈要怎么去优化对象的访问速度,其实javascript中对象的访问原理和我们上诉说的变量其实是一样的,其实每当我们访问对象原型的方法的时候,他的执行效率是最慢的,例如上面的Cat例子,我们访问Cat.toString()方法的时候,浏览器首先会去搜索Cat中是否有toString()的方法,如果没有,他会再去查找原型对象,每多进行一次查找,就会降低我们代码的执行效率。而且还有我们有时候还会使用Cat.name.toString()来操作,这个时候对性能的损耗就更加严重了,他会一级一级的往上去查找toString()方法,直到找到他为止。这就是我们熟称的原型链。我们来看一组统计报告:

        上图显示了浏览器没深入一级原型链的访问,都会导致性能上的损失,特别是在IE和firefox3.5版本中的表现最为突出,这份报告是较为早的统计了,可能我们现在的浏览器都对javascript性能做过了优化,但是IE依旧是存在的着各种版本,所以我们在开发的时候还是尽量要避免使用多级反问原型链。


        3.2 缓存对象成员的值进行优化       

        有许多时候我们会需要使用到使用对象的方法,而且可能一个功能我们会连续调用原型链中的对象方法,这个时候我们优化的方法就是将这个原型方法储存到局部变量中,然后第二次第三次访问的时候直接访问我们的局部变量,原理和我们上诉变量优化的方法有些类似,因为我们知道浏览器访问直接量和局部变量比访问对象本身要快非常多,具体我们来看一个例子吧:

function Fn(elm,class1,Class2){
	return elm.className == class1 && elm.className == class2;
}

    上面这段代码中,我们连续访问了两次elm.className,在这个函数中,我们引起了两次对象成员的搜索过程,其实这个写法在性能上并不是最佳方法,我们其实可以将elm.className存入一个局部变量中,然后使用的时候调用这个局部变量,我修改下上面的代码,例如:

function Fn(elm,class1,Class2){
	var elm_className = elm.className;
	return elm_className == class1 && elm_className == class2;
}

    大家看下上面优化后的代码,原本我们的代码需要搜索两次elm.className对象两次,现在只需要进行一次,以后如果我们的函数有需要使用elm.className的时候我们就可以直接调用这个局部变量。大家不要小看这个优化点,相信大家在大型的项目中是需要操作非常多的次的原型对象的,这些对象中,或许一个大型的JS项目中你或许会操作几百或者上千次,那个时候这个优化方案就会起到非常大的作用了,不相信你们可以测试一下自己写的代码,通过测速工具测试一下速度。

    

    今天的总结就写到这里了,如果上诉总结中,有什么不对的地方,希望大家多多指正,谢谢!


转载请注明来自 520UED http://www.520ued.com/article/5388351fb992a7c43f5c2054

comments powered by Disqus