作者:yanyige | 发布时间:2017-03-06 15:56

第一章 Javascript简介

1.2 Javascript实现

虽然Javascript和ECMAScript通常都被人们用来表达相同的含义,但是Javascript的含义却比ECMA-262(发音为”ek-ma-script”)中规定的要多的多。没错,Javascript实现应该由以下三个不同的部分组成。

1.2.1 ECMAScript

由ECMA-262 定义的ECMAScript 与Web 浏览器没有依赖关系。实际上,这门语言本身并不包含输 入和输出定义。

ECMA-262 定义的只是这门语言的基础,而在此基础之上可以构建更完善的脚本语言。 我们常见的Web 浏览器只是ECMAScript 实现可能的宿主环境之一。

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

而这些扩展——如DOM,则利用ECMAScript 的核心类型和语法提供更多更具体的功能,以便实现针对环境的操作。其他 宿主环境包括Node(一种服务端JavaScript 平台)和Adobe Flash。

既然ECMA-262 标准没有参照Web 浏览器,那它都规定了些什么内容呢?大致说来,它规定了这 门语言的下列组成部分:

1.2.2 文档对象模型(DOM)

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

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

看下面这个HTML 页面:

<html>
    <head>
        <title>Sample Page</title>
    </head>
    <body>
        <p>Hello World!</p>
    </body>
</html>

在DOM 中,这个页面可以通过图1-2 所示的分层节点图表示。 通过DOM 创建的这个表示文档的树形图,开发人员获得了控制页面内容和结构的主动权。借助 DOM 提供的API,开发人员可以轻松自如地删除、添加、替换或修改任何节点。 image

DOM级别

DOM1 级(DOM Level 1)于1998 年10 月成为W3C 的推荐标准。

DOM1级由两个模块组成:DOM核心(DOM Core)和DOM HTML。其中,DOM 核心规定的是如何映射基于XML 的文档结构,以便简化对文档中任意部分的访问和操作。DOM HTML 模块则在DOM 核心的基础上加以扩展,添加了针对HTML 的对象和方法。

如果说DOM1 级的目标主要是映射文档的结构,那么DOM2 级的目标就要宽泛多了。 DOM2 级在原来DOM 的基础上又扩充了(DHTML一直都支持的)鼠标和用户界面事件、范围、遍历(迭代DOM文档的方法)等细分模块,而且通过对象接口增加了对CSS(Cascading Style Sheets,层叠样式表)的支持。DOM1 级中的DOM核心模块也经过扩展开始支持XML 命名空间。

DOM2 级引入了下列新模块,也给出了众多新类型和新接口的定义。

DOM3 级则进一步扩展了DOM,引入了以统一方式加载和保存文档的方法——在DOM 加载和保 存(DOM Load and Save)模块中定义;新增了验证文档的方法——在DOM 验证(DOM Validation)模块中定义。DOM3 级也对DOM 核心进行了扩展,开始支持XML 1.0 规范,涉及XML Infoset、XPath和XML Base。

1.2.3 浏览器对象模型(BOM)

Internet Explorer 3 和Netscape Navigator 有一个共同的特色,那就是支持可以访问和操作浏览器窗口的浏览器对象模型(BOM,Browser Object Model)。开发人员使用BOM 可以控制浏览器显示的页面以外的部分。而BOM真正与众不同的地方(也是经常会导致问题的地方),还是它作为JavaScript 实现 的一部分但却没有相关的标准。这个问题在HTML5 中得到了解决,HTML5 致力于把很多BOM 功能写 入正式规范。HTML5 发布后,很多关于BOM 的困惑烟消云散。 从根本上讲,BOM 只处理浏览器窗口和框架;但人们习惯上也把所有针对浏览器的JavaScript 扩展 算作BOM的一部分。下面就是一些这样的扩展:

以上扩展,用代码如何实现?

