高性能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 代码的性能:将经常使用的对象成员,数组项,和域外变 量存入局部变量中。然后,访问局部变量的速度会快于那些原始变量。

时间: 2024-07-30 11:22:30

高性能javascript(第二章 数据访问)的相关文章

JavaScript 数据访问(通译自High Performance Javascript 第二章) [转]

JavaScript 数据访问(通译自High Performance Javascript 第二章) 提问者:lilei335260(ID:160310) | 悬赏 0.0 希赛币 | 回答数:12 | 关注度:32 | 提问时间:2014-05-03 JavaScript 数据访问(翻译自High Performance Javascript 第二章) 计算机科学中一个经典的问题是决定如何存储数据,以便进行快速的读取和写入操作. 在代码执行期间,数据如何存储将会关系到它们的检索速度.在Jav

第二章 数据,变量和计算

头文件 #include <>或#include "" <> 强制类型转换 double a=1.6; cout<<static_cast<int>(a)<<endl; 输出为1 注意旧的强制类型转换为int(a),这比新的更容易出错,得不到想要的结果,所以推荐static_cast<转换成的类型>() 自动判断数据类型 auto n=16; 自动判断n的类型为int const auto e=1.1; 自动判断静

高性能Javascript--高效的数据访问

接上一篇,希望能写一个高性能Javascript专题. 第一篇:高性能Javascript--脚本的无阻塞加载策略. 经典计算机科学的一个问题是,数据应当存放在什么地方,以实现最佳的读写效率.数据存储是否得当,关系到代码运行期间数据被检索到的速度.在Javascript中,此问题相对简单,因为数据表现方式只有少量方式可供选择.在Javascript中,有四种基本的数据访问位置: Literal values 直接量 直接量仅仅代表自己,而不存储于特定的位置. Javascript的直接量包括:字

第二章 数据是用二进制表示的

看完第二章后,我知道了计算机是以二进制来存储数据的(其实标题已经告诉我们了).以至于计算机为什么用二进制来表示的,是因为计算机内部是由集成电路这种电子部件构成的,它们的所有引脚的每个引脚只有两种状态,就是0V和5V, 二进制数是只有0和1,它们之间的特性很相似. 还有字节(Byte)与位(bit)两者的关系,字节是计算机处理信息的基本单位,而位是最小单位(1字节=8位).还有就是用二进制数表示的数值,计算机不会区分它是数值.文字还是图片等,而是根据编写的各位对计算机发出的指示来进行信息的处理的.

【知识强化】第二章 数据的表示和运算 2.2 定点数的表示与运算

我们进入第二章的学习,就是定点数的表示和运算. 那关于定点数呢我们分为两块内容,一块呢就是定点数的表示也就是我们这一节的内容,一块呢就是定点数的运算.这两块内容呢都非常的难,也都非常的重点.希望大家呢引起重视. 原文地址:https://www.cnblogs.com/ZHONGZHENHUA/p/11370240.html

第二十一章 数据访问(In .net4.5) 之 序列化

1. 概述 应用程序间传递数据,需要先将数据对象转化为字符流或字节流的形式,然后接收端收到后再转化回原始的数据对象.这就是序列化与反序列化. 本章介绍 .net中的序列化与反序列化.序列化器的种类 以及 为序列化配置对象. 2. 主要内容 2.1 序列化与反序列化 序列化只能保存对象的数据部分,不能保存方法部分.可以创建custom data transfer object(DTO)来只保存指定的数据信息. .net平台提供三种类型的序列化: ① XmlSerializer: [Serializ

KnockoutJS 3.X API 第二章 数据监控(1)视图模型与监控

数据监控 KO的三个内置核心功能: 监控(Observable)和依赖性跟踪(dependency tracking) 声明绑定(Declarative bindings) 模板(Templating) 在这个页面上,您将了解三种核心功能的第一种.但在此之前,让我们来看看MVVM模式的概念和视图模型的概念. MVVM模式和视图模型 模型-视图-视图模型(MVVM)是用于构建用户界面的设计模式.它描述了如何将复杂的UI分割成三个部分: 模型:应用程序所存储的数据.这个数据代表了你的业务领域对象和操

《数据挖掘:R语言实战》第二章 数据概览

2.1 n*m数据集 在n*m表格形式的数据集中,n代表数据的行,即观测点的数量:m代表列,即变量的数量:n*m为数据的维度. 一般来说,当拿到一份数据时,最先做的往往就是查看数据集的观测样本数.变量数,以及这些变量的实际含义,以此对数据集的庞大程度和各变量的相对重要性做到心中有数.这对选取何种数据挖掘算法,以及在这之前应该抽取多少及哪些变量及样本纳入建模都有重要的先导作用. 2.2 数据的分类 2.2.1 一般的数据分类 定量数据:连续型数据和离散型数据 定性数据:定类数据.定序数据.定距数据

第二十章 数据访问(In .net4.5) 之 使用LINQ

1. 概述 .net3.5中新添加给C#的LINQ查询,提供了直观便捷的数据查询方式.并且支持多种数据源的查询. 本章介绍标准的LINQ操作,如何用最优的方式使用LINQ 以及 LINQ to XML. 2. 主要内容 2.1 使LINQ可行的语言特性 ① 隐式类型(Implicitly typed variables): 使用var标记,由编译器推断类型.也是强命名的. ② 对象初始化语法(Object initialization syntax):用一种简洁的语法进行对象初始化. var p