《JavaScript高级程序设计》读书笔记

Javascript由以下三部分组成:

  1. 核心(ECMAScript)
  2. 文档对象模型(DOM)
  3. 浏览器对象模型(BOM)

ECMAScript组成部分:

语法、类型、语句、关键字、保留子、操作符、对象。

按照惯例,外部 JavaScript 文件带有.js 扩展名。但这个扩展名不是必需的,因为 浏览器不会检查包含 JavaScript 的文件的扩展名。这样一来,使用 JSP、PHP 或其他 服务器端语言动态生成 JavaScript 代码也就成为了可能。但是,服务器通常还是需要 看扩展名决定为响应应用哪种 MIME 类型。如果不使用.js 扩展名,请确保服务器能 返回正确的 MIME 类型。

无论如何包含代码,只要不存在 defer 和 async 属性,浏览器都会按照<script>元素在页面中出现的先后顺序对它们依次进行解析。

ECMAScript 5 引入了严格模式(strict mode)的概念:“use strict”;严格模式下,JavaScript 的执行结果会有很大不同。

控制语句中使用代码块({…})——即使代码块中只有一条语句。

ECMAScript 中有 5 种简单数据类型(也称为基本数据类型):
Undefined、Null、Boolean、Number 和 String。
还有 1 种复杂数据类型——Object,
Object 本质上是由一组无序的名值对组成的。

字面值 undefined 的主要目的是用于比较。

对于尚未声明过的变量,只能执行一项操作,即使用 typeof 操作符检测其数据类型。

即便未初始化的变量会自动被赋予 undefined 值,但显式地初始化变量依然是明智的选择。

从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回”object”的原因。

如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值。这样 一来,只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用。

实际上,undefined 值是派生自 null 值的:

相等操作符(==)出于比较的目的会转换其操作数

只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null 值。这样做不仅可以 体现 null 作为空对象指针的惯例,而且也有助于进一步区分 null 和 undefined。

八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误。

在默认情况下,ECMASctipt 会将那些小数点后面带有 6 个零以上的浮点数值转换为以 e 表示法 表示的数值(例如,0.0000003 会被转换成 3e7)。

浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2 的结果不是 0.3,而是 0.30000000000000004。因此,永远不要测试某个特定的浮点数值
关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于 IEEE754 数值的浮点计算的通病,ECMAScript 并非独此一家;其他使用相同数值格 式的语言也存在这个问题。

使用 isFinite()函数判断一个数值是不是有穷的(是不是位于最小[-Infinity]和最大[Infinity]的数值之间)。

访问 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以 得到负和正 Infinity 的值。
可以想见,这两个属性中分别保存着-Infinity 和 Infinity。

NaN 本身有两个非同寻常的特点。
首先,任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN,这 个特点在多步计算中有可能导致问题。
其次,NaN 与任何值都不相等,包括 NaN 本身。例如,下面的代 码会返回 false:
alert(NaN == NaN); //false
针对 NaN 的这两个特点,ECMAScript 定义了 isNaN()函数。

只有 0 除以 0 才会返回 NaN,正数除以 0 返回 Infinity,负数除以 0 返回-Infinity。

尽管有点儿不可思议,但 isNaN()确实也适用于对象。在基于对象调用 isNaN() 函数时,会首先调用对象的 valueOf()方法,然后确定该方法返回的值是否可以转 换为数值。如果不能,则基于这个返回值再调用 toString()方法,再测试返回值。 而这个过程也是 ECMAScript 中内置函数和操作符的一般执行流程。

有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()。
一元加操作符的操作与 Number()函数相同。

由于 parseFloat()只解析十进制值,因此它没有用第二个参数指定基 数的用法。最后还要注意一点:如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后 都是零),parseFloat()会返回整数。

变量 lang 开始时包含字符串”Java”。而第二行代码把 lang 的值重新定义为”Java” 与”Script”的组合,即”JavaScript”。实现这个操作的过程如下:首先创建一个能容纳 10 个字符的 新字符串,然后在这个字符串中填充”Java”和”Script”,最后一步是销毁原来的字符串”Java”和字符串”Script”,因为这两个字符串已经没用了。这个过程是在后台发生的,而这也是在某些旧版本的浏览器(例如版本低于 1.0 的 Firefox、IE6 等)中拼接字符串时速度很慢的原因所在。但这些浏览器后 来的版本已经解决了这个低效率问题。

