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.一个属性或方法在原形链中的位置越深,访问速度就越慢。

  • 相关阅读:
    也谈谈我面试的经历
    synchronized和lock比对
    数据结构之查找
    iptable和tcpdump的先后顺序
    iptable的四表五链
    iptable规则的执行顺序
    curl指令的坑
    k8s 网络模型解析之实践
    如何创建一个img文件并且mount 它
    k8s 网络模型解析之原理
  • 原文地址:https://www.cnblogs.com/WangJinYang/p/3530316.html
Copyright © 2011-2022 走看看