《JavaScript高级程序设计》

第一章:JavaScript简介

JavaScript 诞生于 1995 年,当时,它的主要目的是处理以前由服务器端语言(如 Perl)负责的一些输入验证操作

1997 年,以 JavaScript 1.1 为蓝本的建议被提交给了欧洲计算机制造商协会(ECMA, European Computer Manufacturers Association),也就是说先有的JavaScript,后有的ECMAScript

一个完整的 JavaScript 实现应该由下列三个不同的部分组成:

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

ECMAScript这门语言并不包含输入和输出定义,常见的 Web 浏览器只是 ECMAScript 实现可能的宿主环境之一

宿主环境不仅提供基本的ECMAScript 实现,同时也会提供该语言的扩展,以便语言与环境之间对接交互

这些扩展——如DOM,则利用 ECMAScript 的核心类型和语法提供更多更具体的功能,以便实现针对环境的操作

ECMAScript它规定了这门语言的下列组成部分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 对象

文档对象模型(DOM, Document Object Model) 是针对 XML 但经过扩展用于 HTML 的应用程序编程接口(API, Application Programming Interface)

DOM 把整个页面映射为一个多层节点结构, HTML或 XML 页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据

为解决浏览器强强格局互不兼容的局面,W3C开始指定DOM标准

DOM1 级的目标主要是映射文档的结构
DOM2 级引入了下列新模块,也给出了众多新类型和新接口的定义

  • DOM 视图(DOM Views):定义了跟踪不同文档(例如,应用 CSS 之前和之后的文档)视图的接口
  • DOM 事件(DOM Events):定义了事件和事件处理的接口
  • DOM 样式(DOM Style):定义了基于 CSS 为元素应用样式的接口
  • DOM 遍历和范围(DOM Traversal and Range):定义了遍历和操作文档树的接口

BOM 只处理浏览器窗口和框架,但人们习惯上也把所有针对浏览器的 JavaScript 扩展算作 BOM 的一部分,下面就是一些这样的扩展:

  • 弹出新浏览器窗口的功能
  • 移动、缩放和关闭浏览器窗口的功能
  • 提供浏览器详细信息的 navigator 对象
  • 提供浏览器所加载页面的详细信息的 location 对象
  • 提供用户显示器分辨率详细信息的 screen 对象
  • 对 cookies 的支持
  • 像 XMLHttpRequest 和 IE 的 ActiveXObject 这样的自定义对象

第二章:在HTML 中使用 JavaScript

带有 src 属性的<script>元素不应该在其<script></script>标签之间再包含额外的 JavaScript 代码,如果包含了嵌入的代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略

<script>标签定义了 defer 属性,这个属性的用途是表明脚本在执行时不会影响页面的构造
也就是说,脚本会被延迟到整个页面都解析完毕后再运行,因此,在<script>元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行

<script>标签定义了async 属性,同样与 defer 类似,async 只适用于外部脚本文件,并告诉浏览器立即下载文件
与defer不同的是,标记为 async 的脚本并不保证按照指定它们的先后顺序执行

指定 async 属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容

使用<noscript>元素可以指定在不支持脚本的浏览器中显示的替代内容
但在启用了脚本的情况下,浏览器不会显示<noscript>元素中的任何内容

第三章:基本概念

ECMAScript描述JavaScript这门语言的语法、操作符、数据类型以及内置功能等基本概念

ECMAScript 标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的首字母大写

ECMAScript 5 引入了严格模式(strict mode)的概念
严格模式是为 JavaScript 定义了一种不同的解析与执行模型,在严格模式下, ECMAScript 3 中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误

要在整个脚本中启用严格模式,可以在顶部添加如下代码:

"use strict";

这行代码看起来像是字符串,而且也没有赋值给任何变量,但其实它是一个编译指示(pragma),用于告诉支持的 JavaScript 引擎切换到严格模式

关键字和保留字虽然仍然不能作为标识符使用,但现在可以用作对象的属性名,一般来说,最好都不要使用关键字和保留字作为标识符和属性名,以便与将来的 ECMAScript 版本兼容

ECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据

换句话说
每个变量仅仅是一个用于保存值的占位符而已,定义变量时要使用 var 操作符(注意 var 是一个关键字),后跟变量名(即一个标识符)

var message;

未经过初始化的变量,会保存一个特殊的值——undefined

用 var 操作符定义的变量将成为定义该变量的作用域中的局部变量,也就是说,如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会被销毁,例如:

function test(){
var message = "hi"; // 局部变量
}
test();
alert(message); // 错误!

可以像下面这样省略 var 操作符,从而创建一个全局变量:

function test(){
message = "hi"; // 全局变量
}
test();
alert(message); // "hi"

