zoukankan      html  css  js  c++  java
  • 高性能的JavaScript--数据访问(2)

    动态作用域

    无论是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的确切含义不能预先确定。

    闭包,作用域,和内存

     闭包是JavaScript最强大的一个方面,它允许函数访问局部范围之外的数据。闭包的使用在当今最复杂的网页应用中无处不在,不过,有一种性能影响与闭包有关。

    为了解闭包的有关性能问题,考虑下面的例子:

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

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

    当assignEvents()被执行时,一个激活对象被创建,并包含了一些应有的内容,其中包括id变量。它将成为运行期上下文作用域链上的第一个对象,全局对象是第二个。当闭包创建时,[[Scope]]属性与这些对象一起被初始化。

     

    由于闭包的[[Scope]]属性包含与运行期上下文作用域链相同的对象引用,会产生副作用。通常,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,激活对象就无法销毁了,因为引用任然存在于闭包的[[Scope]]属性中,这意味着脚本中的闭包与非闭包函数相比,需要更多的内存开销。在大型网页应用中,这可能是个问题,尤其在IE中更被关注。IE使用非本地JavaScript对象实现DOM对象,闭包可能导致内存泄露。

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

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

    在脚本中最好是小心地使用闭包,内存和运行速度都值得被关注。将常用的域外变量存入局部变量中,然后直接访问局部变量。

    对象成员

     大多数JavaScript代码以面向对象的形式编写。无论通过创建自定义对象还是使用内置对象,诸如文档对象模型(DOM)和浏览器对象模型(BOM)之中的对象。因此,存在很多对象成员访问。

    对象成员包括属性和方法,在JavaScript中,二者差别甚微。对象的一个命名成员可以包括任何数据类型。既然函数也是一种对象,那么对象成员除了传统的数据类型外,也可以包含一个函数。当一个成员用了一个函数时,它被称作一个“方法”,而一个非函数类型的数据则被称作“属性”。

    原形

     对象成员比直接量或局部变量访问速度慢,在某些浏览器上比访问数组项还要慢。这和JavaScript中对象的性质有关。

    JavaScript中的对象是基于原形的,原形是其他对象的基础,定义并实现了一个新对象所必须具有的成员。这一概念完全不同于传统面向对象编程中“类”的概念,它定义了创建新对象的进程。原形对象为给定类型的对象实例所共享,因此所有实例共享原型对象的成员。

    一个对象通过一个内部属性绑定到它的原形。Firefox ,Safari和Chrome向开发人员开放这一属性,称作__proto__,其他浏览器貌似不允许脚本访问这一属性。任何时候你创建一个内置类型的实例,如object或者Arrary,这些实例自动拥有一个Object作为他们的原形。

    因此,对象可以有两种类型的成员:实例成员(“own”成员)和原形成员。实例成员直接存在于实例自身,而原形成员则从对象原形继承。如:

    var book = {
    title: "High Performance JavaScript",
    publisher: "Yahoo! Press"
    };
    alert(book.toString()); //"[object Object]"

    如 book对象有两个实例成员:title 和publisher,注意它并没有定义tostring()接口,但是这个接口却被调用了,也没用抛出错误。toString()函数就是一个book对象继承的原形成员。

    原形链

    对象的原形决定了一个实例的类型。默认情况下,所有对象都是Object 的实例,并继承了所有基本方法。如toString()。也可以用“构造器”创建另外一种类型的原形。

    function Book(title, publisher){
    this.title = title;
    this.publisher = publisher;
    }
    Book.prototype.sayTitle = function(){
    alert(this.title);
    };
    var book1 = new Book("High Performance JavaScript", "Yahoo! Press");
    var book2 = new Book("JavaScript: The Good Parts", "Yahoo! Press");
    alert(book1 instanceof Book);  //true
    alert(book1 instanceof Object);  //true
    book1.sayTitle();  //"High Performance JavaScript"
    alert(book1. toString()); //"[object Object]"

    Book构造器用于创建一个新的Book实例。book1的原形(__proto__)是Book.prototype,Book.prototype的原形是Object。这就创建了一个原形链,book1和book2继承了他们的成员。
    主要的是,两个Book实例共享同一个原形链。每个实例拥有自己的title和publisher属性,但是其他成员均继承自原形。当book1.toString()被调用时,搜索工作必须深入原形链才能找到对象成员"toString",深入原形链越深,搜索速度就越慢。每深入原形链一层都会增加性能损失。搜索实例成员的过程比访问直接量或者局部量负担更重,所以增加遍历原形链的开销正好放大了这种效果。

    嵌套成员

    由于对象成员可能包含其他成员,例如不太常见的写法window.location.href这种模式。每遇到一个点号,JavaScript引擎就要在对象成员上执行一次解析过程。成员嵌套越深,访问速度越慢。location.href总是快于window.location.href,而后者也要比window.location.href.toString()更快。如果这些属性不是对象的实例属性,那么成员解析还要在每个点上索搜原形链,这将需要更长时间。

    缓存对象成员的值

    由于所有这些性能问题与对象成员有关,所以如果可能的话就避免使用他们。更确切的说,只有在必要情况下使用对象成员。例如没有理由在一个函数中多次读取同一个对象成员的值:

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

    element.className被访问了两次,我们可以存入一个局部变量,消除一次搜索过程:

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

    一般来说,如果在同一函数中你要多次读取同一个对象属性,最好将它存入到一个局部变量。以局部变量替代属性,避免多余的属性查找带来的性能开销。在处理嵌套对象成员时这点特别重要,他们会对运行速度产生难以置信的影响。

    总结

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

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

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

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

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

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

  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/WangJinYang/p/3530316.html
Copyright © 2011-2022 走看看