js性能——数据存储

这篇文章是基于对js的作用域链、原型有所了解的朋友们一起交流学习的。

那么我们先看看:

js的四种基本数据存取

  • 字面量:
    只代表自身,不存储在特定位置。js中字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的null和undefined值。
  • 本地变量:
    开发人员使用关键字var定义的数据存储单元。
  • 数组元素
    存储在js数组对象内部,咦数据作为索引。
  • 对象成员
    存储在js对象内部,以字符串作为索引

作用域链和标识符解析

每个js函数都表示为一个对象,更确切的说,是Function对象的一个实例,Function对象同其他对象一样,拥有可以编程访问的属性,和一系列不能通过代码访问而仅供JavaScript引擎存取的内部属性。

其中一个内部属性是[[Scope]],这个属性包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问。函数作用域中的每个UI小被称为一个可变对象,每个可变对象都以“键值对”的形式存在。

作用域链详情查看红宝书(js高级程序设计)第六章

如何影响性能?

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程,从而决定从哪里获取数据。然后就会开始去搜索执行环境的作用域链,查找同名的标识符。搜索过程从当前的作用域头开始,也就是当前运行函数的活动对象。

  • 如果找到了,就使用这个标识符对应的那个变量
  • 如果没有找到,就继续搜索作用域链的下个对象,持续进行,直到找到
  • 最后若无法搜索到,则会视为未定义改标识符

重点来了!就是这个搜索过程影响了我们的性能。

标识符解析的性能

首先要认清一个事实,一个标识符所在的位置越深,它的读写速度也就越慢。so,局部变量的总是最快的,全局变量的读写通常是最慢的。(因为全局变量总是存在执行环境作用域链的最末端)

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function initUI(){
var bd = document.body,
links = document.getElememtsByTagName('a'),
i = 0,
len = links.length;

while(i < len){
update(link[i++]);
}
document.getElementById("go-btn").onclick = function(){
start();
};
bd.className = 'active';
}

上面这个函数,引用了三次document,而document是全局对象,必须遍历整个作用域链才能在全局作用域链中找到。

那么该怎么优化呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function initUI(){
var doc = document,
bd = doc.body,
links = doc.getElememtsByTagName('a'),
i = 0,
len = links.length;

while(i < len){
update(link[i++]);
}
doc.getElementById("go-btn").onclick = function(){
start();
};
bd.className = 'active';
}

这样访问全局变量的次数瞬间减少到一次

改变作用域链(动态作用域)

一般来说,一个执行环境的作用域链是不会被改变的,但是js有两个语句可以在实行时临时改变作用域链。

  • with
  • try catch
  • eval

这的用法需要谨慎,这次不展开详说(其实因为我也没有完全弄通/(ㄒoㄒ)/~~)

闭包、作用域和内存

闭包闭包,总是说闭包会使得内存泄漏,性能下降之类,可是你们知道是为什么吗?

首先,闭包是允许函数访问局部作用域之外的数据。
先看下个代码

1
2
3
4
5
6
function assignEvent(){
var id = 'zyy'
document.getElementById('save-btn').onclick = function(){
saveDocument(id);
}
}

assignEvent()函数给一个dom元素设置时间处理函数,这个时间处理函数就是一个闭包。在函数执行时创建,并且能访问所属作用域的id变量。为了让这个闭包访问id,必须创建一个特定的作用域链。

执行时,一个包含了变量id以及其他数据的活动对象呗创建,成为执行环境作用域链中的第一个对象,全局在后。

一般来说,函数的活动对象会随着执行环境一同销毁,但是引入闭包的同事,由于存在闭包,所以激活对象复发被销毁,意味着脚本中的闭包与非闭包相比,需要更多内存开销

原型

提到原型,则不能不提原型链,原型原型链是什么就不详细介绍了。

但是了解原型链的同学都知道,原型链的深度越深,访问的越深,访问时间就越久,当然,最久的就是找不到的属性,它会一直找到最深处~~

嵌套成员

和所有的原理一样,eg:window.location.href 每次遇到点操作符,嵌套成员会导js引擎搜索所有对象成员。而
location.hrefwindow.location.href快。如果不是实例属性,还需要花更多时间去搜索。

缓存对象成员值

eg:

1
2
3
function hasEitherClass(element,className1,className2){
return element.className == className1 || element.className == className2;
}

上面的element.className被用了两次,我们可以用个变量保存

1
2
3
4
function hasEitherClass(element,className1,className2){
var currentClass = element.className;
return currentClass == className1 || currentClass == className2;
}

小结

  1. 访问字面量和局部变量速度最快,访问数组元素和对象成员相对慢。
  2. 访问局部变量比夸作用域的快。变量在作用域链中越深,时间越长。全局访问速度最慢。
  3. 避免使用with 和try catch
  4. 嵌套对象成员会影响性能,尽量少用
  5. 属性或方法在原型链中的位置越深,访问速度就越慢
  6. 通过吧常用的对象成员、数组元素、跨域变量保存在局部变量中来改善javascript的性能。

参考资料

  1. 高性能javascript

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×