由于没有BOM标准可以遵循,因此每个浏览器都有自己的实现。虽然也存在一些事实标准,例如 要有window对象和navigator对象等,但每个浏览器都会为这两个对象乃至其他对象定义自己的属性和方法。现在有了HTML5,BOM实现的细节有望朝着兼容性越来越高的方向发展。第8章将深入讨论BOM。

第二章 在HTML中使用Javascript

2.1

向HTML 页面中插入JavaScript 的主要方法,就是使用script元素。这个元素由Netscape创造 并在Netscape Navigator 2 中首先实现。后来,这个元素被加入到正式的HTML 规范中。HTML 4.01 为

第三章 基本概念

3.1 语法

ECMAScript 的语法大量借鉴了C 及其他类C 语言(如Java 和Perl)的语法。因此,熟悉这些语言 的开发人员在接受ECMAScript 更加宽松的语法时,一定会有一种轻松自在的感觉。

3.1.1 区分大小写

要理解的第一个概念就是ECMAScript 中的一切(变量、函数名和操作符)都区分大小写。这也就 意味着,变量名test 和变量名Test 分别表示两个不同的变量,而函数名不能使用typeof,因为它 是一个关键字(3.2 节介绍关键字),但typeOf 则完全可以是一个有效的函数名。

3.1.2 标识符

所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。标识符可以是按照下列格式规则 组合起来的一或多个字符:

标识符中的字母也可以包含扩展的ASCII 或Unicode 字母字符(如À和Æ),但我们不推荐这样做。 按照惯例,ECMAScript 标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的 首字母大写,例如:

虽然没有谁强制要求必须采用这种格式,但为了与ECMAScript 内置的函数和对象命名格式保持一 致,可以将其当作一种最佳实践。

3.1.3 注释

ECMAScript 使用C 风格的注释,包括单行注释和块级注释。单行注释以两个斜杠开头,如下所示:

// 单行注释

块级注释以一个斜杠和一个星号(/)开头,以一个星号和一个斜杠(/)结尾,如下所示:

/*
* 这是一个多行
* (块级)注释
*/

3.1.4 严格模式

ECMAScript 5 引入了严格模式(strict mode)的概念。严格模式是为JavaScript定义了一种不同的解析与执行模型。在严格模式下,ECMAScript 3中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。要在整个脚本中启用严格模式,可以在顶部添加如下代码:

"use strict";

3.1.4 语句

ECMAScript 中的语句以一个分号结尾;如果省略分号,则由解析器确定语句的结尾,如下例所示:

var sum = a + b // 即使没有分号也是有效的语句——不推荐
var diff = a - b; // 有效的语句——推荐

虽然语句结尾的分号不是必需的,但我们建议任何时候都不要省略它。因为加上这个分号可以避免 很多错误(例如不完整的输入),开发人员也可以放心地通过删除多余的空格来压缩ECMAScript 代码(代 码行结尾处没有分号会导致压缩错误)。另外,加上分号也会在某些情况下增进代码的性能,因为这样 解析器就不必再花时间推测应该在哪里插入分号了

3.2 关键字和保留字

break do instanceof typeof
case else new var
catch finally return void
continue for switch while
debugger function this with
default if throw delete
in try    

3.3 变量

ECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说, 每个变量仅仅是一个用于保存值的占位符而已。定义变量时要使用var 操作符(注意var 是一个关键 字),后跟变量名(即一个标识符),如下所示:

var message;

这行代码定义了一个名为message 的变量,该变量可以用来保存任何值(像这样未经过初始化的 变量,会保存一个特殊的值——undefined,相关内容将在3.4 节讨论)。ECMAScript 也支持直接初始 化变量,因此在定义变量的同时就可以设置变量的值,如下所示: var message = “hi”; 在此,变量message 中保存了一个字符串值”hi”。像这样初始化变量并不会把它标记为字符串类型; 初始化的过程就是给变量赋一个值那么简单。因此,可以在修改变量值的同时修改值的类型,如下所示:

