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

代码为什么要这样写?

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

 而不这样?

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

  很明显 我们都知道第一个性能要比第二个好,为什么呢?

首先要了解 js引擎的 Data Access  数据访问。这里只说 非优化的js引擎。

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

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

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

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

从上面的例子我们也能猜到 访问 局部变量比访问对象成员和数组项更快。

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

  大多数 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);

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

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

运行期上下文也是有scope chain的。这个作用域链被用于 标识符解析 。

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

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

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

回到最开始的话题你会发现 你仅仅是 把 document 缓存到一个 局部变量 doc 里就可以减少 一次或者更多次非常深的 标识符扫描。

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

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

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

时间: 2024-10-03 06:54:51

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

读高性能JavaScript编程 第一章

草草的看完第一章,虽然看的是译文也是感觉涨姿势了, 我来总结一下: 由于 大多数浏览器都是 single process 处理 ui updatas and js execute 于是产生问题: js (script tag) will blocking page processing 上图说话: 于是大家动脑筋想办法 想到了三个办法 1. Parallel downloads, then execute  并行下载,然后执行. 2.Download times less,Less downlo

读高性能JavaScript编程 第四章 Conditionals

if else 和 switch    &&    递归 if else 和 switch 一般来说,if-else 适用于判断两个离散的值或者判断几个不同的值域.如果判断多于两个离散值,switch表达式将是更理想的选择. 如同 我们在写sql 总习惯把可以过滤掉更多的where 放在前面一样,当 if else 判断的离散值较多时也因该这么干. 二分搜索法: if (value == 0){ //要优化的代码 return result0; } else if (value == 1)

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

Script Positioning 脚本位置 The HTML 4 specification indicates that a <script> tag may be placed inside of a <head> or <body> tag in an HTML document and may appear any number of times within each. Traditionally, script> tags that are use

高性能javascript(第二章 数据访问)

1.数据存储位置关系到访问速度.在 JavaScript 中有四种基本的数据访问位置: 直接量: 变量: 数组: 对象: 2.多数情况下,对一个直接量和一个局部变量数据访问的性能差异是微不足道的. 3.作用域和标示符解析: 每一个 JavaScript 函数都被表示为对象.进一步说,它是一个函数实例.函数对象正如其他对象那样, 拥有你可以编程访问的属性,和一系列不能被程序访问,仅供 JavaScript 引擎使用的内部属性. 其中一个内部属性是[[Scope]],由ECMA-262 标准第三版定

[原创译书] JS函数式编程 第二章总结

?? Functional Programming in Javascript 主目录第二章 函数式编程基础上一节 函数式程序员的工具集 第二章总结 为了理解函数式编程,这章覆盖了很大范围的主题.首先我们分析了一个编程语言的函数式是什么意思, 并且评估了Javascript函数式编程能力.接下来,我们用Javascript实现了一些函数式编程的核心概念, 并展示了一些Javascript内建的函数式编程函数. 尽管Javascript有一些函数式编程的工具,它函数式编程核心的大部分仍被隐藏着,并

C++ 编程第二章小结

switch()用法的注意事项 1:switch语句中的表达式只能是整形数据,字符型数据和枚举型数据,case后面的产量表达式的类型必须与switch括号后面的类型相匹配 2:各个case(包括default)的出现次序可以任意,每个case在带有break的前提下,case的次序不影响执行结果 循环设计 循环设计的几种分类方法这里介绍几种 1:字符图形类 2:素数判断类 3:逻辑判断类 4:级数逼近类 a:对于图形类的基本循环格式一般是 for(int i = 1; i < 10 ;i ++)

读大道至简第二章有感

大道至简第二章的题目是"是懒人造就了方法"而开头也写到李冰的开山并与愚公移山做了比较,无非就是想用具体的例子来证明懒人造就方法的观点.而其深层的含义便是要学会观察,学会思考,同样是一件事,很多人都能做,有人用的时间长资源多,而有的人则截然相反,这就说明了观察思考的重要性,而相对于编程来说,一个简单的比较大小的问题,有的人比较五次,而有的人只比较四次就能运行出结果,这就是程序的优化,也是思考的结果 一百万行代码是可以写在一个文件里的.这反映了一个很常见的问题,很多初学者比如我们总是在关注

高性能JavaScript 编程实践

前言 最近在翻<高性能JavaScript>这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效.但是有些章节的有些内容还是相当不错的,譬如第八章编程实践,为了方便以后的查阅,对此做个总结.失效的代码也会在以后做更进一步的探索. 避免双重求值 这个优化策略很好理解,我们可能都已经不知不觉地运用在了实际的编程中: // not use this setTimeout('alert("hello world&qu

读构建之法 第二章:个人技术和流程

绝大部分软件都是由多人合作完成的,大家的工作相互有依赖关系.某人负责的模块的功能被其他人调用,但如何让自己负责的模块功能定义尽量明确,模块内部的改变不会影响其他模块,而且模块的质量能得到稳定的.量化的保证???单元测试就是一个很有效的解决方案. 创建单元测试函数的主要步骤是: 1.设置数据(一个假象的正确的E-mail地址) 2.使用被测试类型的功能(用E-mail地址来创建一个User类的实体) 3.比较实际结果和预期的结果(Assert.IsTrue(target!=null) 创建完就可以