虽然省略 var 操作符可以定义全局变量,但这也不是我们推荐的做法,因为在局部作用域中定义的全局变量很难维护,而且如果有意地忽略了 var 操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱

ECMAScript 中有 5 种简单数据类型(也称为基本数据类型):

  • Undefined
  • Null
  • Boolean
  • Number
  • String

1 种复杂数据类型——Object
Object 本质上是由一组无序的名值对组成的

typeof操作符

鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof (它是一种操作符)

对一个值使用 typeof 操作符可能返回下列某个字符串:

  • undefined——如果这个值未定义
  • boolean——如果这个值是布尔值
  • string——如果这个值是字符串
  • number——如果这个值是数值
  • object——如果这个值是对象或 null
  • function——如果这个值是函数

调用 typeof null会返回"object",因为特殊值 null 被认为是一个空的对象引用
因为typeof是操作数而不是函数,所以圆括号不是必须的

Undefined类型

Undefined 类型只有一个值,即特殊的 undefined
在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined

对未初始化的变量执行 typeof 操作符会返回 undefined 值,而对未声明的变量执行 typeof 操作符同样也会返回 undefined 值

var message;
alert(typeof message); // "undefined"
alert(typeof age); // "undefined
Null类型

Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null
从逻辑角度来看, null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因

var car = null;
alert(typeof car); // "object

如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值

undefined 值是派生自 null 值的,因此

alert(null == undefined); //true

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

Boolean类型

可以对任何数据类型的值调用 Boolean()函数,而且总会返回一个 Boolean 值
至于返回的这个值是 true 还是 false,取决于要转换值的数据类型及其实际值

string:非空转true 空转false
number : 任何非零数值包括无穷大转true 0和NaN转false
object:任何对象转为true null转为false
undefined: 不适用 undefined转为false

浮点数值

浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字

由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值转换为整数值
显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存

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

浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数
例如, 0.1 加 0.2的结果不是 0.3,而0.30000000000000004
这个小小的舍入误差会导致无法测试特定的浮点数值

ECMAScript 能够表示的最小数值保存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是 5e-324
能够表示的最大数值保存在 Number.MAX_VALUE 中——在大多数浏览器中,这个值是 1.7976931348623157e+308

如果结果超出了范围,负数转换为 -Infinity(负无穷),正数转换为 Infinity(正无穷)

如果某次计算返回了正或负的 Infinity 值,那么该值将无法继续参与下一次的计算,因为 Infinity 不是能够参与计算的数值

确定一个数值是不是有穷的(换句话说,是不是位于最
小和最大的数值之间),可以使用 isFinite()函数,这个函数在参数位于最小与最大数值之间时会返回 true

var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result));  //false
NaN

NaN即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)

在其他编程环境下,任何数除以0会导致错误,从而终止代码执行,但是在ECMAScript中,0/0为NaN,正数/0为+infinity,负数/0为-infinity

NaN有两个特点:

  1. 任何涉及NaN的操作结果都是NaN
  2. NaN与任何值都不相等,包括NaN本身
alert(NaN == NaN); //false

isNaN()函数会帮我们确定传入的参数是否“不是数值”,isNaN()在接收到一个值之后,会尝试将这个值转换为数值
某些不是数值的值会直接转换为数值,例如字符串"10"或Boolean 值,而任何不能被转换为数值的值都会导致这个函数返回 true

alert(isNaN(NaN));  //true
alert(isNaN(10));  //false( 10 是一个数值)
alert(isNaN("10"));  //false(可以被转换成数值 10)
alert(isNaN("blue"));  //true(不能转换成数值)
alert(isNaN(true));  //false(可以被转换成数值 1)

在基于对象调用 isNaN()函数时,会首先调用对象的 valueOf()方法,然后确定该方法返回的值是否可以转换为数值,如果不能,则基于这个返回值再调用 toString()方法,再测试返回值

数值转换

3个函数可以把非数值转换为数值

  • Number() //用于任何数据类型
var num1 = Number("Hello world!"); //NaN  不包含任何有意义的字符串
var num2 = Number(""); //0  空字符串转换为0
var num3 = Number("000011"); //11  忽略前导0保留11
var num4 = Number(true); //  布尔true转为1 false转为0

注意 null转为0,undefined转为NaN,则调用对象的 valueOf()方法,然后依照前面的规则转换返回的值,如果转换的结果是 NaN,则调用对象的 toString()方法,然后再次依照前面的规则转换返回的字符串值

  • parseInt() //用于字符串
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八进制数)
var num6 = parseInt("70"); // 70(十进制数)
var num7 = parseInt("0xf"); // 15(十六进制数)

parseInt() 函数在转换字符串时,更多的是看其是否符合数值模式,它会忽略字符串前面的空格,直至找到第一个非空格字符