var message = "hi";
message = 100; // 有效,但不推荐

在这个例子中,变量message 一开始保存了一个字符串值”hi”,然后该值又被一个数字值100 取 代。虽然我们不建议修改变量所保存值的类型,但这种操作在ECMAScript 中完全有效。 有一点必须注意,即用var 操作符定义的变量将成为定义该变量的作用域中的局部变量。也就是说, 如果在函数中使用var 定义一个变量,那么这个变量在函数退出后就会被销毁,例如:

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

这里,变量message 是在函数中使用var 定义的。当函数被调用时,就会创建该变量并为其赋值。 而在此之后,这个变量又会立即被销毁,因此例子中的下一行代码就会导致错误。不过,可以像下面这样省略var 操作符,从而创建一个全局变量:

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

这个例子省略了var 操作符,因而message 就成了全局变量。这样,只要调用过一次test()函 数,这个变量就有了定义,就可以在函数外部的任何地方被访问到。

思考一下,这是为什么呢?

3.4 数据类型

ECMAScript 中有5 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number 和String。

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

ECMAScript不支持任何创建自定义类型的机制,而所有值最终都将是上述6 种数据类型之一。乍一看,好像只有6种数据类型不足以表示所有数据;但是,由于ECMAScript 数据类型具有动态性,因此的确没有再定义其他数据类型的必要了。

3.4.1 typeof操作符

鉴于ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof 就 是负责提供这方面信息的操作符。对一个值使用typeof 操作符可能返回下列某个字符串:

有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值。比如,调用typeof null会返回”object”,因为特殊值null 被认为是一个空的对象引用。Safari 5 及之前版本、Chrome 7 及之前版本在对正则表达式调用typeof操作符时会返回”function”,而其他浏览器在这种情况下会返回”object”。

3.4.2 Undefined类型

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

var message;
alert(message == undefined); //true

这个例子只声明了变量message,但未对其进行初始化。比较这个变量与undefined 字面量,结 果表明它们是相等的。这个例子与下面的例子是等价的:

var message = undefined;
alert(message == undefined); //true

这个例子使用undefined 值显式初始化了变量message。但我们没有必要这么做,因为未经初始 化的值默认就会取得undefined 值。

一般而言,不存在需要显式地把一个变量设置为undefined 值的情况。字面值 undefined 的主要目的是用于比较,而ECMA-262 第3 版之前的版本中并没有规定 这个值。第3 版引入这个值是为了正式区分空对象指针与未经初始化的变量。

不过,包含undefined 值的变量与尚未定义的变量还是不一样的。看看下面这个例子:

var message; // 这个变量声明之后默认取得了undefined 值
// 下面这个变量并没有声明
// var age
alert(message); // "undefined"
alert(age); // 产生错误

运行以上代码,第一个警告框会显示变量message的值,即”undefined”。而第二个警告框—— 由于传递给alert()函数的是尚未声明的变量age——则会导致一个错误。对于尚未声明过的变量,只能执行一项操作,即使用typeof 操作符检测其数据类型(对未经声明的变量调用delete 不会导致错误,但这样做没什么实际意义,而且在严格模式下确实会导致错误)。

然而,令人困惑的是:对未初始化的变量执行typeof 操作符会返回undefined 值,而对未声明 的变量执行typeof 操作符同样也会返回undefined 值。来看下面的例子:

var message; // 这个变量声明之后默认取得了undefined 值
// 下面这个变量并没有声明
// var age
alert(typeof message); // "undefined"
alert(typeof age); // "undefined"

即便未初始化的变量会自动被赋予undefined 值,但显式地初始化变量依然是 明智的选择。如果能够做到这一点,那么当typeof 操作符返回”undefined”值时, 我们就知道被检测的变量还没有被声明,而不是尚未初始化。

