代码为什么要这样写?

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";
}

 而不这样?

//avoid

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";
}

  很明显 我们都知道第一个提取了局部变量doc保存document对象所以性能要比第二个好,为什么呢?

首先要了解 js引擎的四种Data Access (数据访问)。

1、访问 Literal values        直接量       eg:字符串,数字,布尔值,对象,数组,函数,正则表达式,具有特殊意义的空值,以及未定义。

2、访问 Variables              变量         var创建用于存储数据值。

3、访问 Array items          数组项       具有数字索引的数组对象。

4、访问 Object members   对象成员    具有字符索引的js对象。

从上面的例子我们也能猜到 访问 局部变量(doc)比访问对象成员和数组项更快。(这里暂时不考虑 dom scripting ,因为第三章会讲。)

那么为什么访问对象成员 和数组项要慢呢? 书中说的很详细,我只介绍一下自己的理解和摘录。

  大多数 JavaScript 代码以面向对象的形式编写。无论通过创建自定义对象还是使用内置的对象,诸如

文档对象模型(DOM)和浏览器对象模型(BOM)之中的对象。 

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

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

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

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

这些很难理解,但是看了后很透彻。紧接着最后一句话,你调用实例成员肯定比调用原型成员要快,比如

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

 book 本身是没有toString 的 但是程序编译并不报错,因为toString是它的原型成员。

 假设book.title === book.toString()(上面已经讲了属性和方法的区分其实并不严格)  book.toString() 依旧比 book.title 更慢。

book创建后 其 原型绑定在 _proto_ 内部属性上 , 观察这个原型可以看到 toString() 这个标识符 book 本身并没有toString这个function

而是从它的原型上继承得来。有一个前提问题:在调用 book.title 的时候,js引擎如何知道 title是否是未定义的呢?

在函数执行过程中,每遇到一个变量,标识符识别/解析这些变量的方法是按顺序搜索运行期上下文作用域链查找同名标识符,如果找不到就是未定义,

所以通常返还未定义注定是检索了整个作用域链。正是这种类似的深度搜索影响了性能。

如果在作用域链里 toString 比 title更靠后那么就 可以确定title 会比toString更先找到。

什么是运行期上下文,什么是作用域链?

  每一个 JavaScript 函数都被表示为对象。进一步说,它是一个函数实例。函数对象正如其他对象那样,
拥有你可以编程访问的属性,和一系列不能被程序访问,仅供 JavaScript 引擎使用的内部属性。其中一个
内部属性是[[Scope]],由ECMA-262 标准第三版定义。

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

的形式存在当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问
的数据。例如下面这个全局函数:

function add(num1, num2){
  var sum = num1 + num2;
  return sum;
}

图中 只给出了 全局对象 在作用域链的位置,并没有不包括所有的。

注意:scope chain (作用域链) 里的 这些可变对象都是以键值对的形式存在的。

图中的 activation object 被译为 激活对象 此对象是在 此add函数被调用时创建的  例如:运行此代码  var s = add(1,2);

运行此段代码时会创建一个内部对象(之前已经提到,上图左侧篮框就是)称作   execution context 就是上面讲的运行期上下文。 它定义了一个函数运行是的环境。

而值得一提的是 对函数的每次运行而言,每个运行期上下文都是独一的,所以多次调用同一个函数就会导致多次创建运行期上下文。当函数执行完毕,运行期上下文就被销毁.

运行期上下文也是有scope chain的。这个作用域链被用于 标识符解析 。 换句话说,标识符解析就是在扫描运行期上下文的作用域链,运行期上下文的作用域链是如何构成的呢?

当 运行期上下文被创建的时候 它的作用域链被初始化 连同 函数的[[scope]]属性 中所包含的对象 会按照顺序被复制到 运行期上下文的作用域链里。最后你看到的就是

激活对象了。 如图它被推向了作用域链的前端。 而标识符识别是从前到后的。

简单讲最终结果是 toString 标识符所在的位置 是作用域的后端 而 title 标识符实在作用域链的更前端,所以toString会更慢。

回到最开始的话题你会发现 你仅仅是 把 document 缓存到一个 局部变量 doc 里就可以减少 一次或者更多次非常深的 标识符扫描。 因为在function 执行时产生的运行期上下文局部变量总是更靠前的。

同时可以意识到 成员嵌套越深扫描作用域链越深。成员嵌套越深,访问速度越慢。location.href总是快于 window.location.href

这有点类似于 尾递归的作用了。

书中详细说了 标识符性能、 动态作用域、闭包可能导致内存泄漏、改变作用域链。以及有关原型、原型链。总之受益匪浅。