要把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的 toString()方法。数值、布尔值、对象和字符串值(没错,每个字符串也都有一个 toString()方法,该方法返回字符串的一个副本)都有 toString()方法。
但 null 和 undefined 值没有这个方法,因为它们没有对应的包装器类,从而没有属性和方法。

使用转型函数 String(),这个 函数能够将任何类型的值转换为字符串。String()函数遵循下列转换规则:

  1. 如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果;
  2. 如果值是 null,则返回”null”;
  3. 如果值是 undefined,则返回”undefined”。

ECMAScript 中的对象其实就是一组数据和功能的集合
对象可以通过执行 new 操作符后跟要创建 的对象类型的名称来创建。

在 ECMAScript 中,如果不给构造函数传递参数,则可 以省略后面的那一对圆括号。

Object 的每个实例都具有下列属性和方法:

  1. constructor:保存着用于创建当前对象的函数。
  2. hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定。
  3. isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型。
  4. propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句。
  5. toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
  6. toString():返回对象的字符串表示。
  7. valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。

由于在 ECMAScript 中 Object 是所有对象的基础,因此所有对象都具有这些基本的属性和方法。

在对非数值应用一元加操作符时,该操作符会像 Number()转型函数一样对这个值执行转换。
一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数。

ECMAScript 中的所有数 值都以 IEEE-754 64 位格式存储,但位操作符并不直接操作 64 位的值。而是先将 64 位的值转换成 32 位 的整数,然后执行操作,最后再将结果转换回 64 位。

计算一个数值的二进制补码,需要经过下列 3 个步骤:

  1. 求这个数值绝对值的二进制码(例如,要求-18 的二进制补码,先求 18 的二进制码);
  2. 求二进制反码,即将 0 替换为 1,将 1 替换为 0;
  3. 得到的二进制反码加 1。

如果对非数值应用位操作符,会先使用 Number()函数将该值转换为一个数值(自动完成),然后 再应用位操作。得到的结果将是一个数值。

按位非操作的本质:操作数的负值减 1。
例如:~25 == -25-1。
由于按位非是在数值表示的最底层执行操作,因此速度更快。

注意,左移不会影响操作数的符号位。

有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)。
无符号右移操作符由 3 个大于号(>>>)表示,这个操作符会将数值的所有 32 位都向右移动。对正 数来说,无符号右移的结果与有符号右移相同。但是对负数来说,情况就不一样了。首先,无符号右移是以 0 来填充空位,而不是像有符号右移那 样以符号位的值来填充空位。

逻辑非操作符也可以用于将一个值转换为与其对应的布尔值。
使用两个逻辑非操作符,实际 上就会模拟 Boolean()转型函数的行为。
其中,第一个逻辑非操作会基于无论什么操作数返回一个布尔值,而第二个逻辑非操作则对该布尔值求反,于是就得到了这个值真正对应的布尔值。

不能在逻辑与操作中使用未定义的值。

利用逻辑或的短路特性来避免为变量赋 null 或 undefined 值。
例如:var myObject = preferredObject || backupObject;

两组操作符:相等和不相等——先转换再比较,全等和不全等——仅比较而不转换。

由于相等和不相等操作符存在类型转换问题,而为了保持代码中数据类型的完整性,我们推荐使用全等和不全等操作符。

for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性(非原生属性)。

ECMAScript 对象的属性没有顺序。因此,通过 for-in 循环输出的属性名的顺序是不可预测的。 具体来讲,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。在使用 for-in 循环之前,先检测确认该对象的值不是 null 或 undefined。

在 with 语句的代码块 内部,每个变量首先被认为是一个局部变量,而如果在局部环境中找不到该变量的定义,就会查询 location 对象中是否有同名的属性。如果发现了同名属性,则以 location 对象属性的值作为变量的值。由于大量使用 with 语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。