3.4.3 Null类型

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

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

实际上,undefined 值是派生自null 值的,因此ECMA-262 规定对它们的相等性测试要返回true:

3.4.4 Boolean类型

Boolean 类型是ECMAScript 中使用得最多的一种类型,该类型只有两个字面值:true 和false。 这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0。

小问题: -1转化成Boolean,是true还是false?

转换规则

| 数据类型 | 转换为true的值 | 转换为false的值 | |:————:|:——————-:|:—————–:| | Boolean | true | false | | String | 任何非空字符串 | “” | | Number | 任何非零数字值 | 0和NaN | | Object | 任何对象 | null | | Undefined | n/a | undefined |

3.4.5 Number类型

Number 类型应该是ECMAScript 中最令人关注的数据类型了,这种类型使用IEEE754 格式来表示 整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。为支持各种数值类型,ECMA-262 定 义了不同的数值字面量格式。

最基本的数值字面量格式是十进制整数,十进制整数可以像下面这样直接在代码中输入: var intNum = 55; // 整数 除了以十进制表示外,整数还可以通过八进制(以8 为基数)或十六进制(以16 为基数)的字面值 来表示。

其中,八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7)。如果字面值中的 数值超出了范围,那么前导零将被忽略,后面的数值将被当作十进制数值解析。请看下面的例子:

1. var octalNum1 = 070; // 八进制的56
2. var octalNum2 = 079; // 无效的八进制数值——解析为79
3. var octalNum3 = 08; // 无效的八进制数值——解析为8

八进制字面量在严格模式下是无效的,会导致支持的JavaScript 引擎抛出错误。 十六进制字面值的前两位必须是0x,后跟任何十六进制数字(0~9 及A~F)。其中,字母A~F 可以大写,也可以小写。如下面的例子所示:

1. var hexNum1 = 0xA; // 十六进制的10
2. var hexNum2 = 0x1f; // 十六进制的31

在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。

1. 浮点数值

所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。虽然小 数点前面可以没有整数,但我们不推荐这种写法。以下是浮点数值的几个例子:

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1; // 有效,但不推荐

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

if (a + b == 0.3){ // 不要做这样的测试!
alert("You got 0.3.");
}

在这个例子中,我们测试的是两个数的和是不是等于0.3。如果这两个数是0.05 和0.25,或者是0.15 和0.15 都不会有问题。而如前所述,如果这两个数是0.1 和0.2,那么测试将无法通过。因此,永远不要++测试某个特定的浮点数值++。

2. 数值范围

由于内存的限制,ECMAScript 并不能保存世界上所有的数值。ECMAScript 能够表示的最小数值保存在Number.MIN_VALUE中——在大多数浏览器中,这个值是5e-324;能够表示的最大数值保存在Number.MAX_VALUE中——在大多数浏览器中,这个值是1.7976931348623157e+308。如果某次计算的结果得到了一个超出JavaScript数值范围的值,那么这个数值将被自动转换成特殊的Infinity值。具体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正,则会被转换成Infinity(正无穷)。

如上所述,如果某次计算返回了正或负的Infinity 值,那么该值将无法继续参与下一次的计算, 因为Infinity 不是能够参与计算的数值。要想确定一个数值是不是有穷的(换句话说,是不是位于最 小和最大的数值之间),可以使用isFinite()函数。这个函数在参数位于最小与最大数值之间时会返 回true,如下面的例子所示:

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

尽管在计算中很少出现某些值超出表示范围的情况,但在执行极小或极大数值的计算时,检测监控 这些值是可能的,也是必需的。

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

那么问题来了,Number.NEGATIVE_INFINITY 和Number.POSITIVE_INFINITY 这两个值分别保存在哪里?

3. NaN

NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数 未返回数值的情况(这样就不会抛出错误了)。例如,在其他编程语言中,任何数值除以0都会导致错误,从而停止代码执行。但在ECMAScript中,任何数值除以0会返回NaN,因此不会影响其他代码的执行。

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