读高性能JavaScript编程 第二章 让我知道了代码为什么要这样写的更多相关文章

  1. 读高性能JavaScript编程 第一章

    草草的看完第一章,虽然看的是译文也是感觉涨姿势了, 我来总结一下: 由于 大多数浏览器都是 single process 处理 ui updatas and js execute 于是产生问题: js ...

  2. 读高性能JavaScript编程 第三章

    第三章  DOM Scripting  最小化 DOM 访问,在 JavaScript 端做尽可能多的事情. 在反复访问的地方使用局部变量存放 DOM 引用. 小心地处理 HTML 集合,因为他们表现 ...

  3. 读高性能JavaScript编程 第四章 Conditionals

    if else 和 switch    &&    递归 if else 和 switch 一般来说,if-else 适用于判断两个离散的值或者判断几个不同的值域.如果判断多于两个离散 ...

  4. 读高性能JavaScript编程 第四章 Duff's Device

    又要开始罗里吧嗦的 第四章  Summary 了. 这一次我尽量精简语言. 如果你认为 重复调用一个方法数次有点辣眼睛的话 比如: function test(i){ process(i++); pr ...

  5. 读高性能JavaScript编程学英语 第一章第三页第一段话

    When the browser encounters a <script> tag, as in this HTML page, there is no way of knowing w ...

  6. 高性能JavaScript 编程实践

    前言 最近在翻<高性能JavaScript>这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效.但是有些章节的 ...

  7. [转]Windows Shell 编程 第二章 【来源:http://blog.csdn.net/wangqiulin123456/article/details/7987893】

    第二章Shell的结构  “Shell 编程”的大伞之下有大量的API函数和COM接口.这个种类繁多的‘命令’集允许你用不同的方法对Windows Shell进行编程.函数和接口并不是两种提供相同功能 ...

  8. java面向对象编程——第二章 java基础语法

    第二章 java基础语法 1. java关键字 abstract boolean break byte case catch char class const continue default do ...

  9. 使用MYSQL数据库实现编程----第二章第三章课堂知识小总结

    第二章1:创建数据库create database myschool 2.数据类型  1.整型 int  2.小数 double  精确度要求高的 ----Decimal(18,4)  2222222 ...

随机推荐

  1. offsetHeight,scrollHeight,clientHeight,scrollTop以及pageX,clientX,offsetX,screenX,offsetLeft,style.left等的区别以及使用详解

    一.写在前面 在阅读本文前,希望大家能针对每个属性亲手测试,网上现有的大量相关博客都有不等的概念错误,毕竟亲手实践才能更好的掌握这些概念. 1.pageX,clientX,screenX与offset ...

  2. 数组filter()参数详解,巧用filter()数组去重

    数组方法挺多,但是用来用去可能也就foreach,splice以及slice接触较多,filter()说实话之前也没过多了解.其实filter()为数组提供过滤功能,它会遍历数组所有元素,并返回满足条 ...

  3. MVC实现加载更多

    MVC中实现加载更多 作者 欢醉 关注 2016.01.25 08:48 字数 945 阅读 136评论 0喜欢 2 需要实现的功能: 数据太多想初次加载部分数据,在底部加上“加载更多”按钮 点击后加 ...

  4. RadioButtonList根据值触发OnSelectedIndexChanged事件

    Insus.NET有使用Iframe来处理另外一个站点的enter form,由于需要自动循环填入数据,免去人手操作.但是原来的Enter from有RadioButtonList控件以及OnSele ...

  5. WPF备忘录(3)如何从 Datagrid 中获得单元格的内容与 使用值转换器进行绑定数据的转换IValueConverter

    一.如何从 Datagrid 中获得单元格的内容 DataGrid 属于一种 ItemsControl, 因此,它有 Items 属性并且用ItemContainer 封装它的 items. 但是,W ...

  6. 素数回文(hdu1431)

    素数回文 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  7. 卡片游戏(hdu4550)贪心

    卡片游戏 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submi ...

  8. Java虚拟机 - 类加载机制

    [深入Java虚拟机]之四:类加载机制 类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下 ...

  9. Mybatis中的缓存

    Mybatis提供缓存查询功能,用于减轻数据库压力,提升数据查询能力. Mybatis中定义了两级缓存:包括一级缓存与二级缓存.示意图如下所示: 一.一级缓存 一级缓存的特点: 每一个SqlSessi ...

  10. HTML自定义标签与标签自定义属性

    大部分浏览器支持自定义HTML标签和为标准标签自定义属性,而且很多浏览器对这两种自定义行为的支持都很直接了当. 自定义HTML标签 在firefox.chrome这种现代浏览器里,自定义标签很简单,就 ...