目录
数据访问
数据的存储位置,关系到代码运行时数据被检索到的速度,JS中有四种数据存储位置:直接量、变量、数组、对象。其中直接量可能比较少听说,其实可以理解为表示匿名函数、匿名对象的一个变量,如var sum = function(a,b){return a+b},sum就是一个函数直接量。
四种数据存储位置中,直接量和局部变量的访问性能微不足道,性能消耗高的是全局变量、数组和对象成员。
Scope
每一个JS函数都是一个对象,函数对象和其他对象一样,拥有编程可以访问的属性和仅供JS引擎使用的属性。其中有一个虚拟属性叫做[[Scope]],[[Scope]]中包含函数作用域中对象的集合(作用域链),它表示当前函数环境可以访问的数据,以链式存在,作用域链的创建改变过程如下:
1.
声明一个函数时函数作用域链中被推入一个可变的全局变量,代表了所有全局范围内的变量。作用域链:A
2.
执行一个函数时会创建一个内部对象(执行完销毁),称其为运行期上下文,运行期上下文会拷贝函数本身的[[Scope]]对象,按照原来的顺序复制到运行期上下文的作用域链中,此时的运行期上下文作用域链:A。然后运行期上下文的作用域链中被推入一个新的对象,名叫‘激活对象’,它存储了所有的局部变量、命名参数、参数集合和this的接口。此时运行期上下文作用域链:B -> A
3.
当函数运行遇到变量时,会检索作用域链,首先会去B中查找是否存在,再去A中查找,显然A中变量访问性能要比B中的更慢。
优化数据检索
- 既然全局变量的访问性能更慢,那么当一个函数中多次访问全局变量的时候,就应该用一个局部变量来进行缓存。
function fun(){
var doc = document;
a = doc.getElementById("a");
b = doc.getElementById("b");
c = doc.getElementById("c");
}
- 减少使用动态作用域链:with()可以临时改变函数的作用域链,把变量推到作用域链最前端,如下面的demo,运行期上下文作用域链:document -> B -> A,在使用getElementById的时候会先到document对象里查找,速度确实快了。但是问题也显而易见,因为局部变量对象B被推到了第二,所以当访问a,b,c变量的时候,多查找了一次document对象,有时候得不偿失。
// 当使用with时,document临时被放到了作用域链的最前端
// 在使用getElemntById时会先到document对象里面寻找
function fun(){
with(document){
a = getElementById("a");
b = getElementById("b");
c = getElementById("c");
}
}
- try-catch:当运行出错时,程序会自动把异常推到作用域链的最前端,带来性能问题,可以在catch块中运行错误处理函数,将错误对象作为参数传给错误处理函数,catch块中作用域链的改变就没什么影响了。
try{
//do something
}catch(e){
handleError(e);
}
- 慎用闭包:原因有两个,一个是函数闭包访问外部变量最少经过两次查找。二是如果闭包需要访问外部变量,那么函数运行期的激活对象就会被保存,无法销毁,不仅会消耗更多内存,在IE中还会导致内存泄漏。
原型
- 什么是原型?初学者往往很难理解原型的概念,如果你了解过OOP的编程语言,那么对继承一定不陌生,原型可以简单的理解为被继承的基类。任何时候创建一个实例,这些实例都将自动拥有一个Object作为他们的原型。
- 一个对象拥有两种成员:实例成员和原型成员。实例成员直接存在于实例自身,原型成员则是从原型继承。
- 下面的例子中,cat的实例成员是name和age,原型成员就是cat.proto中的成员属性,即Object中的属性(Object.prototype)。如调用toString方法,会先在cat的实例成员中查找,找不到再去原型成员中查找,所以同样导致性能问题。
// cat._proto_ = Object
var cat = {
name:"xiaohua",
age:1
}
原型链
- 对象的原型决定了一个实例的类型,默认情况下对象都是Object的实例,但当我们使用构造器创建实例时,就创建了另外一种类型的原型。
- 如下面的例子,
cat._proto_
=Animal.prototype
,Animal.prototype._proto_
=Object
。如果我们调用了toString,搜索路径如下:cat ->cat._proto_
(Animal.prototype) ->cat._proto_.constructor
(Animal) ->cat._proto_.constructor._proto_
(Object)
function Animal(name,age){
this.name = name;
this.age = age;
}
// 通过prototype可以向函数对象添加成员
Animal.prototype.sayHello = function(){
console.log("Hello,I am a " + this.name);
}
var cat = new Animal("cat",1);
var dog = new Animal("dog",1);
缓存对象成员的值
由原型链的访问过程可知,当访问的对象成员处在较深处时,访问的代价还是挺大的,所以当多次访问同一个对象成员时,可以进行缓存。
function foo(ele,className1,className2) {
// 可以用一个局部变量来存储ele.className
// var eleClassName = ele.className;
return ele.className == className1 || ele.className == className2;
}
总结
- 针对数据访问导致的相关性能问题,主要的解决办法就是对数据进行暂存,比如将全局变量暂存为局部变量,减少作用域链的深入搜索;将实例的属性暂存,减少对原型链的多次深入搜索;另一个就是减少使用动态作用域和闭包。