alert(NaN == NaN); //false

针对NaN 的这两个特点,ECMAScript 定义了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)

4. 数值转换

有3 个函数可以把非数值转换为数值:Number()、parseInt()和parseFloat()。第一个函数,即转型函数Number()可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。这3个函数对于同样的输入会有返回不同的结果。 Number()函数的转换规则如下。

var num1 = Number("Hello world!");
var num2 = Number("");
var num3 = Number("000011");
var num4 = Number(true);

一元加操作符(3.5.1 节将介绍)的操作与Number()函数相同。

由于Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()函数。parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN;也就是说,用parseInt()转换空字符串会返回NaN(Number()对空字符返回0)。如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如,”1234blue”会被转换为1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为22,因为小数点并不是有效的数字字符。

如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的 十进制、八进制和十六进制数)。也就是说,如果字符串以”0x”开头且后跟数字字符,就会将其当作一 个十六进制整数;如果字符串以”0”开头且后跟数字字符,则会将其当作一个八进制数来解析。 为了更好地理解parseInt()函数的转换规则,下面给出一些例子:

var num1 = parseInt("1234blue");
var num2 = parseInt("");
var num3 = parseInt("0xA");
var num4 = parseInt(22.5);
var num5 = parseInt("070");
var num6 = parseInt("70");
var num7 = parseInt("0xf");

在使用parseInt()解析像八进制字面量的字符串时,ECMAScript 3 和5 存在分歧。例如: //ECMAScript 3 认为是56(八进制),ECMAScript 5 认为是70(十进制)

为了消除在使用parseInt()函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换 时使用的基数(即多少进制)。如果知道要解析的值是十六进制格式的字符串,那么指定基数16 作为第 二个参数,可以保证得到正确的结果,例如:

var num = parseInt("0xAF", 16); //175

实际上,如果指定了16 作为第二个参数,字符串可以不带前面的”0x”,如下所示:

var num1 = parseInt("AF", 16);
var num2 = parseInt("AF");

3.4.6 String类型

String 类型用于表示由零或多个16 位Unicode 字符组成的字符序列,即字符串。字符串可以由双 引号(”)或单引号(’)表示,因此下面两种字符串的写法都是有效的:

var firstName = "Nicholas";
var lastName = 'Zakas';

与PHP 中的双引号和单引号会影响对字符串的解释方式不同,ECMAScript 中的这两种语法形式没 有什么区别。用双引号表示的字符串和用单引号表示的字符串完全相同。不过,以双引号开头的字符串也必须以双引号结尾,而以单引号开头的字符串必须以单引号结尾。例如,下面这种字符串表示法会导 致语法错误:

var firstName = 'Nicholas"; // 语法错误(左右引号必须匹配)
1. 字符字面量

String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其 他用途的字符。这些字符字面量如下表所示:

这些字符字面量可以出现在字符串中的任意位置,而且也将被作为一个字符来解析,如下面的例子所示:

var text = "This is the letter sigma: \u03a3.";

这个例子中的变量text 有28 个字符,其中6 个字符长的转义序列表示1个字符。 任何字符串的长度都可以通过访问其length 属性取得,例如:

alert(text.length); // 输出28

这个属性返回的字符数包括16 位字符的数目。如果字符串中包含双字节字符,那么length属性 可能不会精确地返回字符串中的字符数目。

2. 字符串的特点

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

3. 转换为字符串

要把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的toString()方法(第5 章将讨论这个方法的特点)。这个方法唯一要做的就是返回相应值的字符串表现。来看下面的例子:

var age = 11;
var ageAsString = age.toString(); // 字符串"11"
var found = true;
var foundAsString = found.toString(); // 字符串"true"

