zoukankan      html  css  js  c++  java
  • 高性能的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子句中运行的唯一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。由于只有一条语句,没有局部变量访问,作用域链临时改变就不会影响代码的性能。

  • 相关阅读:
    html5+css3中的background: -moz-linear-gradient 用法 (转载)
    CentOS 安装Apache服务
    Linux 笔记
    CURL 笔记
    Spring Application Context文件没有提示功能解决方法
    LeetCode 389. Find the Difference
    LeetCode 104. Maximum Depth of Binary Tree
    LeetCode 520. Detect Capital
    LeetCode 448. Find All Numbers Disappeared in an Array
    LeetCode 136. Single Number
  • 原文地址:https://www.cnblogs.com/WangJinYang/p/3519706.html
Copyright © 2011-2022 走看看