如果第一个字符不是数字字符或者负号, parseInt()就会返回 NaN;也就是说,用 parseInt()转换空字符串会返回NaN(Number()对空字符返回 0)

如果第一个字符是数字字符, parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符,例如, “1234blue”会被转换为 1234,因为“blue”会被完全忽略,类似地, “22.5”会被转换为 22,因为小数点并不是有效的数字字符

  • parseFloat() //用于字符串
    parseFloat()也是从第一个字符(位置 0)开始解析每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止
    第二个小数点就是无效的
var num1 = parseFloat("1234blue"); //1234 (整数)
var num2 = parseFloat("0xA"); //0  始终被转换为0
var num3 = parseFloat("22.5"); //22.5  
var num4 = parseFloat("22.34.5"); //22.34  //第二个小数点后无效
var num5 = parseFloat("0908.5"); //908.5  //始终忽略前导0
var num6 = parseFloat("3.125e7"); //31250000

parseFloat()只解析十进制值,因此它没有用第二个参数指定基数的用法

String类型

ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变,要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,例如:

var lang = "Java";
lang = lang + "Script";
转换为字符串

要把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的 toString()方法

数值、布尔值、对象和字符串值(没错,每个字符串也都有一个 toString()方法,该方法返回字符串的一个副本)都有 toString()方法,但 null 和 undefined 值没有这个方法

在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。

String()函数遵循下列转换规则:

  • 如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果
  • 如果值是 null,则返回"null"
  • 如果值是 undefined,则返回"undefined"
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;
alert(String(value1)); // "10"
alert(String(value2)); // "true"
alert(String(value3)); // "null"
alert(String(value4)); // "undefined"

因为 null 和 undefined 没有 toString()方法,所以 String()
函数就返回了这两个值的字面量

可以使用“+”操作符将其与""加在一起

Object类型

ECMAScript 中的对象其实就是一组数据和功能的集合

var o = new Object();  //创建一个对象

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

  • constructor:保存着用于创建当前对象的函数,对于前面的例子而言,构造函数(constructor)就是 Object()
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如: o.hasOwnProperty(“name”))
  • isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第五章)
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句,与 hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应
  • toString():返回对象的字符串表示
  • valueOf():返回对象的字符串、数值或布尔值表示,通常与 toString()方法的返回值相同
操作符

执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的(在计算机科学领域,这种情况通常被称作副效应)

var age = 29;
var anotherAge = --age + 2;
alert(age); // 输出 28
alert(anotherAge); // 输出 30

后置递增和递减与前置递增和递减有一个非常重要的区别,即递增和递减操作是在包含它们的语句被求值之后才执行的

var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2; // 等于 22
var num4 = num1 + num2; // 等于 21

递增和递减操作符遵循下列规则

  • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作,字符串变量变成数值变量
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为 NaN(第 4 章将详细讨论)字符串变量变成数值变量
  • 在应用于布尔值 false 时,先将其转换为 0 再执行加减 1 的操作,布尔值变量变成数值变量
  • 在应用于布尔值 true 时,先将其转换为 1 再执行加减 1 的操作,布尔值变量变成数值变量
  • 在应用于浮点数值时,执行加减 1 的操作
  • 在应用于对象时,先调用对象的 valueOf()方法(第 5 章将详细讨论)以取得一个可供操作的值,然后对该值应用前述规则,如果结果是 NaN,则在调用 toString()方法后再应用前述规
    则,对象变量变成数值变量
var s1 = "2";
var s2 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1++; // 值变成数值 3
s2++; // 值变成 NaN
b++; // 值变成数值 1
f--; // 值变成 0.10000000000000009(由于浮点舍入错误所致)
o--; // 值变成数值-2
一元加和减操作符

在对非数值应用一元加操作符时,该操作符会像 Number()转型函数一样对这个值执行转换

换句话说,布尔值 false 和 true 将被转换为 0 和 1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的 valueOf()或 toString()方法,再转换得到的值

var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1 = +s1; // 值变成数值 1
s2 = +s2; // 值变成数值 1.1
s3 = +s3; // 值变成 NaN  相当于Number(s3) 因为字符串z无法被转为数字所以结果为NaN
b = +b; // 值变成数值 0
f = +f; // 值未变,仍然是 1.1
o = +o; // 值变成数值-1

一元减操作符主要用于表示负数,例如将 1 转换成-1
在将一元减操作符应用于数值时,该值会变成负数,而当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数

位操作符

在 ECMAScript 中,当对数值应用位操作符时,后台会发生如下转换过程: 64 位的数值被转换成 32位数值,然后执行位操作,最后再将 32 位的结果转换回 64 位数值。这样,表面上看起来就好像是在操作 32 位数值,就跟在其他语言中以类似方式执行二进制操作一样

