zoukankan      html  css  js  c++  java
  • 高性能javascript(第二章 数据访问)

    1、数据存储位置关系到访问速度。在 JavaScript 中有四种基本的数据访问位置:

    直接量:

    变量:

    数组:

    对象:

    2、多数情况下,对一个直接量和一个局部变量数据访问的性能差异是微不足道的。

    3、作用域和标示符解析:

      每一个 JavaScript 函数都被表示为对象。进一步说,它是一个函数实例。函数对象正如其他对象那样, 拥有你可以编程访问的属性,和一系列不能被程序访问,仅供 JavaScript 引擎使用的内部属性。

      其中一个内部属性是[[Scope]],由ECMA-262 标准第三版定义。

    4、内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可由函数访问。此函数作用域链中的每个对象被称为一个可变对象。

      当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问的数据。例如下面这个全局函数:
    function add(num1, num2) {
        var sum = num1 + num2;
        return sum;
    }

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

      图 2-2 指出它们之间的关系(注意: 此图中只画出全局变量中很少的一部分,其他部分还很多)。

      

      

      var total = add(5, 10);

      运行此 add 函数时建立一个内部对象,称作“运行期上下文”。一个运行期上下文定义了一个函数运行时的环境。对函数的每次运行而言,每个运行期上下文都是独一的,所以多次调用同一个函数就会导致多次创建运行期上下文。当函数执行完毕,运行期上下文就被销毁。

      一个运行期上下文有它自己的作用域链,用于标识符解析。当运行期上下文被创建时,它的作用域链被初始化,连同运行函数的[[Scope]]属性中所包含的对象。这些值按照它们出现在函数中的顺序,被复制到运行期上下文的作用域链中。

      这项工作一旦完成,一个被称作“激活对象”的新对象就为运行期上下文创建好了。此激活对象作为函数执行期的一个可变对象,包含访问所有局部变量,命名参数,参数集合,和 this 的接口。然后,此对象被推入作用域链的前端。

      当作用域链被销毁时,激活对象也一同销毁。图 2-3 显示 了前面实例代码所对应的运行期上下文和它的作用域链。

        

      在函数运行过程中,每遇到一个变量,标识符识别过程要决定从哪里获得或者存储数据。函数访问 sum,num1,num2 时都会产生这样的搜索过程。正是这种搜索过程影响了性能。


    5、用局部变量存储本地范围之外的变量值,如果它们在函数中的使用多于一次,看下面的两段代码:

      改进前:三个对 document 的引用,document 是一个全局对象。搜索此变量,必须遍历整个作用域链, 直到最后在全局变量对象中找到它

    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 的引用存入局部变量 doc 中。现在访问全局变量的次数是 1 次,而不 是 3 次。用 doc 替代 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";
    }
      改变作用域链:with   
    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";
        }
    }

       虽然这样可以防止多次书写document,但是导致另外一个问题:

        当代码流执行到一个 with 表达式时,运行期上下文的作用域链被临时改变了。一个新的可变对象将被创建,它包含指定对象的所有属性。

        此对象被插入到作用域链的前端,意味着现在函数的所有局部变量都被推入第二个作用域链对象中,所以访问代价更高了,如图所示:

           

      改变作用链域:try-catch

      当 try 块发生错误时,程序流程自动转入 catch 块,并将异常对象推入作用域链前端的一个可变对象中。在 catch 块中,函数的所有局部变量现在被放在第二个作用域链对象中。

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

      当然,如果你知道错误会发生,那么说明你应该修正的是代码本身

      将错误交给一个专用函数来处理由于只有一条语句,没有局部变量访问,作用域链临时改变就不会影响代码的性能。

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

    6、动态作用域:

      无论是 with 表达式还是 try-catch 表达式的 catch 子句,以及包含()的函数,都被认为是动态作用域。一 个动态作用域只因代码运行而存在,因此无法通过静态分析(察看代码结构)来确定(是否存在动态作用 域)。例如:

    function execute(code) {
        (code);
        function subroutine() {
            return window;
        }
    
        var w = subroutine(); //what value is w?
    };

      execute()函数看上去像一个动态作用域,因为它使用了()。w 变量的值与 code 有关。大多数情况下,w 将等价于全局的 window 对象,但是请考虑如下情况:

      execute("var window = {};") 

       这种情况下,()在 execute()函数中创建了一个局部 window 变量。所以 w 将等价于这个局部 window 变 量而不是全局的那个。所以说,不运行这段代码是没有办法了解具体情况的,标识符 window 的确切含义不能预先确定。

       所以,一般不推荐使用动态作用域:


    7、闭包,作用域,和内存的性能

    function assignEvents() {
        var id = "xdi9592";
        document.getElementById("save-btn").onclick = function (event) {
            saveDocument(id);
        };
    } 

    assignEvents()函数为一个 DOM 元素指定了一个事件处理句柄。此事件处理句柄是一个闭包,当 assignEvents()执行时创建,可以访问其范围内部的 id 变量。用这种方法封闭对 id 变量的访问,必须创建 一个特定的作用域链。

    运行期上下文的作用域和闭包图:

     

    由于闭包的[[Scope]]属性包含与运行期上下文作用域链相同的对象引用,会产生副作用。通常,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,激活对象就无法销毁了,

    因为引用仍然存在于闭包的[[Scope]]属性中。这意味着脚本中的闭包与非闭包函数相比,需要更多内存开销。在大型网页应用中, 这可能是个问题,尤其在 Internet Explorer 中更被关注。

    IE 使用非本地 JavaScript 对象实现 DOM 对象,闭包可能导致内存泄露(更多信息参见第 3 章)。

    当闭包被执行时,一个运行期上下文将被创建,它的作用域链与[[Scope]]中引用的两个相同的作用域链同时被初始化,然后一个新的激活对象为闭包自身被创建(参见下图)。

    注意闭包中使用的两个标识符,id 和 saveDocument,存在于作用域链第一个对象之后的位置上。这是闭包最主要的性能关注点:你经常访问一些范围之外的标识符,每次访问都导致一些性能损失。

    在脚本中最好是小心地使用闭包,内存和运行速度都值得被关注。但是,你可以通过本章早先讨论过的关于域外变量的处理建议,减轻对运行速度的影响:将常用的域外变量存入局部变量中,然后直接访问局部变量。


    8、对象成员:

      原型链与作用链域的区别:

      访问时间与属性深度的关系  

        location.href 总是快于 window.location.href 快于 window.location.href.toString()

       看下面两段代码的改进:
    代码一:
    function hasEitherClass(element, className1, className2) {
        return element.className == className1 || element.className == className2;
    }

     改进:

    function hasEitherClass(element, className1, className2) {
        var currentClassName = element.className;
        return currentClassName == className1 || currentClassName == className2;
    }

    代码:

    function toggle(element) {
        if (YAHOO.util.Dom.hasClass(element, "selected")) {
            YAHOO.util.Dom.removeClass(element, "selected");
            return false;
        } else {
            YAHOO.util.Dom.addClass(element, "selected");
            return true;
        }
    }

     改进:

    function toggle(element) {
        var Dom = YAHOO.util.Dom;
        if (Dom.hasClass(element, "selected")) {
            Dom.removeClass(element, "selected");
            return false;
        } else {
            Dom.addClass(element, "selected");
            return true;
        }
    }

    总结:

    在 JavaScript 中,数据存储位置可以对代码整体性能产生重要影响。有四种数据访问类型:直接量,变 量,数组项,对象成员。它们有不同的性能考虑。

    直接量和局部变量访问速度非常快,数组项和对象成员需要更长时间。

    局部变量比域外变量快,因为它位于作用域链的第一个对象中。变量在作用域链中的位置越深,访问所需 的时间就越长。全局变量总是最慢的,因为它们总是位于作用域链的最后一环。

    避免使用 with 表达式,因为它改变了运行期上下文的作用域链。而且应当小心对待 try-catch 表达式的 catch 子句,因为它具有同样效果。

    嵌套对象成员会造成重大性能影响,尽量少用。

    一个属性或方法在原形链中的位置越深,访问它的速度就越慢。

    一般来说,你可以通过这种方法提高 JavaScript 代码的性能:将经常使用的对象成员,数组项,和域外变 量存入局部变量中。然后,访问局部变量的速度会快于那些原始变量。

  • 相关阅读:
    稳扎稳打Silverlight(47) 4.0UI之操作剪切板, 隐式样式, CompositeTransform, 拖放外部文件到程序中
    返璞归真 asp.net mvc (9) asp.net mvc 3.0 新特性之 View(Razor)
    返璞归真 asp.net mvc (6) asp.net mvc 2.0 新特性
    稳扎稳打Silverlight(48) 4.0其它之打印, 动态绑定, 增强的导航系统, 杂七杂八
    精进不休 .NET 4.0 (9) ADO.NET Entity Framework 4.1 之 Code First
    稳扎稳打Silverlight(42) 4.0控件之Viewbox, RichTextBox
    稳扎稳打Silverlight(53) 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
    稳扎稳打 Silverlight 4.0 系列文章索引
    稳扎稳打Silverlight(54) 4.0通信之对UDP协议的支持: 通过 UdpAnySourceMulticastClient 实现 ASM(Any Source Multicast),即“任意源多播”
    返璞归真 asp.net mvc (8) asp.net mvc 3.0 新特性之 Model
  • 原文地址:https://www.cnblogs.com/liguwe/p/3962465.html
Copyright © 2011-2022 走看看