数值、布尔值、对象和字符串值(没错,每个字符串也都有一个toString()方法,该方法返回字 符串的一个副本)都有toString()方法。但null 和undefined 值没有这个方法。 多数情况下,调用toString()方法不必传递参数。但是,在调用数值的toString()方法时,可 以传递一个参数:输出数值的基数。默认情况下,toString()方法以十进制格式返回数值的字符串表 示。而通过传递基数,toString()可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格 式表示的字符串值。下面给出几个例子:

var num = 10;
alert(num.toString()); // "10"
alert(num.toString(2)); // "1010"
alert(num.toString(8)); // "12"
alert(num.toString(10)); // "10"
alert(num.toString(16)); // "a"

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

下面再看几个例子:

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"

这里先后转换了4 个值:数值、布尔值、null 和undefined。数值和布尔值的转换结果与调用toString()方法得到的结果相同。因为null和undefined 没有toString()方法,所以String()函数就返回了这两个值的字面量。

要把某个值转换为字符串,可以使用加号操作符(3.5 节讨论)把它与一个字符 串(”“)加在一起。

3.4.7 Object类型

ECMAScript 中的对象其实就是一组数据和功能的集合。对象可以通过执行new 操作符后跟要创建 的对象类型的名称来创建。而创建Object 类型的实例并为其添加属性和(或)方法,就可以创建自定 义对象,如下所示:

var o = new Object();

这个语法与Java 中创建对象的语法相似;但在ECMAScript 中,如果不给构造函数传递参数,则可 以省略后面的那一对圆括号。也就是说,在像前面这个示例一样不传递参数的情况下,完全可以省略那 对圆括号(但这不是推荐的做法):

var o = new Object; // 有效,但不推荐省略圆括号

仅仅创建Object 的实例并没有什么用处,但关键是要理解一个重要的思想:即在ECMAScript 中, (就像Java 中的java.lang.Object 对象一样)Object 类型是所有它的实例的基础。换句话说, Object 类型所具有的任何属性和方法也同样存在于更具体的对象中。 Object 的每个实例都具有下列属性和方法。

由于在ECMAScript 中Object 是所有对象的基础,因此所有对象都具有这些基本的属性和方法。 第5 章和第6 章将详细介绍Object 与其他对象的关系。

3.5 操作符

3.5.1 一元操作符

1. 递增和递减操作符

++和– 考虑如下代码:

var age = 29;
var anotherAge = --age + 2;
var age = 29;
var anotherAge = age-- + 2;
2. 一元加和减操作符

绝大多数开发人员对一元加和减操作符都不会陌生,而且这两个ECMAScript 操作符的作用与数学 书上讲的完全一样。一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响, 如下面的例子所示:

var num = 25;
num = +num; // 仍然是25

不过,在对非数值应用一元加操作符时,该操作符会像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
b = +b; // 值变成数值0
f = +f; // 值未变,仍然是1.1
o = +o; // 值变成数值-1

3.5.2 位操作符

x\^y==y\^x, (x\^y)\^z == x\^(y\^z), x^x == 0, x^0 == x

使用位运算交换两个值,如何交换?

3.5.3 布尔操作符

3.5.4 乘性操作符

var max = (num1 > num2) ? num1 : num2;

3.5.9 赋值操作符

3.5.10 赋值操作符

使用逗号操作符可以在一条语句中执行多个操作,如下面的例子所示:

var num1=1, num2=2, num3=3;

逗号操作符多用于声明多个变量;但除此之外,逗号操作符还可以用于赋值。在用于赋值时,逗号 操作符总会返回表达式中的最后一项,如下面的例子所示:

var num = (5, 1, 4, 8, 0); // num 的值为0

由于0 是表达式中的最后一项,因此num 的值就是0。虽然逗号的这种使用方式并不常见,但这个 例子可以帮我们理解逗号的这种行为。

3.6 语句

3.6.1 if语句

3.6.2 do-while语句

do {
statement
} while (expression);

3.6.3 while语句

