Javascript性能优化(二)- 数据访问优化

目录

数据访问

数据的存储位置,关系到代码运行时数据被检索到的速度,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.prototypeAnimal.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;
}

总结

  • 针对数据访问导致的相关性能问题,主要的解决办法就是对数据进行暂存,比如将全局变量暂存为局部变量,减少作用域链的深入搜索;将实例的属性暂存,减少对原型链的多次深入搜索;另一个就是减少使用动态作用域和闭包。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值