在 switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有 问题。其次,每个 case 的值不一定是常量,可以是变量,甚至是表达式

switch 语句在比较值时使用的是全等操作符,因此不会发生类型转换(例如,字符串”10″不等于数值 10)。

arguments 的值永远与对应命名参数的值保持同步。

每次执行这个 doAdd()函数都会重写第二个参数,将第二个参数的值修改为 10。因为 arguments 对象中的值会自动反映到对应的命名参数,所以修改 arguments[1],也就修改了 num2,结果它们的 值都会变成 10。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。另外还要记住,如果只传入了一个参数,那么为 arguments[1]设置的值不会反应到命名参数中。这是因为 arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名 参数的个数决定的。

严格模式对如何使用 arguments 对象做出了一些限制。首先,像前面例子中那样的赋值会变得无效。也就是说,即使把 arguments[1]设置为 10,num2 的值仍然还是 undefined。其次,重写 arguments 的值会导致语法错误(代码将不会执行)。

ECMAScript 变量可能包含两种不同数据类型的值: 基本类型值引用类型值
基本类型值指的是 简单的数据段,而引用类型值指那些可能由多个值构成的对象。

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制 到为新变量分配的位置上。
当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到 为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一 个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另 一个变量。

ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参 数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而 引用类型值的传递,则如同引用类型变量的复制一样。有不少开发人员在这一点上可能会感到困惑,因 为访问变量有按值和按引用两种方式,而参数只能按值传递。在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用 ECMAScript 的概念来说,就是 arguments 对象中的一个元素)。在向参数传递引用类型的值时,会把 这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。


如果 person 是按引用传递的,那么 person 就会自动被修改为指向其 name 属性值 为”Greg”的新对象。但是,当接下来再访问 person.name 时,显示的值仍然是”Nicholas”。这说明 即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写 obj 时,这 个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

可以把 ECMAScript 函数的参数想象成局部变量。

确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

JavaScript 没有块级作用域

垃圾收集机制的原理其实很简单:找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。

两个策略:

  1. JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。主流浏览器都是标记清除式的 垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。
  2. 另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。例如Objective-C。

 

将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。

分配给 Web 浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的主要是出于安全方面的考虑, 目的是防止运行 JavaScript 的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量 分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)。不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几 点总结:

  1. 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  2. 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
  3. 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
  4. 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  5. 变量的执行环境有助于确定应该何时释放内存。

 

对象字面量也是向函数传递大量可选参数的首选方式。

JavaScript访问对象属性:

  1. 点表示法;
  2. 方括号法。主要优点是可以通过变量来访问属性,例如:var propertyName = “name”;alert(person[propertyName]); //”Nicholas”如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括 号表示法。通常,除非必须使用变量来访问属性,否则我们建议使用点表示法。

在使用 Array 构造函数时也可以省略 new 操作符。

var colors = Array(3); // 创建一个包含 3 项的数组

与对象一样,在使用数组字面量表示法时,也不会调用 Array 构造函数。

数组的 length 属性很有特点——它不是只读的。
因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。请看下面的例子:


如果将其 length 属性设置为大于数组 项数的值,则新增的每一项都会取得 undefined 值,如下所示:

利用 length 属性也可以方便地在数组末尾添加新项,如下所示:

sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以 确定如何排序。即使数组中的每一项都是数值,sort()方法比较的也是字符串。


正则表达式中的元字符包括: 11 ( [ { \ ^ $ | ) ? * + .]}

这些元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,

就必须对它们进行转义。

由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

最后一种定义函数的方式是使用 Function 构造函数。Function 构造函数可以接收任意数量的参数, 但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。

var sum = new Function(“num1”, “num2”, “return num1 + num2”); // 不推荐

将函数名想象为指针,也有助于理解为什么 ECMAScript 中没有函数重载的概念。

两个同名函数,而结果则是后面的函数覆盖了前面的函数。

解析器在向执行环境中加载数据时,对函数声明函数表达式并非一视同仁。
解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);
至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

在函数内部,有两个特殊的对象:arguments 和 this

虽然 arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。

this 引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时, this 对象引用的就是 window)。

一定要牢记,函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的 sa