3.6.4 for语句

3.6.5 for-in语句

for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。以下是for-in 语句的语法:

for (var propName in window) {
document.write(propName);
}

3.6.7 break和continue语句

break 和continue 语句用于在循环中精确地控制代码的执行。其中,break 语句会立即退出循环, 强制继续执行循环后面的语句。而continue 语句虽然也是立即退出循环,但退出循环后会从循环的顶 部继续执行。请看下面的例子:

var num = 0;
for (var i=1; i < 10; i++) {
    if (i % 5 == 0) {
        break; //换成continue
    }
        num++;
}
alert(num); //4

3.6.8 with语句

with 语句的作用是将代码的作用域设置到一个特定的对象中。with 语句的语法如下:

with(location){
    var hostName = hostname;
}

3.6.9 switch语句

3.7 函数

函数对任何语言来说都是一个核心的概念。通过函数可以封装任意多条语句,而且可以在任何地方、 任何时候调用执行。ECMAScript 中的函数使用function 关键字来声明,后跟一组参数以及函数体。 函数的基本语法如下所示:

function sayHi(name, message) {
    alert("Hello " + name + "," + message);
}

ECMAScript 中的函数在定义时不必指定是否返回值。实际上,任何函数在任何时候都可以通过 return 语句后跟要返回的值来实现返回值。请看下面的例子:

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

3.7.1 理解参数

ECMAScript 函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript 函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递数,而解析器永远不会有什么怨言。之所以会这样,原因是ECMAScript中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。 其实,arguments 对象只是与数组类似(它并不是Array的实例),因为可以使用方括号语法访问它的每一个元素(即第一个元素是arguments[0],第二个素是argumetns[1],以此类推),使用length属性来确定传递进来多少个参数。在前面的例子中,sayHi()函数的第一个参数的名字叫name,而该参数的值也可以通过访问arguments[0]来获取。因此,那个函数也可以像下面这样重写,即不显式地使用命名参数:

function sayHi() {
    alert("Hello " + arguments[0] + "," + arguments[1]);
}

这个重写后的函数中不包含命名的参数。虽然没有使用name 和message 标识符,但函数的功能依旧。这个事实说明了ECMAScript函数的一个重要特点:命名的参数只提供便利,但不是必需的。另外,在命名参数方面,其他语言可能需要事先创建一个函数签名,而将来的用必须与该签名一致。但在ECMAScript中,没有这些条条框框,解析器不会验证命名参数。通过访问arguments 对象的length属性可以获知有多少个参数传递给了函数。下面这个函数会在每次被调用时,输出传入其中的参数个数:

function howManyArgs() {
    alert(arguments.length);
}
howManyArgs("string", 45); //2
howManyArgs(); //0
howManyArgs(12); //1

执行以上代码会依次出现3 个警告框,分别显示2、0 和1。由此可见,开发人员可以利用这一点让 函数能够接收任意个参数并分别实现适当的功能。请看下面的例子:

function doAdd() {
    if(arguments.length == 1) {
        alert(arguments[0] + 10);
    } else if (arguments.length == 2) {
        alert(arguments[0] + arguments[1]);
    }
}
doAdd(10); //20
doAdd(30, 20); //50

3.7.2 没有重载

ECMAScript 函数不能像传统意义上那样实现重载。而在其他语言(如Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述,ECMAScirpt函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。

如果在ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数。请看下面的例子:

function addSomeNumber(num){
    return num + 100;
}
function addSomeNumber(num) {
    return num + 200;
}
var result = addSomeNumber(100); //300

在此,函数addSomeNumber()被定义了两次。第一个版本给参数加100,而第二个版本给参数加200。由于后定义的函数覆盖了先定义的函数,因此当在最后一行代码中调用这个函数时,返回的结果就是300。

如前所述,通过检查传入函数中参数的类型和数量并作出不同的反应,可以模仿方法的重载。