这个转换过程也导致了一个严重的副效应,即在对特殊的 NaN 和 Infinity 值应用位操作时,这两个值都会被当成 0 来处理

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

1. 按位非(NOT)
按位非操作符由一个波浪线(~)表示,执行按位非的结果就是返回数值的反码
按位非是ECMAScript 操作符中少数几个与二进制计算有关的操作符之一

var num1 = 25; // 二进制 00000000000000000000000000011001
var num2 = ~num1; // 二进制 11111111111111111111111111100110
alert(num2); // -26

按位非操作的本质:操作数的负值减 1

2. 按位与(AND)
按位与操作符由一个和号字符(&)表示,它有两个操作符数按位与操作符由一个和号字符(&)表示,它有两个操作符数

按位与操作只在两个数值的对应位都是 1 时才返回 1,任何一位是 0,结果都是 0

var result = 25 & 3;
alert(result); //1

底层:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

核心就是将十进制数转换为二进制

3. 按位或(OR)
按位或操作符由一个竖线符号(|)表示,同样也有两个操作数
与按位与相反
按位或操作在有一个位是 1 的情况下就返回 1, 而只有在两个位都是 0 的情况下才返回 0

var result = 25 | 3;
alert(result); //27

底层:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

4. 按位异或(XOR)
按位异或操作符由一个插入符号(^)表示,也有两个操作数
按位异或与按位或的不同之处在于,这个操作在两个数值对应位上只有一个 1 时才返回 1,如果对应的两位都是 1 或都是 0,则返回 0

var result = 25 ^ 3;
alert(result); //26

底层同理合并

5. 左移
左移操作符由两个小于号(<<)表示,这个操作符会将数值的所有位向左移动指定的位数

在向左移位后,原数值的右侧多出了 5 个空位,左移操作会以 0 来填充这些空位,以便得到的结果是一个完整的 32 位二进制数

var oldValue = 2; // 等于二进制的 10
var newValue = oldValue << 5; // 等于二进制的 1000000,十进制的 64

左移不会影响操作数的符号位。换句话说,如果将-2 向左移动 5 位,结果将是-64,而非 64

6. 有符号的右移

有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)

javascript高级程序设计第五版 脚本之家 javascript高级程序设计第一版_《JavaScript高级程序设计》


在符号为之后数值之前插入空白符实现右移

7. 无符号右移
无符号右移操作符由 3 个大于号(>>>)表示,这个操作符会将数值的所有 32 位都向右移动。对正数来说,无符号右移的结果与有符号右移相同

但是对负数来说,情况就不一样了
首先,无符号右移是以 0 来填充空位,而不是像有符号右移那样以符号位的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了

无符号右移操作符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常之大

布尔操作符

1. 逻辑非
逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再
对其求反,也就是说,逻辑非操作符遵循下列规则:

  • 如果操作数是一个对象,返回 false
  • 如果操作数是一个空字符串,返回 true
  • 如果操作数是一个非空字符串,返回 false
  • 如果操作数是数值 0,返回 true
  • 如果操作数是任意非 0 数值(包括 Infinity),返回 false
  • 如果操作数是 null,返回 true
  • 如果操作数是 NaN,返回 true
  • 如果操作数是 undefined,返回 true

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

2. 逻辑与
记忆:两true为true,其余为false
逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值

在有一个操作数不是布尔值的情况下,逻辑与操作就不一定返回布尔值;此时,它遵循下列规则:

  • 如果第一个操作数是对象,则返回第二个操作数
  • 如果第二个操作数是对象,则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象
  • 如果两个操作数都是对象,则返回第二个操作数
  • 如果有一个操作数是 null,则返回 null
  • 如果有一个操作数是 NaN,则返回 NaN
  • 如果有一个操作数是 undefined,则返回 undefined

逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值
对于逻辑与操作而言,如果第一个操作数是 false,则无论第二个操作数是什么值,结果都不再可能是true 了

3. 逻辑或
记忆:有true为true
与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值;此时,它遵循下列规则:

  • 如果第一个操作数是对象,则返回第一个操作数
  • 如果第一个操作数的求值结果为 false,则返回第二个操作数
  • 如果两个操作数都是对象,则返回第一个操作数
  • 如果两个操作数都是 null,则返回 null
  • 如果两个操作数都是 NaN,则返回 NaN
  • 如果两个操作数都是 undefined,则返回 undefined

与逻辑与操作符相似,逻辑或操作符也是短路操作符
也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作数求值了

乘性操作符

ECMAScript 定义了 3 个乘性操作符:乘法、除法和求模

1.乘法
乘法操作符遵循下列特殊的规则: