专栏首页java跬步高性能的JavaScript--数据访问(1)

高性能的JavaScript--数据访问(1)

写在前面

数据存储在哪里,关系到代码运行期间数据被检索到的速度。在JavaScript中,此问题相对简单,因为数据存储只有少量方式可供选择。正如其他语言那样,数据存储位置关系到访问速度。在JavaScript中有四种基本的数据访问位置:

1.Literal values 直接量

直接量仅仅代表自己,而不存储于特定位置。 JavaScript的直接量包括:字符串,数字,布尔值,对象,数组,函数,正则表达式,具有特殊意义的空值,以及未定义。

2.Variables 变量

开发人员使用var关键字创建用于存储数据值。

3.Array items 数组项

具有数字索引,存储一个JavaScript数组对象。

4.Object members 对象成员

具有字符串索引,存储一个JavaScript对象。

每一种数据存储位置都具有特定的读写操作负担。大多数情况下,对一个直接量和一个局部变量数据访问的性能差异是微不足道的。访问数组项和对象成员的代价要高一些,具体高多少,很大程度上依赖于浏览器。总的来说,直接量和局部变量的访问速度要快于数组项和对象成员的访问速度。,如果关心运行速度,那么尽量使用直接量和局部变量,限制数组项和对象成员的使用。

管理作用域

1.作用域链和标识符解析

每一个JavaScript函数都被表示为对象。进一步说,它是一个函数实例。函数对象正如其他对象那样,拥有你可以编程访问的属性,和一系列不能被程序访问,仅供JavaScript引擎使用的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义。内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可由函数访问。此函数作用域链中的每个对象被称为一个可变对象,每个可变对象都以“键值对”的形式存在。当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问的数据。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain,不简称sc)来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)

例如:

function add(x,y){
            var b=x+y;
            return b;
        }

 当add()函数创建后,它的作用域链中填入一个单独的可变对象,此全局对象代表了所有全局范围定义的变量。此全局对象包含诸如窗口、浏览器和文档之类的访问接口。

如图:

 上图就是函数Add()的作用域链。

Add函数的作用域链将在运行时用到。如果运行下面的代码

var total = add(5, 10);

运行此add函数时建立一个内部对象,称作“运行期上下文”。一个运行期上下文定义了一个函数运行时的环境。对函数的每次运行而言,每个运行期上下文都是独一的。所以每次调用同一个函数就会导致多处调用上下文。当函数执行完毕,运行期上下文就被销毁。 一个运行期上下文有它自己的作用域链,用于标示符解析。当运行期上下文被创建时,它的作用域被初始化,连同运行函数的[[Scope]]属性中所包含的对象。这些值按照它们出现在函数中的顺序,被复制到运行期上下文的作用域链中。这项工作一旦完成,一个被称作“激活对象”的新对象就为运行期上下文创建好了。此激活对象作为函数执行期的一个可变对象,包含访问所有局部变量,命名参数,参数集合,和this的接口,然后,这个对象被推入作用域的前端。当作用域链被销毁时,激活对象也一同销毁。

 上图是运行时Add()函数的作用域链。

在函数运行过程中,没遇到一个变量,标识符识别过程要决定从哪里获得或者存储数据,此过程收索运行期上下文的作用域链,查找同名的标识符。搜索工作从运行函数的激活目标之作用域链的前端开始。如果找到了,那么就使用这个具有指定标识符的变量,如果没有找到,搜索工作将进入作用域链的下一个对象。此过程持续进行,直到找到标示符。如果整个过程都没有找到那么被认为是undefined。正是这种搜索过程影响了性能。

2.标识符解析的性能

标示符识别不是免费的,事实上没有哪种电脑操作可以不产生性能开销。在运行期山下文的作用域链中,一个标示符所处的位置越深,它的读写速度就越慢。所以,函数中局部变量的访问速度总是最快的,而全局变量通常是最慢的(优化的JavaScript引擎在某些情况下可以改变这种情况,如谷歌浏览器)。全局变量总是处于运行前上下文作用域链的最后一个位置。所以总是最远才能触及。 用局部变量存储本地范围之外的变量值,如果它们在函数中的使用多于一次。考虑下面的例子:

function initUI(){
  var 
    bd = document.body,
    links = document.getElementsByTagName_r("a"),
     i = 0,
    len = links.length;
  
  
   while(i < len){

    update(links[i++]); }

    document.getElementById("go-btn").onclick = function(){ start();

   };

   bd.className = "active";
}

此函数包括三个对document的引用,document是一个全局对象。搜索此变量,必须遍历整个作用域链,指导最后在全局变量对象中找到它。你可以通过这种方法减轻重复的全局变量访问对性能的影响;首先将全局变量的引用放在一个局部变量中,然后使用整个局部变量代替全局变量。 代码重写如下:

function initUI(){

    var doc = document,
    bd = doc.body,
    links = doc.getElementsByTagName_r("a"),
    i = 0,
   len = links.length;
  
   while(i < len){

     update(links[i++]); 
   }

   doc.getElementById("go-btn").onclick = function(){ 
    start();
   };

  bd.className = "active";
 }    

 initUI()的新版本首先将document的引用存入局部变量doc中,现在访问全局变量的次数是1次,而不是3次。用doc替代document更快,因为它是一个局部变量。当然,这个简单的函数不回显示出巨大的性能改进,因为数量的原因。不过如果几十个全局变量被反复访问,那么性能改进将明显的多么出色。

3.改变作用域链

 一般来说,一个运行期上下文的作用域链不会突然被改变。但是,有两种表达式可以在运行时临时改变运行期上下文作用域链。第一个是with表达式。

with表达式为所有的对象属性创建了一个默认操作变量。在其他语言中,类似的功能通常用来避免书写一些重复的代码。initUI()函数可以重写成如下样式:

function  initUI(){
with (document){ //avoid!
var bd = body,
links = getElementsByTagName_r("a"),
i = 0,
len = links.length;
while(i < len){
update(links[i++]);
}
getElementById("go-btn").onclick = function(){
start();
};
bd.className = "active";
}
}

此重写的initUI()版本使用了一个with表达式,避免多次书写document,这看起来似乎更有效率,而实际上却产生了一个性能问题。 当代码流执行到一个with表达式时,运行期上下文的作用域链被临时改变了。一个新的可变对象将被创建,她包含指定对象的所有属性。此对象被插入到作用域链的前端,意味着现在函数的所有局部变量都被推入第二个作用域链对象中,所以访问代价更高了。

通过将document对象传递给with表达式,一个新的可变对象容纳了document对象的所有属性,被插入到作用域链的前端。这使得访问document的属性非常快,但是访问局部变量的速度却变慢了,例如bd变量。正因为这个原因,最好不要使用with表达式。正如前面提到的,只要简单地将document存储在一个局部变量中,就可以获得性能上的提升。

在JavaScript中不只是with表达式人为地改变运行期上下文的作用域链,try-catch表达式的catch子句具有相同效果。当try块发生错误时,程序流程自动转入catch块,并将异常对象推入作用域链前端的一个可变对象中。在catch块中,函数的所有局部变量现在被放在第二个作用域链对象中。例如: 

try {
  methodThatMightCauseAnError();
} catch (ex){
  alert(ex.message); //scope chain is augmented here
}

但是,只要catch子句执行完毕,作用域链就会返回到原来的状态。

如果使用得当,try-catch表达式是非常有用的语句,所以不建议完全避免。如果你计划使用一个try-catch语句,请确保你了解可能发生的错误。一个try-catch语句不应该作为JavaScript错误的解决办法。如果你知道一个错误会经常发生,那说明应当修正代码本身的问题

你可以通过精缩代码的办法最小化catch子句对性能的影响。一个很好的模式是将错误交给一个专用函数来处理。如:

try {
  methodThatMightCauseAnError();
} catch (ex){
  handleError(ex); //delegate to handler method
}

handleError()函数是catch子句中运行的唯一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。由于只有一条语句,没有局部变量访问,作用域链临时改变就不会影响代码的性能。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • linq 学习笔记之 Linq基本子句

    var value=from v in values where v.indexof("it")>-1 select v

    DougWang
  • js 遮罩层 loading 效果

    //关闭事件<button onclick='LayerHide()'>关闭</button>,在loadDiv(text)中,剔除出来

    DougWang
  • 了解 Spring Data JPA

    自 JPA 伴随 Java EE 5 发布以来,受到了各大厂商及开源社区的追捧,各种商用的和开源的 JPA 框架如雨后春笋般出现,为开发者提供了丰富的选择。它一...

    DougWang
  • 爬虫不得不学之 JavaScript 函数对象篇

    今天好像是情人节?所以最适合面向对象,JavaScript 也有对象,我们也可以随时面向对象,方便得很,那怎样才有对象呢?下面告诉你!

    sergiojune
  • 闭包 + 局部作用域 === 如鱼得水

    闭包:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

    前端_AWhile
  • 你知道Python中的4种变量作用域是哪些吗?

    意思是,在局部找不到的,便去局部外的局部作用域找(例如 闭包),再找不到的就去全局作业域里找,再找不到就去内建作业域中找。

    lyhue1991
  • JavaScript 易错知识点整理

    本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由...

    哲洛不闹
  • 致命一击:藉助 GPU 让流感不再成为中年危机

    这两天一篇《流感下的北京中年》又刷了Lady我的朋友圈。文中那句“习惯是如此之轻,以至于无法察觉。又是如此之重,以至于无法挣脱”深深触动了我这个中年少女之心。想...

    GPUS Lady
  • 转载:从程序员的角度看ASCII, GB2312, UNICODE, UTF-8

    以下内容转自博客:http://blog.chinaunix.net/uid-22670933-id-1771613.html。

    黯然销魂掌
  • CSS预测:华为2022年将发布自主移动操作系统

    关于中国要发布自主移动操作系统的传闻已久,微软的反盗版的大规模锁死系统蓝屏事件,让我们暴露出了在核心技术上的短板,中兴被美禁芯事件,更击碎我们的家国情怀,而发布...

    人称T客

扫码关注云+社区

领取腾讯云代金券