本章内容
语法
数据类型
流控制语句
函数
3.1 语法
ECMAScript 的语法大量借鉴了 C 及其他类 C 语言(如 Java 和 Perl)的语法。
3.1.1 区分大小写
3.1.2 标识符
所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。标识符可以是按照下列格式规则 组合起来的一或多个字符:
第一个字符必须是一个字母、下划线(_)或一个美元符号($);
其他字符可以是字母、下划线、美元符号或数字。
标识符中的字母也可以包含扩展的 ASCII 或 Unicode 字母字符(如 À 和 Æ),但我们不推荐这样做。 按照惯例,ECMAScript 标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的首字母大写,例如:
firstSecond
myCar
doSomethingImportant
虽然没有谁强制要求必须采用这种格式,但为了与 ECMAScript 内置的函数和对象命名格式保持一 致,可以将其当作一种最佳实践。
3.1.3 注释
ECMAScript 使用 C 风格的注释,包括单行注释和块级注释。单行注释以两个斜杠开头,如下所示: //
单行注释块级注释以一个斜杠和一个星号(/*
)开头,以一个星号和一个斜杠(*/
)结尾,如下所示:
/*
* 这是一个多行
* (块级)注释
*/
虽然上面注释中的第二和第三行都以一个星号开头,但这不是必需的。之所以添加那两个星号,纯粹是为了提高注释的可读性(这种格式在企业级应用中用得比较多)。
3.1.4 严格模式
ECMAScript 5 引入了严格模式(strict mode)的概念。严格模式是为 JavaScript 定义了一种不同的 解析与执行模型。在严格模式下,ECMAScript 3 中的一些不确定的行为将得到处理,而且对某些不安全 的操作也会抛出错误。要在整个脚本中启用严格模式,可以在顶部添加如下代码:
"use strict";
这行代码看起来像是字符串,而且也没有赋值给任何变量,但其实它是一个编译指示(pragma), 用于告诉支持的 JavaScript 引擎切换到严格模式。这是为不破坏 ECMAScript 3 语法而特意选定的语法。在函数内部的上方包含这条编译指示,也可以指定函数在严格模式下执行:
function doSomething(){
"use strict";
//函数体
}
3.1.5 语句
ECMAScript 中的语句以一个分号结尾;如果省略分号,则由解析器确定语句的结尾,如下例所示:
var sum = a + b // 即使没有分号也是有效的语句——不推荐
var diff = a - b; // 有效的语句——推荐(1.可阅读性好2.在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了。)
3.2 关键字和保留字
3.3 变量
ECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。定义变量时要使用 var 操作符(注意 var 是一个关键 字),后跟变量名(即一个标识符),如下所示:
var message;
这行代码定义了一个名为 message 的变量,该变量可以用来保存任何值(像这样未经过初始化的变量,会保存一个特殊的值——undefined,相关内容将在 3.4 节讨论)。ECMAScript 也支持直接初始 化变量,因此在定义变量的同时就可以设置变量的值,如下所示:
var message = "hi";
在此,变量 message 中保存了一个字符串值"hi"。像这样初始化变量并不会把它标记为字符串类型;初始化的过程就是给变量赋一个值那么简单。因此,可以在修改变量值的同时修改值的类型,如下所示:
var message = "hi";
message = 100; // 有效,但不推荐
有一点必须注意,即用 var 操作符定义的变量将成为定义该变量的作用域中的局部变量。也就是说, 如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会被销毁,例如:
function test(){
var message = "hi"; // 局部变量
}
test();
alert(message); // 错误!
不过,可以像下面这 样省略 var 操作符,从而创建一个全局变量:
function test(){
message = "hi"; // 全局变量
}
test();
alert(message); // hi
可以使用一条语句定义多个变量:
var message = "hi",
found = false,
age = 29;
在严格模式下,不能定义名为 eval 或 arguments 的变量,否则会导致语法错误。
3.4 数据类型
ECMAScript 中有 5 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number 和 String。还有 1 种复杂数据类型——Object,Object 本质上是由一组无序的名值对组成的。
3.4.1 typeof操作符
鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof 就 是负责提供这方面信息的操作符。对一个值使用 typeof 操作符可能返回下列某个字符串:
“undefined”——如果这个值未定义;
“boolean”——如果这个值是布尔值;
“string”——如果这个值是字符串;
“number”——如果这个值是数值;
“object”——如果这个值是对象或 null;
“function”——如果这个值是函数。
下面是几个使用 typeof 操作符的例子:
var message = "hello";
alert(typeof message); // string
alert(typeof (message)); // string
alert(typeof 666); // number
alert(typeof null); // object
alert(typeof x); // undefined
typeof 是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必需的。
3.4.2 Undefined类型
Undefined 类型只有一个值,即特殊的 undefined。在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined,例如:
var message1;
alert(message1 == undefined); // true
var message2 = undefined;
alert(message2 == undefined); // true
不过,包含 undefined 值的变量与尚未定义的变量还是不一样的。看看下面这个例子:
var message; // 这个变量声明之后默认取得了 undefined 值
// 下面这个变量并没有声明
// var age
alert(message);
alert(typeof age); // undefined
alert(age); // 产生错误
3.4.3 Null类型
Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null。从逻辑角度来看,null 值表 示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因,如下面 的例子所示:
var car = null;
alert(typeof car); // "object"
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值。这样 一来,只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用,如下面的例子 所示:
if (car != null){
// 对 car 对象执行某些操作
}
实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true:
alert(null == undefined); //true
这里,位于 null 和 undefined 之间的相等操作符(==)总是返回 true,不过要注意的是,这个 操作符出于比较的目的会转换其操作数(本章后面将详细介绍相关内容)。
尽管 null 和 undefined 有这样的关系,但它们的用途完全不同。如前所述,无论在什么情况下 都没有必要把一个变量的值显式地设置为 undefined,可是同样的规则对 null 却不适用。换句话说, 只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null 值。这样做不仅可以 体现 null 作为空对象指针的惯例,而且也有助于进一步区分 null 和 undefined。
3.4.4 Boolean类型
Boolean 类型是 ECMAScript 中使用得最多的一种类型,该类型只有两个字面值:true 和 false。 这两个值与数字值不是一回事,因此 true 不一定等于 1,而 false 也不一定等于 0。
True 和 False (以及其他的混合大小写形式)都不是 Boolean 值,只是标识符。
虽然 Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值 等价的值。要将一个值转换为其对应的 Boolean 值,可以调用转型函数 Boolean(),如下例所示:
var message = "Hello world!";
var messageAsBoolean = Boolean(message); // true
下表给出了各种数据类型及其对 应的转换规则:
注:n/a(或 N/A),是 not applicable 的缩写,意思是“不适用”。
这些转换规则对理解流控制语句(如if语句)自动执行相应的Boolean转换非常重要,请看下面 的代码:
var message = "Hello world!";
if (message){
alert("Value is true"); // Value is true
}
3.4.5 Number类型
Number 类型应该是 ECMAScript 中最令人关注的数据类型了,这种类型使用 IEEE754 格式来表示 整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。
var intNum = 55; // 十进制数
var octalNum1 = 070; // 八进制数
var octalNum2 = 079; // 无效八进制数——解析为十进制79
var octalNum3 = 08; // 无效八进制数——解析为十进制8
var hexNum1 = 0xA; // 十六进制数
var hexNum2 = 0x1f; // 十六进制数
八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误。
在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。
浮点数值
var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1; // 有效,但不推荐
由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值 转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。同样 地,如果浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数,如下面的例子所示:
var floatNum1 = 1.; // 小数点后面没有数字——解析为 1
var floatNum2 = 10.0; // 整数——解析为 10
var floatNum = 3.125e7; // 等于31250000,科学计数法,e可以换成E
0.00000000000000003,这个数值可以使用更简洁的 3e17 表示。在默认情况下,ECMASctipt 会将那些小数点后面带有 6 个零以上的浮点数值转换为以 e 表示法 表示的数值(例如,0.0000003 会被转换成 3e-7)。
浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2 的结果不是 0.3,而是 0.30000000000000004。
数值范围
由于内存的限制,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
NaN
NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。
NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN,这 个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。
alert(0 / 0); // NaN
alert(1 / 0); // Infinity
alert(-1 / 0); // -Infinity
alert(NaN == NaN); // false
alert(true == "hello"); // true
alert(isNaN(NaN)); // true
alert(isNaN(10)); // false(10是一个数值)
alert(isNaN("10")); // false(可以转成数值10)
alert(isNaN("blue")); // true(不能转成数值)
alert(isNaN(true)); // false(可以转成数值1)
数值转换
有 3 个函数可以把非数值转换为数值:Number()、parseInt()和parseFloat()。第一个函数, 即转型函数 Number()可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。这 3 个 函数对于同样的输入会有返回不同的结果。
Number()函数的转换规则如下。
如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0。 如果是数字值,只是简单的传入和返回。
如果是 null 值,返回 0。
如果是 undefined,返回 NaN。
如果是字符串,遵循下列规则:
如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1" 会变成 1,“123"会变成 123,而"011"会变成 11(注意:前导的零被忽略了);
如果字符串中包含有效的浮点格式,如"1.1”,则将其转换为对应的浮点数值(同样,也会忽 略前导零);
如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整 数值;
如果字符串是空的(不包含任何字符),则将其转换为 0;
如果字符串中包含除上述格式之外的字符,则将其转换为 NaN。
如果是对象,则调用对象的 valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是 NaN,则调用对象的 toString()方法,然后再次依照前面的规则转换返回的字符串值。
根据这么多的规则使用 Number()把各种数据类型转换为数值确实有点复杂。下面还是给出几个具体的例子吧。
alert(Number("Hello")); // NaN
alert(Number("")); // 0
alert(Number("00000001")); // 1
alert(Number(true)); // 1
parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字 符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt() 就会返回 NaN;也就是说,用 parseInt()转换空字符串会返回 NaN(Number()对空字符返回 0)。如 果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了 一个非数字字符。例如,"1234blue"会被转换为 1234,因为"blue"会被完全忽略。类似地,“22.5” 4 会被转换为 22,因为小数点并不是有效的数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的 十进制、八进制和十六进制数)。也就是说,如果字符串以"0x"开头且后跟数字字符,就会将其当作一 5 个十六进制整数;如果字符串以"0"开头且后跟数字字符,则会将其当作一个八进制数来解析。 为了更好地理解 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()解析像八进制字面量的字符串时,ECMAScript 3 和 5 存在分歧。例如: //ECMAScript 3 认为是 56(八进制),ECMAScript 5 认为是 70(十进制)
var num = parseInt("070");
在 ECMAScript 3 JavaScript 引擎中,“070"被当成八进制字面量,因此转换后的值是十进制的 56。 而在 ECMAScript 5 JavaScript 引擎中,parseInt()已经不具有解析八进制值的能力,因此前导的零会 被认为无效,将这个值当成"70”,结果就得到十进制的 70。在 ECMAScript 5 中,即使是在非严格模式下也会如此。
为了消除在使用 parseInt()函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换 时使用的基数(即多少进制)。
var num1 = parseInt("0xAF", 16); // 175
var num2 = parseInt("AF", 16); // 175
var num3 = parseInt("AF"); // NAN
var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10(按十进制解析)
var num4 = parseInt("10", 16); //16(按十六进制解析)
不指定基数意味着让 parseInt()决定如何解析输入的字符串,因此为了避免错误的解析,我们建 议无论在什么情况下都明确指定基数。
与 parseInt()函数类似,parseFloat()也是从第一个字符(位置 0)开始解析每个字符。而且 也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。也就是说,字符串中的第 一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。举例来说, "22.34.5"将会被转换为 22.34。
除了第一个小数点有效之外,parseFloat()与 parseInt()的第二个区别在于它始终都会忽略前导 的零。parseFloat()可以识别前面讨论过的所有浮点数值格式,也包括十进制整数格式。但十六进制格 式的字符串则始终会被转换成 0。由于 parseFloat()只解析十进制值,因此它没有用第二个参数指定基 数的用法。最后还要注意一点:如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后 都是零),parseFloat()会返回整数。以下是使用 parseFloat()转换数值的几个典型示例。
var num1 = parseFloat("1234blue"); // 1234
var num2 = parseFloat("0xA"); // 0
var num3 = parseFloat("22.5"); // 22.5
var num4 = parseFloat("22.34.5"); // 22.34
var num5 = parseFloat("0908.5"); // 908.5
var num6 = parseFloat("3.125e7"); // 31250000
3.4.6 String类型
String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。字符串可以由双引号(")或单引号(’)表示,因此下面两种字符串的写法都是有效的:
var firstName = "Nicholas";
var lastName = 'Zakas';
字符字面量
String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其 他用途的字符。这些字符字面量如下表所示:
var text = "This is the letter sigma: \u03a3.";
这个例子中的变量 text 有 28 个字符,其中 6 个字符长的转义序列表示 1 个字符。
任何字符串的长度都可以通过访问其 length 属性取得,例如:
alert(text.length); // 输出28
这个属性返回的字符数包括 16 位字符的数目。如果字符串中包含双字节字符,那么 length 属性 可能不会精确地返回字符串中的字符数目。
字符串的特点
ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变 某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量, 例如:
var lang = "Java";
lang = lang + "Script";
以上示例中的变量 lang 开始时包含字符串"Java"。而第二行代码把 lang 的值重新定义为"Java" 与"Script"的组合,即"JavaScript"。实现这个操作的过程如下:首先创建一个能容纳 10 个字符的 新字符串,然后在这个字符串中填充“Java”和"Script",最后一步是销毁原来的字符串"Java"和字 符串"Script",因为这两个字符串已经没用了。这个过程是在后台发生的,而这也是在某些旧版本的浏览器(例如版本低于 1.0 的 Firefox、IE6 等)中拼接字符串时速度很慢的原因所在。但这些浏览器后 来的版本已经解决了这个低效率问题。
转换为字符串
要把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的 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()函数遵循下列转换规则:
如果值有 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"
3.4.7 Object类型
var o = new Object();
var o = new Object; // 有效,但不推荐省略圆括号
在 ECMAScript 中, (就像 Java 中的 java.lang.Object 对象一样)Object 类型是所有它的实例的基础。换句话说,Object 类型所具有的任何属性和方法也同样存在于更具体的对象中。
Object 的每个实例都具有下列属性和方法。
constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor) 8 就是 Object()。
hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例 的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例 如:o.hasOwnProperty(“name”))。
isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原 型)。
propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句 (本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符
串形式指定。
toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
toString():返回对象的字符串表示。 valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。
3.5 操作符
ECMA-262 描述了一组用于操作数据值的操作符,包括算术操作符(如加号和减号)、位操作符、 关系操作符和相等操作符。ECMAScript 操作符的与众不同之处在于,它们能够适用于很多值,例如字 符串、数字值、布尔值,甚至对象。不过,在应用于对象时,相应的操作符通常都会调用对象的 valueOf() 和(或)toString()方法,以便取得可以操作的值。
3.5.1 一元操作符
前置和后置的++,–,所有这 4 个操作符对任何值都适用,也就是它们不仅适用于整数,还可以用于字符串、布尔值、浮 点数值和对象。在应用于不同的值时,递增和递减操作符遵循下列规则。
在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 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.100
o--; // 值变成数值-2
一元加和减操作符
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;
IncrementDecrementExample04.htm
} };
s1 = +s1; // 值变成数值 1
s2 = +s2; // 值变成数值 1.1
s3 = +s3; // 值变成 NaN
b= +b; // 值变成数值 0
f= +f; // 值未变,仍然是 1.1
o= +o; // 值变成数值-1
var num = 25;
num = -num; // 变成了-25
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 位操作符
位操作符用于在最基本的层次上,即按内存中表示数值的位来操作数值。ECMAScript 中的所有数 值都以 IEEE-754 64 位格式存储,但位操作符并不直接操作 64 位的值。而是先将 64 位的值转换成 32 位 的整数,然后执行操作,最后再将结果转换回 64 位。对于开发人员来说,由于 64 位存储格式是透明的, 因此整个过程就像是只存在 32 位的整数一样。
对于有符号的整数,32 位中的前 31 位用于表示整数的值。第 32 位用于表示数值的符号:0 表示正 数,1 表示负数。这个表示符号的位叫做符号位,符号位的值决定了其他位数值的格式。其中,正数以 纯二进制格式存储,31 位中的每一位都表示 2 的幂。第一位(叫做位 0)表示 20,第二位表示 21,以此 9 类推。没有用到的位以 0 填充,即忽略不计。例如,数值 18 的二进制表示是 00000000000000000000000000010010,或者更简洁的 10010。这是 5 个有效位,这 5 位本身就决定了实 际的值(如图 3-1 所示)。
负数同样以二进制码存储,但使用的格式是二进制补码。计算一个数值的二进制补码,需要经过下 列 3 个步骤:
ECMAScript 会尽力向我们隐藏所有这些信息。换句话说,在以二进制字符串形式输出一个负数时, 我们看到的只是这个负数绝对值的二进制码前面加上了一个负号。如下面的例子所示:
var num = -18;
alert(num.toString(2)); // "-10010"
要把数值18 转换成二进制字符串时,得到的结果是"-10010"。这说明转换过程理解了二进制补 码并将其以更合乎逻辑的形式展示了出来。
在 ECMAScript 中,当对数值应用位操作符时,后台会发生如下转换过程:64 位的数值被转换成 32 位数值,然后执行位操作,最后再将 32 位的结果转换回 64 位数值。这样,表面上看起来就好像是在操 作 32 位数值,就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重 的副效应,即在对特殊的 NaN 和 Infinity 值应用位操作时,这两个值都会被当成 0 来处理。
如果对非数值应用位操作符,会先使用 Number()函数将该值转换为一个数值(自动完成),然后 再应用位操作。得到的结果将是一个数值。
位运算符号:&, |, ~, ^, <<, >>, >>>(无符号右移,左边的位用0填充)。
3.5.3 布尔操作符
逻辑非(!)
无论这个值是什么数据 类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再 对其求反。也就是说,逻辑非操作符遵循下列规则:
如果操作数是一个对象,返回 false;
如果操作数是一个空字符串,返回 true;
如果操作数是一个非空字符串,返回 false;
如果操作数是数值 0,返回 true;
如果操作数是任意非 0 数值(包括 Infinity),返回 false;
如果操作数是 null,返回 true;
如果操作数是 NaN,返回 true;
如果操作数是 undefined,返回 true。
逻辑与(&&)
逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。
逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况 下,逻辑与操作就不一定返回布尔值;此时,它遵循下列规则:
如果第一个操作数是对象,则返回第二个操作数;
如果第二个操作数是对象,则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象;
如果两个操作数都是对象,则返回第二个操作数;
如果有一个操作数是 null,则返回 null;
如果有一个操作数是 NaN,则返回 NaN;
如果有一个操作数是 undefined,则返回 undefined。
var found = true;
var result = (found && someUndefinedVariable); // someUndefinedVariable为定义,出错
alert(result); // 这一行不会执行
var found = false;
var result = (found && someUndefinedVariable); alert(result); // 会执行("false")
逻辑或(||)
与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值;此时,它遵循下 列规则:
如果第一个操作数是对象,则返回第一个操作数;
如果第一个操作数的求值结果为 false,则返回第二个操作数; 如果两个操作数都是对象,则返回第一个操作数;
如果两个操作数都是 null,则返回 null;
如果两个操作数都是 NaN,则返回 NaN;
如果两个操作数都是 undefined,则返回 undefined。
与逻辑与操作符相似,逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为 true,就不会对第二个操作数求值了。下面看一个例子:
var found = true;
var result = (found || someUndefinedVariable); // 不会发生错误 alert(result); // 会执行("true")
var found = false;
var result = (found || someUndefinedVariable); // 这里会发生错误
alert(result); // 这一行不会执行
3.5.4 乘性操作符
如果参与乘性计算的某 个操作数不是数值,后台会先使用 Number()转型函数将其转换为数值。也就是说,空字符串将被当作 0,布尔值 true 将被当作 1。
乘法(*)
在处理特殊值的情况下,乘法操作符遵循下列特殊的规则:
如果操作数都是数值,执行常规的乘法计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果乘积超过了 ECMAScript 数值的表示范围,则返回 Infinity 或-Infinity;
如果有一个操作数是 NaN,则结果是 NaN;
如果是 Infinity 与 0 相乘,则结果是 NaN;
如果是 Infinity 与非 0 数值相乘,则结果是 Infinity 或-Infinity,取决于有符号操作数的符号;
如果是 Infinity 与 Infinity 相乘,则结果是 Infinity;
如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值,然后再应用上面的规则。
除法(/)
如果操作数都是数值,执行常规的除法计算,即两个正数或两个负数相除的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果商超过了 ECMAScript 数值的表示范围,则返回 Infinity 或-Infinity;
如果有一个操作数是 NaN,则结果是 NaN;
如果是 Infinity 被 Infinity 除,则结果是 NaN;
如果是零被零除,则结果是 NaN;
如果是非零的有限数被零除,则结果是 Infinity 或-Infinity,取决于有符号操作数的符号;
如果是 Infinity 被任何非零数值除,则结果是 Infinity 或-Infinity,取决于有符号操作数的符号;
如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值,然后再应用上面的规则。
求模(%)
求模操作符会遵循下列特殊规则来处理特殊的值: 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
如果被除数是无穷大值而除数是有限大的数值,则结果是 NaN;
如果被除数是有限大的数值而除数是零,则结果是 NaN;
如果是 Infinity 被 Infinity 除,则结果是 NaN;
如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
如果被除数是零,则结果是零;
如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值,然后再应用上面的规则。
3.5.5 加性操作符
加法(+)
如果两个操作符都是数值,执行常规的加法计算,然后根据下列规则返回结果:
如果有一个操作数是 NaN,则结果是 NaN;
如果是 Infinity 加 Infinity,则结果是 Infinity;
如果是-Infinity 加-Infinity,则结果是-Infinity;
如果是 Infinity 加-Infinity,则结果是 NaN; 如果是+0 加+0,则结果是+0;
如果是0 加0,则结果是0;
如果是+0 加0,则结果是+0。
不过,如果有一个操作数是字符串,那么就要应用如下规则:
如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来;
如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。
如果有一个操作数是对象、数值或布尔值,则调用它们的 toString()方法取得相应的字符串值,然后再应用前面关于字符串的规则。对于 undefined 和 null,则分别调用 String()函数并取得字符 串"undefined"和"null"。
var result2 = 5 + "5"; // 一个数值和一个字符串相加 (第一个操作数也被转换成了"5")
alert(result2); // "55"
减法(-)
ECMAScript 中的减法操作符在处理各种数据类型转换时,同样需要遵循一些 特殊规则,如下所示:
如果两个操作符都是数值,则执行常规的算术减法操作并返回结果; 如果有一个操作数是 NaN,则结果是 NaN;
如果是 Infinity 减 Infinity,则结果是 NaN;
如果是-Infinity 减-Infinity,则结果是 NaN;
如果是 Infinity 减-Infinity,则结果是 Infinity;
如果是-Infinity 减Infinity,则结果是-Infinity;
如果是+0 减+0,则结果是+0;
如果是+0 减0,则结果是-0;
如果是0 减0,则结果是+0;
如果有一个操作数是字符串、布尔值、null 或 undefined,则先在后台调用 Number()函数将其转换为数值,然后再根据前面的规则执行减法计算。如果转换的结果是 NaN,则减法的结果就是 NaN;
如果有一个操作数是对象,则调用对象的 valueOf()方法以取得表示该对象的数值。如果得到的值是 NaN,则减法的结果就是 NaN。如果对象没有 valueOf()方法,则调用其 toString()方法并将得到的字符串转换为数值。
3.5.6 关系操作符
小于(<)、大于(>)、小于等于(<=)和大于等于(>=)。这几个操作符都返回一个布尔值,如下面的例子所示:
var result1 = 5 > 3; //true
var result2 = 5 < 3; //false
与 ECMAScript 中的其他操作符一样,当关系操作符的操作数使用了非数值时,也要进行数据转换 或完成某些奇怪的操作。以下就是相应的规则。
如果两个操作数都是数值,则执行数值比较。
如果两个操作数都是字符串,则比较两个字符串对应的字符编码值。
如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
如果一个操作数是对象,则调用这个对象的 valueOf()方法,用得到的结果按照前面的规则执行比较。如果对象没有 valueOf()方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。
如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。
在比较字符串时,实际比较的是两个字符串中对应位置的每个字符的字符编码值。经过这么一番比较之后,再返回一个布尔值。
var result = "Brick" < "alphabet"; //true
var result = "Brick".toLowerCase() < "alphabet".toLowerCase(); //false
var result = "23" < 3; //false
var result = "23" < "3"; //true
var result = "a" < 3; // false,因为"a"被转换成了 NaN
var result1 = NaN < 3; //false
var result2 = NaN >= 3; //false
3.5.7 相等操作符
在涉及到对象的比较时,相等和不相等——先转换再比较,全等和不 全等——仅比较而不转换。
相等(==)和不相等(!=)
在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:
如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false 转换为 0,而true 转换为 1;
如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法,用得到的基本类型值按照前面的规则进行比较;这两个操作符在进行比较时则要遵循下列规则。
null 和 undefined 是相等的。
要比较相等性之前,不能将 null 和 undefined 转换成其他任何值。
如果有一个操作数是 NaN,则相等操作符返回 false,而不相等操作符返回 true。重要提示:即使两个操作数都是 NaN,相等操作符也返回 false;因为按照规则,NaN 不等于 NaN。
如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true;否则,返回 false。
下表列出了一些特殊情况及比较结果:
全等(===
)和不全等(!===
)
var result1 = ("55" == 55); //true,因为转换后相等
var result2 = ("55" === 55); //false,因为不同的数据类型不相等
var result1 = ("55" != 55); //false,因为转换后相等
var result2 = ("55" !== 55); //true,因为不同的数据类型不相等
记住:null == undefined
会返回 true,因为它们是类似的值;但 null === undefined
会返回 false,因为它们是不同类型的值。
3.5.8 条件操作符
和java里的条件操作符用法相同。
variable = boolean_expression ? true_value : false_value;
3.5.9 赋值操作符(=)
每个主要算术操作符(以及个别的其他操作符)都有对应的复合赋值操作符。这些操作符如下所示:
乘/赋值(*=);
除/赋值(/=);
模/赋值(%=);
加/赋值(+=);
减/赋值(=);
左移/赋值(<<=);
有符号右移/赋值(>>=);
无符号右移/赋值(>>>=)。
设计这些操作符的主要目的就是简化赋值操作。使用它们不会带来任何性能的提升。
3.5.10 逗号操作符
逗号操作符多用于声明多个变量:
var num1=1, num2=2, num3=3;
逗号操作符还可以用于赋值。在用于赋值时,逗号 操作符总会返回表达式中的最后一项,如下面的例子所示:
var num = (5, 1, 4, 8, 0); // num的值为0
3.6 语句
3.6.1 if语句
3.6.2 do-while语句
3.6.3 while语句
3.6.4 for语句
上面几种语句用法同c语言中的对应语句。但判断条件可以是任意表达式, ECMAScript 会自动调用 Boolean()转换函数将这个表达式的结果转换为一个布尔值。
// ECMAScript 中不存在块级作用 域(第 4 章将进一步讨论这一点),因此在循环内部定义的变量也可以在外部访问到。
var count = 10;
for (var i = 0; i < count; i++){
alert(i);
}
alert(i); //10()
3.6.5 for-in语句
for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。
for (var propName in window) {
document.write(propName);
}
在这个例子中,我们使用 for-in 循环来显示了 BOM 中 window 对象的所有属性。ECMAScript 对象的属性没有顺序。因此,通过 for-in 循环输出的属性名的顺序是不可预测的。 具体来讲,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。
但是,如果表示要迭代的对象的变量值为 null 或 undefined,for-in 语句会抛出错误。 ECMAScript 5 更正了这一行为;对这种情况不再抛出错误,而只是不执行循环体。为了保证最大限度的兼容性,建议在使用 for-in 循环之前,先检测确认该对象的值不是 null 或 undefined。
3.6.6 label语句
使用 label 语句可以在代码中添加标签,以便将来使用。以下是 label 语句的语法: label: statement
下面是一个示例:
start: for (var i=0; i < count; i++) {
alert(i);
}
这个例子中定义的 start 标签可以在将来由 break 或 continue 语句引用。加标签的语句一般都要与 for 语句等循环语句配合使用。
3.6.7 break和continue语句
用法同c语言。
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++;
}
}
alert(num); //55
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost;
}
num++;
}
}
alert(num); //95
3.6.8 with语句
3.6.9 switch语句
虽然 ECMAScript 中的 switch 语句借鉴自其他语言,但这个语句也有自己的特色。首先,可以在 switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有 问题。其次,每个 case 的值不一定是常量,可以是变量,甚至是表达式。请看下面这个例子:
switch ("hello world") {
case "hello" + " world":
alert("Greeting was found.");
break;
case "goodbye":
alert("Closing was found.");
break;
default:
alert("Unexpected message was found.");
}
var num = 25;
switch (true) {
case num < 0:
alert("Less than 0.");
break;
case num >= 0 && num <= 10:
alert("Between 0 and 10.");
break;
case num > 10 && num <= 20:
alert("Between 10 and 20.");
break;
default:
alert("More than 20.");
}
3.7 函数
ECMAScript 中的函数使用 function 关键字来声明,后跟一组参数以及函数体。 函数的基本语法如下所示:
function functionName(arg0, arg1,...,argN) {
statements
}
ECMAScript 中的函数在定义时不必指定是否返回值。实际上,任何函数在任何时候都可以通过 return 语句后跟要返回的值来实现返回值。
function sum(num1,num2){
return num1 + num2;
}
var ret = sum(5, 10);
alert(ret);
3.7.1 理解参数
ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript函数不介意传递进 来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数, 在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不 会有什么怨言。之所以会这样,原因是 ECMAScript 中的参数在内部是用一个数组来表示的。函数接收 到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任 何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过 arguments 对象来 访问这个参数数组,从而获取传递给函数的每一个参数。
其实,arguments 对象只是与数组类似(它并不是 Array 的实例),因为可以使用方括号语法访 问它的每一个元素(即第一个元素是 arguments[0],第二个元素是 argumetns[1],以此类推),使 用 length 属性来确定传递进来多少个参数。
下面两个实现了同样但功能:
function sayHi(name, message) {
alert("Hello " + name + "," + message);
}
sayHi("Nicholas", "how are you today?");
function sayHi() {
alert("Hello " + arguments[0] + "," + arguments[1]);
}
sayHi("Nicholas", "how are you today?");
通过访问 arguments 对象的 length 属性可以获知有多少个参数传递给了函数。下面这个函数会 在每次被调用时,输出传入其中的参数个数:
function howManyArgs() {
alert(arguments.length);
}
howManyArgs("string", 45); //2
howManyArgs(); //0
howManyArgs(12); //1
每次执行下面这个 doAdd()函数都会重写第二个参数,将第二个参数的值修改为 10。因为 arguments 对象中的值会自动反映到对应的命名参数,所以修改 arguments[1],也就修改了 num2,结果它们的 值都会变成 10。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但 它们的值会同步。另外还要记住,如果只传入了一个参数,那么为 arguments[1]设置的值不会反应到 命名参数中。这是因为 arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名 参数的个数决定的。
function doAdd(num1, num2) {
arguments[1] = 10;
alert(arguments[0] + num2);
}
关于参数还要记住最后一点:没有传递值的命名参数将自动被赋予 undefined 值。这就跟定义了 变量但又没有初始化一样。例如,如果只给 doAdd()函数传递了一个参数,则 num2 中就会保存 undefined 值。
严格模式对如何使用 arguments 对象做出了一些限制。首先,像前面例子中那样的赋值会变得无 效。也就是说,即使把 arguments[1]设置为 10,num2 的值仍然还是 undefined。其次,重写 arguments 的值会导致语法错误(代码将不会执行)。
3.7.2 没有重载
如果在 ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数。请看下面的例子:
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100);//300