需要了解的基本内容:

  • 语法
  • 数据类型
  • 流控制语句

本章将主要按照第3版定义的ECMAScript介绍这门语言的基本概念,并就第5版的变化给出说明。

  • 3.1 语法

    • 3.1.1 区分大小写
    • 3.1.2 标识符
    • 3.1.3注释
    • 3.1.4 严格模式
    • 3.1.5 语句
  • 3.2 关键字和保留字
  • 3.3 变量
  • 3.4 数据类型
    • 3.4.1 typeof 操作符
    • 3.4.2 Undefined类型
    • 3.4.3 Null类型
    • 3.4.4 Boolean类型
    • 3.4.5 Number类型
    • 3.4.6 String类型
    • 3.5.2 位操作符
    • 3.5.3 布尔操作符
    • 3.5.4 乘性操作符
    • 3.5.5 加性操作符
    • 3.5.6 关系操作符
    • 3.5.7 相等操作符
    • 3.5.8 条件操作符
    • 3.5.9 赋值操作符
    • 3.5.10 逗号操作符
  • 3.6 语句
    • 3.6.1 if语句
    • 3.6.2 do-while语句
    • 3.6.3 while语句
    • 3.6.4 for语句
    • 3.6.5 for-in语句
    • 3.6.6 label语句
    • 3.6.7 break和continue语句
    • 3.6.8 with语句
    • 3.6.9 switch语句
  • 3.7 函数
    • 3.7.1 理解参数
    • 3.7.2 没有重载
  • 3.8 小结

3.1 语法

ECMAScript的语法大量借鉴了C及其他类C语言(如Java和Perl)的语法。

3.1.1 区分大小写

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

3.1.2 标识符

标识符,就是指变量、函数、属性的名字,或者函数的参数。规则如下:

  • 第一个字符必须是一个字母、下划线(_)或一个美元符号($)
  • 其他字符可以是字母、下划线、美元符号或数字。

标识符中的字母也可以包含扩展的ASCII或Unicode字母字符,但我们不推荐这么做。
安装惯例,ECMAScript标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个有意义的单词的首字母大写,例如:firstSecond、myCar、doSomethingImportant
虽然没有强制要求必须采用这种格式,但为了与ECMAScript内置的函数和对象命名格式保持一致,可以将其当做一种最佳实践。

不能把关键字、保留字、true、false、null用作标识符。

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";
//函数体
}

严格模式下,JavaScript的执行结果会有很大不同。支持严格模式的浏览器包括IE10+、Firefox 4+、Safari 5.1+、Opera 12+和Chrome。

3.1.5 语句

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

var sum = a + b    //及时没有分好也是有效的语句 -- 不推荐
var diff = a - b;     //有效的语句 -- 推荐

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

if (test) {test = false;alert(test);
}   

虽然条件控制语句(如if语句)只在执行多条语句的情况下才要求使用代码块,但最佳实践是始终在控制语句中使用代码块–即使代码块中只有一条语句,例如:

if (test) alert(test);    // 有效单容易出错,不要使用
if (test){               // 推荐使用alert(test);
}

在控制语句中使用代码块可以让编码意图更加清晰,而且也能降低修改代码时出错的几率。

3.2 关键字和保留字

ECMA-262描述了一组具有特定用途的关键字,这些关键字用于表示控制语句的开始或结束,或者用于执行特定操作等。按照规则,关键字也是语言保留的,不能用作标识符。一下就是ECMAScript的全部关键字(带*号上标的是第5版新增的关键字):

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

ECMA-26还描述了另外一组不能用作标识符的保留字。尽管保留字在这门语言中还没有任何特定的用途,但它们有可能在将来被用作关键字。一下是ECMA-262第3版定义的全部保留字:

abstract          enum          int           short
boolean           export        interface     static
byte              extenda       long          super
char              final         native        synchronized
class             float         package       throws
const             goto          private       transient
debugger          implements    protected     volatile
double            import        public

第5版把在非严格模式下运行时的保留字缩减为下列这些:

class            enum            extends            super
const            export           import

在严格模式下,第5版还对以下保留字施加了限制:

implements            package            public
interface             private            static
let                   protected          yield

注意,let和yield是第5版新增的保留字,其他保留字都是第3版定义的。为了最大程度地保证兼容性,建议将第3版定义的保留字外加let和yield作为编程时的参考。
在实现ECMAScript 3的JavaScript引擎中使用关键字作标识符,会导致“Identifier Expected”错误。而使用保留字作为标识符可能会也可能不会导致相同的错误,具体取决于特定的引擎。
第5版对使用关键字和保留字的规则进行了少许修改。关键字和保留字虽然仍然不能作为标识符使用,但现在可以用作对象的属性名。一般来说,最好都不要使用关键字和保留字作为标识符和属性名,以便与将来的ECMAScript版本兼容。
ECMA-262第5版还对eval和arguments还施加了限制。在严格模式下,这两个名字也不能作为标识符或属性名,否则会抛出错误。

3.3 变量

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

var message;

这行代码定义了一个名为message的变量,该变量可以用来保存任何值(像这样未经过初始化的变量,会保存一个特殊的值–undefined)。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()函数,这个变量就有了定义,就可以在函数外部的任何地方被访问到。

虽然省略var操作符可以定义全局变量,但这也不是我们推荐的做法。因为在局部作用域中定义的全局变量很难维护,而且如果有意地忽略了var操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱。给未经声明的变量赋值在严格模式下会导致抛出ReferenceError错误。
可以用一条语句定义多个变量,只要像下面这样把每个变量(初始化或不初始化均可)用逗号分隔开即可:

var message = "hi",found = false,age = 29;

这个例子定义并初始化了3个变量。同样由于ECMAScript是松散类型的,因而使用不同类型初始化变量的操作可以放在一条语句中来完成。虽然代码里的换行和变量缩进不是必需的,但这样做可以提高可读性。
在严格模式下,不能定义名为eval或arguments的变量,否则会导致语法错误。

3.4 数据类型

ECMAScript中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String。还有一种复杂数据类型–Object,本质上是由一组无序的名值对组成的。ECMAScript不支持任何创建自定义类型的机制,而所有值最终都将是上述6种数据类型之一。

3.4.1 typeof 操作符

鉴于ECMAScript是松散类型的,因此需要一种手段来检测给定变量的数据类型 – typeof就是负责提供这方面信息的操作符。对一个值使用typeof操作符可能返回下列某个字符串:
- undefined – 如果这个值未定义
- boolean – 如果这个值是布尔值
- string – 如果这个值是字符串
- number – 如果这个值是数值
- object – 如果这个值是对象或null
- function – 如果这个值是函数
下面是几个使用typeof操作符的例子:

var message = “some string”;
alert(typeof message);     // string
alert(typeof(message));     // string
alert(typeof 95);     // number

这几个例子说明,typeof操作符的操作数可以是变量(message),也可以是数值字面量。注意,typeof是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必需的。
有些时候,typeof操作符会返回一些令人迷惑但技术上却正确的值。比如,调用typeof null会返回“object”,因为特殊值null被认为是一个空的对象引用。Safari 5及之前版本、Chrome 7及之前版本在对正则表达式调用typeof操作符时会返回“function”,而其他浏览器在这种情况下会返回object。

从技术角度讲,函数在ECMAScript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof操作符来区分函数和其他对象是有必要的。

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
结果表明,对未初始化和未声明的变量执行typeof操作符都返回了undefined值;这个结果有其逻辑上的合理性。因为虽然这两种变量从技术角度看有本质区别,但实际上无论对哪种变量也不可能执行真正的操作。

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

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.一下为变量赋Boolean类型值的例子:

var found = true;
var lost = false;

需要注意的是,Boolean类型的字面值true和false是区分大小写的。也就是说,True和False(以及其他的混合大小写形式)都不是Boolean值,只是标识符。
虽然Boolean类型的字面值只有两个,但ECMAScript中所有类型的值都有与这两个Boolean值等价的值。要将一个值转换为其对于的Boolean值,可以调用转型函数Boolean(),如下例所示:

var message = "Hello World";
var messageAsBoolean = Boolean(message);

在这个例子中,字符串message被转换成了一个Boolean值,该值被保存在messageAsBoolean变量中。可以对任何数据类型的值调用Boolean()函数,而且总会返回一个Boolean值。至于返回的这个值是true还是false,取决于要转换值的数据类型及其实际值。
下表给出了各种数据类型及其对应的转换规则:

数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 “”(空字符串)
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined n/a undefined

这些转换规则对理解流控制语句(如if语句)自动执行相应的Boolean转换非常重要,如下例所示:

var message = "Hello World!";
if (message) {alert("Value is true");
}

运行这个示例,就会显示一个警示框,因为字符串message被自动转换成了对应的Boolean值(true)。由于存在这种自动执行的Boolean转换,因此确切地知道在流控制语句中使用的是什么变量至关重要。错误地使用一个对象而不是一个Boolean值,就有可能彻底改变应用程序的流程。

3.4.5 Number类型

Number类型应该是ECMAScript中最令人关注的数据类型了,这种类型使用IEEE754格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。为支持各种数值类型,ECMA-262定义了不同的数值字面量格式。
最基本的数值字面量格式是十进制整数,十进制整数可以像下面这样直接在代码中输入:
var intNum = 55; // 整数
除了以十进制表示外,整数还可以通过八进制(以8为基数)或十六进制(以16为基数)的字面值来表示。其中,八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7)。如果字面中的数值超过了范围,那么前导零将被忽略,后面的数值将被当做十进制数值解析。请看下面示例:
var octalNum1 = 070; // 八进制的56
var octalNum2 = 079; // 无效的八进制数值–解析为79
var octalNum3 = 08; // 无效的八进制数值–解析为8
八进制字面量在严格模式下是无效的,会导致支持的JavaScript引擎抛出错误。
十六进制字面值的前两位必须是0x,后跟任何十六进制数字(0~9及A~F)。其中,字面A~F可以大写,也可以小写。如下面的例子所示:
var hexNum1 = 0xA; // 十六进制的10
var hexNum2 = 0xlf; // 十六进制的31
在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。

基于JavaScript中保存数值的方式,可以保存正零(+0)和负零(-0)。正零和负零被认为相等,但为了读者更好地理解上下文,这里特别做此说明。

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

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

由于保存浮点数值需要的内容空间是保存正数值的两倍,因此ECMAScript会不失时机地将浮点数值转换为正数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以整数来保存。同样地,如果浮点数值本身表示的就是一个整数(如1.0),那么该值也会被转换为整数,如下例所示:

var floatNum1 = 1.;         // 小数点后面没有数字--解析为1
var floatNum2 = 10.0;     //整数--解析为10

对于那些极大或极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。用e表示法表示的数值等于e前面的数值乘以10的指数次幂。ECMAScript中e表示法的格式也是如此,即前面是一个数值(可以是整数也可以是浮点数)。中间是一个大写或小写的字母E,后面是10的幂中的指数,该幂值将用来与前面的数相乘。下面是一个使用e表示法表示数值的例子:

var floatNum = 3.125e7;   // 等于31250000

在这个例子中,使用e表示法表示的变量foatNum的形式虽然简洁,但它的实际值则是31250000。在此,e表示法的实际含义就是“3.125乘以10的7次方”。
也可以使用e表示法表示极小的数值,如0.0000003,这个数值可以使用更简洁的3e-17表示。在默认情况下,ECMAScript会将那些小数点后面带有6个零以上的浮点数值转换为e表示法表示的数值。
浮点数值的最高精度是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,那么测试将无法通过。因此,永远不要测试某个特定的浮点数值。

关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于IEEE754数值的浮点计算的通病,ECMAScript并非独此一家;其他使用相同数值格式的语言也存在这个问题。

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。

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

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

4.数值转换
有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()方法,然后再次依照前面的规则转换返回的字符串值。举例如下:
var num1 = Number("Hello World!");      // NaN
var num2 = Number(" ");                         // 0
var num3 = Number("000011");              // 11
var num4 = Number(true);                      // 1

一元加操作符的操作与Number()函数相同。

由于Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()函数。parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN,也就是说,用parseInt()转换空字符串会返回NaN(Number对空字符返回0)。如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。
例如:“1234blue”会被转换为1234,“22.5”会被转换为22,因为小数点并不是有效的数字字符。
如果字符串中的第一个字符数数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的十进制、八进制和十六进制数)。也就是说,如果字符串以“0x”开头且后面跟数字字符,就会将其当做一个十六进制整数,如果字符串以“0”开头且后跟数字字符,则会将其当做一个八进制数来解析。
举例如下:

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 认为是0(十进制)
var num = parseInt("070");

在ECMAScript JavaScript引擎中,“070”被当成八进制字面量,因此转换后的值是十进制的56。而在ECMAScript 5 JavaScript引擎中,parseInt()已经不具有解析八进制值的能力,因此前导的零视为无效。ECMAScript 5的严格模式下依旧如此。
parseInt()函数提供第二个参数,转换时使用的基数(多少进制)。举例如下:

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

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

var num1 = parseInt("AF", 16)            //175
var num1 = 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  (按十六进制解析)

多数情况下,我们要解析的都是十进制数值,因此始终将10作为第二个参数是非常必要的。

parseFloat()也是从第一个字符(位置0)开始解析每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。也就是说,字符串的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。举例如下:

“22.34.5”,被转换为22.34

除了第一个小数点有效外,parseFloat()与parseInt()的第二个区别在于它始终都会忽略前导的零。parseFloat()可以识别前面讨论过的所有浮点数值格式,也包括十进制整数格式。但十六进制格式的字符串则始终被转换为0,。由于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字符组成的字符序列,即字符串。字符串可以由双引号 “ 或单引号 ’ 表示。
与PHP中的双引号和单引号会影响对字符串的解释方式不同,ECMAScript中的这两种语法形式没有什么区别。
1. 字符字面量
String数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其他用途的字符。如下所示:

字面量 含义
\n 换行
\t 制表
\b 空格
\r 回车
\f 进纸
\ 斜杠
\’ 单引号,在用单引号表示的字符串中使用
\” 双引号,在用双引号表示的字符串终使用
\xnn 以十六进制代码nn表示的一个字符(其中n为0~F)。例如,\x41表示“A”
\unnn 以十六进制代码nnn表示的一个Unicode字符(其中n为0~F)。
例如,\u03a3表示希腊字符∑

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

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()方法,返回响应值的字符串表现。举例如下:

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

默认的无参数输出值与指定基数10时的输出值相同。
在不知道要转换的值是不是null或undefined的情况下,还可以使用转型函数String(),这个函数能够将任何类型的值转换为字符串。String()函数遵循系列转换规则:
- 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果;
- 如果值是null,则返回”null”
- 如果值是undefined,则返回”undefined”
举例如下:

var value1 = 10;
var value2 = true;
var value3 = null;
var value4l
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.4.7 Object类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象类型的名称来创建。而创建Object类型的实例并为其添加属性和(或)方法,就可以创建自定义对象,如下所示:

var o = new Object();

这个语法与Java中创建对象的语法相似,但在ECMAScript中,如果不给构造函数传递参数,则可以省略后面的那一堆圆括号,举例如下:

var o = new Object;      //有效,但不推荐

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

  • Constructor:保存着用于创建当前对象的函数。对应前面的例子而言,构造函数(constructor)就是Object()。
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty(“name”))。
  • isPrototypeOf(object):用于检查传入的对象是否是另一个对象的原型
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够适应for-in语句来枚举。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。
  • toLocalString():返回对象的字符串表示,该字符串与执行环境的地区对应。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。
    由于在ECMAScript中Object是所有对象的基础,因此所有对象都具有这些基本的属性和方法。

    从技术角度讲,ECMA-262中对象的行为不一定适用于JavaScript中的其他对象。浏览器环境中的对象,比如BOM和DOM中的对象,都属于宿主对象,因为它们是由宿主实现提供和定义的。ECMA-262不负责定义宿主对象,因此宿主对象可能会也可能不会继承Object。

3.5 操作符
ECMA-262描述了一组用于操作数据值的操作符,包括算术操作符(如加号和减号)、位操作符、关系操作符和相等操作符。ECMAScript操作符的不同之处在于它们能够适用于很多值,例如字符串、数字值、布尔值、甚至对象。当应用于对象时,相应的操作符通常会调用对象的valueOf()和(或)toString()方法,以便取得可以操作的值。
3.5.1 一元操作符
只能操作一个值的操作符叫做一元操作符。
1. 递增和递减操作符
分为前置型和后置型。前置型位于要操作的变量之前,后置型位于要操作的变量之后。
前置递增操作举例如下:

var age = 29;
++age;

在这个例子中,前置递增操作符把age的值变成了30。等同如下: age = age + 1; 前置递减操作举例如下:

var age = 29;
--age;

变量age的值减少为28,执行前置递增和递减操作是,变量的值都是在语句被求值以前改变的。(在计算机科学领域,这种情况通常被称作副效应。)举例如下:

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

这个例子中变量anotherAge的初始值等于变量age的前置递减之后加2。由于先执行了减法操作,age的值变成了28,所以再加上2就是30。 由于前置递增和递减操作与执行语句的优先级相等,因此整个语句会从左至右被求值。举例如下:

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

在这里,num3等于21是因为num1减去1才与num2相加。num4等于21是因为使用的num1减去1之后的值。 后置型递增和递减操作符的语法不变(仍然是++和–),只不过要放在变量后面而不是前面。后者递增和递减与前置递增和递减有一个非常重要的区别,即递增和递减操作是在包含它们的语句被求值之后才执行的。这个区别在某些情况下不是什么问题,但是,当语句中还包含其他操作时,上述区别就会非常明显了,举例如下:

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

这里num3里的num1是原始值,而num4里的num1是递减后的值。 这里的4个操作符对任何值都适用,也就是它们不仅适用于整数,还可以用于字符串、布尔值、浮点数值和对象。在应用于不同的值是,递减和递减操作符合下列规则:

  • 应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减1的操作。字符串变量变成数值变量。
  • 在应用于布尔值false时,先将其转换为0再执行加减1的操作。布尔值变量变成数值变量。
  • 在应用于布尔值true时,先将其转换为1再执行加减1的操作。布尔值变量变成数值变量。
  • 在应用于浮点数值时,执行加减1的操作。
  • 在应用于对象时,先调用对象的valueOf()方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是NaN,则在调用toString()方法后再应用前述规则。对象变量变成数值变量。举例如下:
var s1 = "2";
var s1 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function() {return -1;
}
};
s1++;        //值变成数值3
s2++;        //只变成NaN
b++;         //值变成数值1
f--;         //值变成0.1000000000000000009(由于浮点舍入错误所致)
o--;         //值变成数值-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

一元减操作符注意用于表示负数,例如将1转换成-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)表示2º,第二位表示2¹,以此类推。没有用到的位以0填充,即忽略不计。例如,数值18的二进制表示000000000000000000000010010,或者简洁的10010。这是5个有效位,这5位本身就决定了实际的值

1 0 0 1 0
(2^4x1)+(2³x0)+(2²x0)+(2¹x1)+(2°x0)
16 +0 +0 +2 +0
18

负数同样以二进制码存储,但使用的格式是二进制补码。计算一个数值的二进制补码,需要经过下列3个步骤:
(1)求这个数值绝对值的二进制码(例如,要求-18的二进制补码,先求18的二进制码)
(2)求二进制反码,即将0替换为1,将1替换为0;
(3)得到的二进制反码加1.
要根据这3个步骤求得-18的二进制码,首先要求得18的二进制码,即:
0000 0000 0000 0000 0000 0000 0001 0010
然后,求其二进制反码,即0和1互换:
1111 1111 1111 1111 1111 1111 1110 1101
最后,二进制反码加1:

这样就是-18的二进制表示,注意的是,在处理有符号整数时,是不能访问位31的。
ECMAScript会尽力向我们隐藏所有这些信息。换句话说,以二进制字符串形式输出一个负数时,我们看到的只是这个负数绝对值的二进制码前面加上了一个负号。如下所示:

var num = -18;
alert(num.toString(2));           //-10010

要把数值-18转换成二进制字符串时,得到的结果是-10010。说明转换过程理解了二进制补码并将其以更合乎逻辑的形式展示了出来。

默认情况下,ECMAScript中的所有证书都是有符号整数。也存在无符号整数。对于无符号整数,第32位不再表示符号,因为无符号整数只能是正数。而且,无符号整数的值可以更大,因为多出的一位不再表示符号,可以用来表示数值。
在ECMAScript中,当对数值应用位操作符时,后台会发生如下转换过程:64位的数值被转换成32位数值,然后执行位操作,最后将32位的结果转换回64位数值。这样,表面上看起来是在操作32位数值,就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重的副效应,即在对特殊的NaN和Infinity值应用位操作时,这两个值都会被当成0处理。

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

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

这里对25执行按位非操作,结果得到了-26。这也验证了按位非操作的本质:操作数的负值减1。因此,下面的代码也能得到相同的结果。

var num1 = 25;
var num2 = ~num1-1;
alert(num2);

虽然以上代码也能返回同样的结果,但由于按位非是在数值表示的最底层执行操作,因此速度更快。
2. 按位与(AND)
按位与操作符由一个和号字符(&)表示,它有两个操作符数。从本质上讲,按位与操作就是讲两个数值的每一位对齐,然后根据下表中的规则,对相同位置上的两个数执行AND操作:
只有当两个数值的对应位都是1时才返回1,任何一位是0,结果都是0。举例如下:
var result = 25 & 3;
alert(result); //1
可见,对25和3执行按位与操作的结果是1。底层操作如下:

3. 按位或(OR)
按位与操作由一个竖线符号(|)表示,同样也有两个操作数。按位或操作遵循下面这个真值表。
按位或操作在有一个位是1的情况下就返回1,而只有在两个位都是0的情况下才返回0。举例如下:

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

对25和3执行按位或的结果是27,其底层操作如下所示:

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

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

对25和3执行按位异的结果是26,其底层操作如下所示:

这两个数值都包含4个1,但第一位上则都是1,因此结果的第一位变成了0.而其他位上的1在另一个数值中都没有对应的1,可以直接放到结果中。二进制码11010等于十进制26(注意这个结果比执行按位或时小1)。
5. 左移
左移操作符由两个小于号(<),这个操作符会将数值的所有位向左移动指定的位数。例如,如果将数值2(二进制码为10)向左移动5位,结果就是64(二进制码为1000000),代码如下:

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

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

注意,左移不会影响操作数的符号位。换句话说,如果将-2向左移动5位,结果将是-64,而非64
6. 有符号的右移
有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)。有符号的右移操作与左移操作恰好相反,即如果将64向右移动5位,结果将变回2:

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

同源,在移位的过程中,原数值中也会出现空位。只不过这次的空位出现在原数值的左侧、符号位的右侧。而此时ECMAScript会用符号位的值来填充所有空位,以便得到一个完整的值。
7. 无符号右移
无符号右移操作符由3个大于号(>>>)表示,这个操作符会将数值的所有32位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。举例如下:

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

但对负数来说,情况就不一样了。首先,无符号右移是以0来填充空位,而不是像有符号右移那样以符号位的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了。其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常之大,如下例所示:

var oldValue = -64;   //等于二进制的11111111111111111111111111000000
var newValue = oldValue >>> 5;  //等于十进制的134217726

这里,当对-64执行无符号右移5位的操作后,得到的结果是134217726。之所以结果如此之大,是因为-64的二进制码是11111111111111111111111111000000,而且无符号右移操作会把这个二进制码当成正数的二进制码,换算成十进制就是4294967232.如果把这个值右移5位,结果就变成了000001111111111111111111111111110,即十进制的134217726

3.5.3 布尔操作符

布尔操作符一共有3个:非(NOT)、与(AND)和或(OR)。
1. 逻辑非
逻辑非操作符是由一个叹号!表示,可以应用于ECMAScript中的任何值。无论这个值是什么数据类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再对其求反。逻辑非操作符遵循下列规则:

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

举例如下:

alert(!false);                  //true
alert(!"blue");                 //false
alert(!0);                        //true
alert(!NaN);                   //true
alert(!" ");                       //true
alert(!12345);                 //false

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

alert(!!"blue");                 //true
alert(!!0);                        //false
alert(!!NaN);                   //false
alert(!!" ");                       //false
alert(!!12345);                 //true

2.逻辑与
逻辑与操作符由两个和号(&&)表示,有两个操作数,如下所示:
var result = true && false;
逻辑与的真值表如下:
逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作就不一定返回布尔值,此时,遵循以下规则:

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

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

var found = true;
var result = (found && somUndefindeVariable);   //这里会发生错误
alert(result);            //这一行不会执行

在上面的代码中,当执行逻辑与操作时会发生错误,因为变量someUndefinedVariable没有声明。由于面临found的值是true,所以逻辑与操作符会继续对变量someUndefinedVariable求值。但someUndefinedVariable尚未定义,因此就会导致错误。这说明不能在逻辑与操作中使用未定义的值。如果像下面这个例子中一样,将found设为false,就不会发生错误了:

var found = false;
var result = (found && somUndefindeVariable);              //不会发生错误
alert(result);            //会执行(“false”)

3.逻辑或
逻辑或操作符由两个竖线符号(||)表示,有两个操作数,举例如下:
var result = true || false;
逻辑或的真值表如下:
与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值,规则如下:

  • 如果第一个操作数是对象,则返回第一个操作数
  • 如果第一个操作数的求值结果为false,则返回第二个操作数;
  • 如果两个操作数都是对象,则返回第一个操作数
  • 如果两个操作数都是null,则返回null
  • 如果两个操作数都是NaN,则返回NaN
  • 如果两个操作数都是undefined,则返回undefined
    与逻辑与操作符相似,逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作数求值了,举例如下:
var found = true;
var result = (found || someUndefinedVariable);  //不会发生错误
alert(result);          //会执行(true)

而相反的把found改为false,就会报错

var found = false;
var result = (found || someUndefinedVariable);  //这里会发生错误
alert(result);          //这一行不会执行

我们可以利用逻辑或的这一行为来避免为变量赋null或undefined值。例如:
var myObject = preferredObject || backupObject;
这个例子中,变量myObject将被赋予等号后面两个值中的一个。变量preferredObject中包含优先赋给变量myObject的值,变量backupObject负责在preferredObject中不包含有效值的情况下提供后备值。如果preferredObject的值不是null,那么它的值将被赋给myObject;如果是null,则将backupObject的值赋给myObject。ECMAScript程序的赋值语句经常会使用这种模式。

3.5.4 乘性操作符

ECMAScript定义了3个乘性操作符:乘法、除法和求模。这些操作符与Java、C或者Perl中的相应操作符用途类似,只不过在操作数未非数值的情况下会执行自动的类型转换。如果参与乘法计算的某个操作数不是数值,后台会先使用Number()转型函数将其转换为数值。即,空字符串被当做0,布尔值true将被当做1。
1. 乘法
星号(*)表示,用于计算两个数值的乘积。举例如下:
var result = 34 * 56;
在处理特殊情况下,遵循规则如下:
- 如果操作数都是数值,执行常规的计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有负号,那么结果就是负数。如果乘积超过了ECMAScript数值的表示范围,则返回Infinity或-Infinity;

  • 如果有一个操作数是NaN,则结果都是NaN;
  • 如果是Infinity与0相乘,则结果是NaN;
  • 如果是Infinity与非0数值相乘,则结果是Infinity或-Infinity,取决于有符号操作数的符号
  • 如果Infinity与Infinity相乘,则结果是Infinity。
  • 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后再应用上面的规则。

1.乘法
星号(*)表示,用于计算两个数值的乘积。举例如下:
var result = 34 * 56;
在处理特殊情况下,遵循规则如下:

  • 如果操作数都是数值,执行常规的计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有负号,那么结果就是负数。如果乘积超过了ECMAScript数值的表示范围,则返回Infinity或-Infinity;
  • 如果有一个操作数是NaN,则结果都是NaN;
  • 如果是Infinity与0相乘,则结果是NaN;
  • 如果是Infinity与非0数值相乘,则结果是Infinity或-Infinity,取决于有符号操作数的符号
  • 如果Infinity与Infinity相乘,则结果是Infinity。
  • 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后再应用上面的规则。

3.5.5 加性操作符

与乘性操作符类似,加性操作符也会在后台转换不同的数据类型。
1. 加法
var result = 1 + 2;
如果两个操作符都是数值,执行常规的加法计算,然后根据下列规则返回结果:

  • 如果有一个操作数是NaN,则结果是NaN;
  • 如果是Infinity加Infinity,则结果是Infinity
  • 如果是-Infinity加-Infinity,则结果是-Infinity
  • 如果是Infinity加-Infinity,则结果是NaN
  • 如果是+0加+0,则结果是+0
  • 如果是-0加-0,则结果是-0
  • 如果是+0加-0,则结果是+0

不过,如果有一个操作数是字符串,规则如下:

  • 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来
  • 如果是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 result1 = 5 + 5;        //两个数相加
alert(result);                 //10
var result2 = 5 + "5";      //一个数值和一个字符串相加
alert(result2);

忽视加法操作中的数据类型是ECMAScript编程中最常见的一个错误,举例如下:

var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
alert(message);                  //"The sum of 5 and 10 is 510"

这个例子中,变量message的值是执行两个加法操作之后的结果。有人可能以为最后得到的字符串是“The sum of 5 and 10 is 15”,但实际却是“The sum of 5 and 10 is 510”。原因是每个加法操作都是独立执行的。第一个加法操作将一个字符串和一个数值(5)拼接了起来,结果是一个字符串,第二各加法操作同上,也得到一个字符串。如果想先进行数值计算,然后再与字符串拼接,则如下:

var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + (num1 + num2);
alert(message);                  //"The sum of 5 and 10 is 15"

2.减法
var result = 2 - 1;
与加法操作符类似,规则如下:

  • 如果两个操作符都是数值,则执行常规的算术减法操作并返回结果;
  • 如果有一个操作数是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()方法并将得到的字符串转换为数值。
    举例如下:
var result1 = 5 - true;         //4,因为true被转换成了1
var result2 = NaN - 1;         //NaN
var result3 = 5 - 3;              //2
var result4 = 5 - "";              //5,因为“”被转换成了0
var result5 = 5 - "2";            //3,因为“2”被转换成了2
var result6 = 5 - null;           //5,因为null被转换成了0

3.5.6 关系操作符

小于(<)、大于(>)、小于等于(<=)和大于等于(>=)这几个关系操作符用于对两个值进行比较,比较的规则与我们在数学课上所学的一样。这几个操作符都返回一个布尔值,如下所示:

var result1 = 5 > 3;            //true
var result2 = 5 < 3;            //false

当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。规则如下:

  • 如果两个操作数都是数值,则执行数值比较
  • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值
  • 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较
  • 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果安装前面的规则执行比较。如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果根据前面的规则执行比较
  • 如果一个操作数是布尔值,则先将其转换为数值,再执行比较。
    在比较字符串时,实际比较的是两个字符串终对应位置的每个字符的字符编码值,然后返回一个布尔值。由于大写字母的字符编码全部小于小写字母的字符编码,因此会出现下述情况:
var result = "Brick" < "alphabet";                //true

这个例子中,字符串“Brick”被认为小于字符串“alphabet”。原因是字母B的字符编码为66,而字母a的字符编码是97.如果要真正按字母表顺序比较字符串,就必须把两个操作数转换为相同的大小写形式(全部大写或全部小写),然后再执行比较,如下所示:

var result = "Brick".toLowerCase() < "alphabet".toLowerCase();        //false

通过将两个操作数都转换为小写形式,就可以得出“alphabet”按字母表顺序排在“Brick”之前的正确判断了。
另一种奇怪的现象如下:

var result = "23" < "3";               //true

确实,当比较字符串“23”是否小于“3”时,结果居然是true。因为这两个操作数都是字符串,而字符串比较的是字符编码(”2”的字符编码是50,而“3”的字符编码是51)。如果这样改,结果就正常了:

var result = "23" < 3;               //false

此时,字符串“23”会被转换成数值23,然后再与3进行比较,因此就会得到合理的结果。在比较数值和字符串时,字符串都会被转换成数值,然后再以数值方式与另一个数值比较。但是,如果字符串不能转换成一个合理的数值,如下:

var result = "a" < 3;                //false,因为“a”被转换成了NaN

由于字母”a”不能转换成合理的数值,因此就被转换成了NaN。根据规则,任何操作数与NaN进行关系比较,结果都是false。于是出现如下情况:

var result1 = NaN < 3;           //false
var result2 = NaN  >= 3;         //false

3.5.7 相等操作符

最早的ECMAScript中的相等和不等操作符会在执行比较之前,先将对象转换成相似的类型。后来,有人提出了这种转换到底是否合理的质疑。最后,ECMAScript的解决方案就是提供两组操作符:相等和不想等–先转换再比较,全等和不全等—–仅比较而不转换
1. 相等和不相等
ECMAScript中的相等操作符由两个等于号(==)表示,如果两个操作数相等,则返回true。而不相等操作符由叹号后跟等于号(!=)表示,如果两个操作数不相等,则返回true。这两个操作符都会先转换操作数(通常称为强制转型),然后再比较它们的相等性。
相等和不相等的基本规则如下:

  • 如果有一个操作数是布尔值,则在比较相等性之前现将其转换为数值 – false转换为0,而true转换为1;
  • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf(),用得到的基本类型值安装前面的规则进行比较;
    这两个操作符在进行比较时则要遵循下列规则:
  • null和undefined是相等的
  • 要比较相等性之前,不能将null和undefined转换成其他任何值
  • 如果有一个操作数是NaN,则相等操作符返回false,而不相等操作符返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN。
  • 如果两个操作数都是对象,则比较它们是不是同一个对象,如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回false;
    特殊情况列表:
    2.全等和不全等
    除了在比较之前不转换操作数之外,全等和不全等操作符与相等和不相等操作符没有什么区别。全等操作符由3个等于号(===)表示,它值在两个操作数未经转换就相等的情况下返回true,如下例所示:
var result1 = ("55" == 55);          //true,因为转换后相等
var result2 = ("55" === 55);        //false,因为不同的数据类型不相等

不全等操作符由一个叹号后跟两个等于号(!==)表示,它在两个操作数未经转换就不相等的情况下返回true。例如:

var result1 = ("55" != 55);       //false,因为转换后相等
var result2 = ("55" !== 55);      //true,因为不同的数据类型不相等

null == undefined会返回true,因为它们是类似的值;但null === undefined会返回false,因为它们是不同类型的值

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

3.5.8 条件操作符

条件操作符应该算是ECMAScript中最灵活的一种操作符,而且遵循与Java中的条件操作符相同的语法形式,举例如下:

variable = boolean_expression ? true_value : false_value;

本质上,这行代码的含义就是基于对boolean_expression求值的结果,决定给变量variable赋什么值。如果求值结果为true,则给变量variable赋true_value值,如果求值结果为false,则给变量variable赋false_value值,举例如下:

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

3.5.9 赋值操作符

等号(=)表示。把右侧的值赋给左侧的变量,举例如下:

var num = 10;

如果在等于号(=)前面再添加乘性操作符、加性操作符或位操作符,就可以完成复核赋值操作。这种复合赋值操作相当于对下面常规表达式的简写形式:

var num = 10;
num = num + 10;

其中第二行代码可以用一个复合赋值来代替:

var num = 10;
num += 10;

每个主要算术操作符(以及个别的其他操作符)都有对应的复合赋值操作符,如下:

  • 乘/赋值(*=)
  • 除/赋值(/=)
  • 模/赋值(%=)
  • 加/赋值(+=)
  • 减/赋值(-=)
  • 左移/赋值(<<=)
  • 有符号右移/赋值(>>=)
  • 无符号右移/赋值(>>>=)

这些操作符的主要目的就是简化赋值操作。

3.5.10 逗号操作符

使用逗号操作符可以在一条语句中执行多个操作:

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

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

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

3.6 语句

ECMA-262规定了一组语句(也称为流控制语句)。从本质上按,语句定义了ECMAScript中的主要语法,语句通常使用一或多个关键字来完成给定任务。语句很简单,例如通知函数退出,也可以比较复杂,例如指定重复执行某个命令的次数。

3.6.1 if语句

大多数编程语言中最为常用的一个语句就是if语句。

if (condition) statement1 else statement2

其中的condition(条件)可以是任意表达式,而且对这个表达式求指导额结果不一定是布尔值。ECMAScript会自动调用Boolean()转换函数将这个表达式的结果转换为一个布尔值。如果condition求值的结果是true,则执行statement1(语句1),如果condition的结果是false,则执行statement2(语句2)。这两个语句既可以是一行代码,也可以是一个代码块(以一对花括号括起来的多行代码)。举例如下:

if (i > 25) alert(“Great than 25.”);                //单行语句
else {alert("Less than or equal to 25.")     //代码块中的语句
}

推荐使用代码块,即使要执行的只有一行代码。
另外,也可以像下面这样把整个if语句写在一行代码中:

if (condition1) statement1 else if (condition2) statement2 else statement3

推荐语法如下:

if (i > 25) {alert("Greater than 25.");
} else if (i < 0) {alert("Less than 0.");
} else {alert("Between 0 and 25,")
}

3.6.2 do-while语句

do-while是一种后测试循环语句,即只有在循环体重的代码执行之后,才会测试出口条件。换句话说,在对条件表达式求值之前,循环体内的代码至少会被执行一次。语法如下:

do {statement
} while (expression);

举例如下:

var i = 0;
do {i += 2;
} while (i < 10);
alert(i);

上述例子中,只要变量 i 的值小于10,循环就会一直继续下去。而且变量i的值最初为0,每次循环递增2。

像do-while这种后测式循环语句最常用于循环体中的代码至少要被执行一次的情形。

3.6.3 while语句

while语句属于前测试循环语句,在循环体内的代码被执行之前,就会对出口条件求值。语法如下:

while(expression) statement

举例如下:

var i = 0;
while(i < 10) {i += 2;
}

上述例子中,当变量i开始时的值为0,每次循环递增2,只有I的值小于10,循环就会循环下去

3.6.4 for语句

也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力,语法:

for (initialization, expression, post-loop-expression) statement

举例如下:

var count = 10;
for (var i = 0; i < count; i++) {alert(i);
}

以上代码定义了变量 i 的初始值为0,只有当条件表达式( i < count)返回true的情况下才会进入for循环,因此也有可能不会执行循环体中的代码。如果执行了循环体中的代码,则一定会对循环后的表达式(i++)求值,即递增 i 的值。这个for循环语句与下面的while语句的功能相同:

var count = 10;
var i = 0;
while (i < count) {alert(i);i++;
}

使用while循环做不到的,for循环也做不到。也就是说,for循环只是把与循环有关的代码集中在了一个位置。
在for循环的变量初始化表达式中,也可以不使用var关键字。该变量的初始化可以在外部执行,例如:

var count = 10;
var i;
for (i = 0; i < count; i++) {alert(i);
}

由于ECMAScript中不存在块级作用域,因此在循环内部定义的变量也可以在外部访问到。举例如下:

var count = 10;
for (var i = 0; i < count; i++) {alert(i);
}
alert(i);          //10

另外,for语句的初始化表达式、控制表达式和循环后表达式都是可选的。将这两个表达式全部省略,就会创建一个无线循环,例如:

for(;;) {                          //无限循环doSomething();
}

而只给出控制表达式实际上就把for循环转换成了while循环,例如:

var count = 10;
var i = 0;
for (; i < count; ) {alert(i);i++;
}

由于for语句存在极大的灵活性,因此也是ECMAScript中常用的一个语句。

3.6.5 for-in语句

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

for (property in expression) statement

举例如下:

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

上述例子中,我们使用for-in循环显示了BOM中window对象的所有属性。每次执行循环时,都会将window对象中存在的一个属性名赋值给变量proName。这个过程会一直持续到对象中的所有属性都被枚举一遍为止于for语句类似,这里控制语句中的var操作符也不是必须的,但是为了保证使用局部属性,推荐var。
ECMAScript对象的属性没有顺序。因此,通过for-in循环输出的属性名的顺序是不可预测的。具体说,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。
当表示迭代的对象的变量值为null或undefined,for-in语句会抛出错误。ECMAScript 5对这种情况不再抛出错误,而是不执行循环体。为保证最大限度的兼容性,建议在使用for-in循环之前,先检测确认该对象的值不是null或undefined。

Safari 3以前版本的for-in语句中存在一个bug,该bug会导致某些属性被返回两次。

3.6.6 label语句

使用label语句可以在代码中添加标签,语法:

label: statement

举例如下:

start: for (var i=0; i < count; i++) {alert(i);
}

这个例子中定义的start标签可以在将来由break或continue语句引用。加标签的语句一般都要与for语句等循环语句配合使用。

3.6.7 break和continue语句

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

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

在变量 i 等于5时,循环共执行了4此,而break语句的执行,导致了循环在num再次递增之前就退出了。
如果这里把break替换为continue,结果如下:

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

当变量 i 等于5时,循环会在num再次递增之前退出,但接下来执行的是下一次循环,即 i 的值等于6的循环。于是循环又继续执行,知道i等于10时自然结束。而num的最终值之所以为8,是因为continue语句导致它少递增了一次。
结合label语句,返回代码中特定的位置,这种联合使用的情况多发生在循环嵌套的情况,举例如下:

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

在上例中,outermost标签表示外部的for语句。如果每个循环正常秩序100次,则num++语句就会正常执行100次。换句话说,如果两个循环都自然结束,num的值应该是100.但内部循环中的break语句带了一个参数:要返回到的标签。添加这个标签的结果将导致break语句不仅会退出内部的for语句(即使用变量 j 的循环),而且也会退出外部的for语句(即使用变量 i 的循环)。为此,当变量 i 和 j 都等于5时,num的值正好是55。
同样,continue语句也可以与label语句联用,举例如下:

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

在这种情况下,continue语句会强制继续执行循环–退出内部循环、执行外部循环。当 j 是5时,continue语句执行,而这也就意味着内部循环少执行了5次,因此num的结果是95。
虽然联用break、continue和label语句能够执行复杂的操作,但如果使用过度,也会给调试带来麻烦。因此建议如果使用label语句,一定要使用描述性的标签,同时不要嵌套过多的循环。

3.6.8 with语句

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

with (expression) statement;

定义with语句的目的主要是为了简化多次编写同一个对象的工作,举例如下:

var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;

上面几行代码都包含了location对象。如果使用with语句,可以把上面的代码改写成如下所示:

with(location) {var qs = search.substring(1);var hostName = hostname;var url = href;
}

在这个重写后的例子中,with语句关联了location对象。意味着在with语句的代码块内部,每个变量首先被认为是一个局部变量,而如果在局部环境中找不到该变量的定义,就会查询location对象中是否有同名的属性。如果发现了同名属性,则以location对象属性的值作为变量的值。
严格模式不允许使用with语句,否则视为语法错误。

由于大量使用with语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用with语句。

3.6.9 switch语句

与 if 语句的关系最为密切,也是其他语言中普遍使用的一种流控制语句。ECMAScript中switch语法与其他基于C的语言非常接近,如下所示:

switch (expression) {case value: statementbreak;case value: statementbreak;case value: statementbreak;case value: statementbreak;default: statement
}

switch语句中的每一种情形(case)的含义是:“如果表达式等于这个值 value,则执行后面的语句(statement)”。而break关键字会导致代码执行流跳出switch语句。如果省略break关键字,就会导致执行完当前case后,继续执行下一个case。最后default关键字则用于在表达式不匹配前面任何一种情形的时候,执行机动代码(相当于一个else语句)。
从根本上讲,switch语句是为了避免下述代码:

if (i == 25) {alert("25");
} else if (i == 35) {alert("35");
} else if (i == 45) {alert("45");
} else {alert("Other");
}

同等的switch语句如下:

switch (i) {case 25:alert("25");break;case 35:alert("35");break;case 45:alert("45");break;default:alert("Other");
}

通过为每个case后面添加一个break语句,就可以避免同时执行多个case代码的情况。如果确实要混合,最好在代码中添加注释,说明是有意省略了break关键字,举例如下:

switch (i) {case 25:case 35:alert("25 or 35");break;case 45:alert("45");break;default;alert("Other");
}

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.")
}

上述switch语句使用的是字符串。第一中是一个对字符串拼接操作求值的表达式。由于这个字符串拼接表达式的结果与switch的参数相等,因此结果就会显示“Greeting was found.”。而且,使用表达式作为case值还可以实现下列操作:

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

这个例子首先在switch语句外声明了变量num。之所以给switch语句传递表达式true,是因为每个case值都可以返回一个布尔值。这样,每个case安装顺序被求值,指定找到匹配的值或者遇到default语句为止。

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

3.7 函数

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

function functionName(arg0, arg1,..., argN) {statements
}

以下是一个函数示例:

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

这个函数可以通过其函数名来调用,后面还要加上一对圆括号和参数(圆括号中的参数如果有多个,可以用逗号隔开)。调用sayHi()函数的代码如下所示:

sayHi("Nicholas", "how are you today?");

这个函数的输出结果是“Hello Nicholas, how are you today?”。函数中定义中的命名参数name和message被用作了字符串拼接的两个操作数,而结果最终通过警告框显示了出来。
ECMAScript中的函数在定义时不必指定是否返回值。实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。举例如下:

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

这个sum()函数的作用是把两个值加起来返回一个结果。这里除了return语句之外,没有任何声明表示该函数会返回一个值。调用函数示例如下:
var result = sum(5, 10);
这个函数会在执行完return语句之后停止并立即退出。因此,位于return语句之后的任何代码都永远不会执行。举例如下:

function sum(num1, num2) {return num1 + num2;alert("Hello world");                //永远不会执行
}

这里,由于调用alert()函数的语句位于return语句之后,因此永远不会显示警示框。
当然,一个函数中也可以包含多个return语句,举例如下:

function diff(num1, num2) {if (num1 < num2) {return num2 - num1;} else {return num1 - num2;}
}

另外,return语句也可以不带有任何返回值。这样,函数在停止执行后将返回undefined值。这种用法一般用在需要提前停止函数执行而又不需要返回值的情况,举例如下:

function sayHi(name, message) {return;alert("Hello" + name + "," + message);      // 用于不会调用
}

推荐做法是要么让函数始终都返回一个值,要么永远都不要返回值。否则,如果函数有时候返回值,有时候不返回值,会给调试代码带来不便。

严格模式对函数有有一些限制:

  • 不能把函数命名为eval或arguments;
  • 不能把参数命名为为eval或arguments;
  • 不能出现两个命名参数同名的情况。

3.7.1 理解参数

ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript函数不介意传递进来多数参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数。之所以会这样,原因是ECMAScript中的参数在内部是用一个数组来表示。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数值中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数。
其实,arguments对象只是与数组类似(它并不是Array的实例),因为可以使用方括号语法访问它的每一个元素(即第一个元素是arguments[0],第二个元素是arguments[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

另一个与参数相关的重要方面,就是arguments对象可以与命名参数一起使用,举例如下:

function doAdd(num1, num2) {if(arguments.length == 1) {alert(num1 + 10);} else if (arguments.length == 2) {alert(arguments[0] + num2);}
}

在重新后的这个doAdd()函数中,两个命名参数都与arguments对象一起使用。由于num1的值与arguments[0]的值相同,因此它们可以互换使用。
关于arguments的行为,还有一点,就是它的值永远与对应名参数的值保持同步,举例如下:

function doAdd(num1, num2) {arguments[1] = 10;alert(arguments[0] + num2);
}

每次执行这个doAdd()函数都会重新第二个参数,将第二个参数的值修改为10.因为arguments对象中的值会自动反映到对应的命名参数,所以修改arguments[1],也就修改了num2,结果它们的值都会变成10。不过,这不是说读取这两个值会访问相同的内存空间,它们的内存空间是独立的,但它们的值会同步。这种影响是单向的:修改命名参数不会改变arguments中对应的值。如果只传入了一个参数,那么为arguments[1]设置的值不会反应到命名参数中。这是因为arguments对象的长度由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。
关于参数还要记住最后一点:没有传递值的命名参数将自动被赋予undefined值。这就跟定义了变量但又没有初始化一样。例如,如果只给doAdd()函数传递了一个参数,则num2中就会保存undefined值。
严格模式对如何使用arguments对象做出了一些限制。首先,像前面例子中那样的赋值会变得无效。也就是说,即使把arguments[1]设置为10,num2的值仍然还是undefined。其次,重新arguments的值会导致语法错误(代码将不会执行)。

ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。

3.7.2 没有重载

ECMAScript函数不能像传统意义上那样实现重载。而在其他语句(如Java)中,可以为一个函数编写两个定义,只有这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述,ECMASCript函数没有签名,因为其参数是由包含零或多个值的数值来表示的。而没有函数签名,真正的重置是不可能做到的。
如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数。举例如下:

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

在此,函数addSomeNumber()被定义了两次。第一个版本给参数加100,而第二个版本给参数加200.由于后定义的函数覆盖了先定义的函数,因此当在最后一行代码中调用这个函数时,返回的结果就是300.
如前所述,通过检查传入函数中参数的类型和数量并作出不同的反应,可以模仿方法的重载。

3.8 小结

JavaScript的核心语言特性在ECMA-262中是以名为ECMAScript的伪语言的形式来定义的。ECMAScript中包含了所有基本的语法、操作符、数据类型以及完成基本的计算任务所必须的对象,但没有对取得输入和产生输出的机制作出规定。理解ECMAScript及其纷繁复杂的各种细节,是理解其在Web浏览器中的实现–JavaScript的关键。目前大多数实现所遵循的都是ECMA-262第3版,但很多也已经开始实现第5版了。以下简要总结了ECMAScript中基本的要素:

  • ECMAScript中的基本数据类型包括Undefined、Null、Boolean、Number和String。
  • 与其他语言不同,ECMAScript没有为整数和浮点数值分别定义不同的数据类型,Number类型可用于表示所有数值。
  • ECMAScript中也有一种复杂的数据类型,即Object类型,该类型是这门语言中所有对象的基础类型。
  • 严格模式为这门语言中容易出错的地方施加了限制。
  • ECMAScript提供了很多与C及其他类C语言中相同的基本操作符,包括算术操作符、布尔操作符、关系操作符、相等操作符及赋值操作符等。
  • ECMAScript从其他语言中借鉴了很多流控制语句,例如if语句、for语句和switch语句等。

ECMAScript中的函数与其他语言中的函数有诸多不同之处:

  • 无须指定函数的返回值,因为任何ECMAScript函数都可以在任何时候返回任何值。
  • 实际上,未指定返回值的函数返回的是一个特殊的undefined值。
  • ECMAScript中也没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的。
  • 可以向ECMAScript函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。
  • 由于不存在函数签名的特性,ECMAScript函数不能重载。

(3)JavaScript基本概念相关推荐

  1. js基础--javascript基础概念之函数

    js基础--javascript基础概念之函数 引用w3c介绍: 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块. JavaScript 函数语法 函数就是包裹在花括号中的代码块,前面使用 ...

  2. javascript的概念

    一.了解JavaScript 页面中所有的动态效果或动态交互基本上都是基于JavaScript来实现的 1.      页面的各种动态效果 2.      对用户操作的响应 3.      对客户端数 ...

  3. 33 个 JavaScript 核心概念系列(三): 显式 (名义) 与 隐式 (鸭子)类型转换

    原文地址:落明的博客 一. 前言 说实话,JavaScript 的类型转换是个相当头疼的问题,无论是对于初学者还是有经验的老司机.它的难处并不在于概念多难理解,而是情况多且杂,看似相同的情况结果却又出 ...

  4. 深入浅出理解Javascript原型概念以及继承机制(转)

    在Javascript语言中,原型是一个经常被讨论到但是有非常让初学者不解的概念.那么,到底该怎么去给原型定义呢?不急,在了解是什么之前,我们不妨先来看下为什么. Javascript最开始是网景公司 ...

  5. javascript 核心概念(1)-数据类型

    语法 (1)到现在为止,大多数浏览器也还是支持到ECMAScript 第三版的标准. 核心概念就是一个语言的基本工作原理,涉及语法,操作符,数据类型. (2)javascript的一切--变量,函数名 ...

  6. 面试:JavaScript基础概念

    文章目录 1. JS基础概念 JavaScript 常见知识总结 重点 1.js的基本数据类型都有哪些 2.判断基本数据类型的方法 typeof instanceof Object.prototype ...

  7. JavaScript基本概念

    javascript简介 JavaScript 简介 JavaScript 是互联网上最受欢迎的编程语言之一,用于为网页添加交互性,处理数据,以及创建各种应用程序(移动应用程序,桌面应用程序,游戏等) ...

  8. JavaScript 核心概念之作用域和闭包

    相信大家已经阅读了很多关于作用域和闭包文章,我也一样.作用域和闭包是 JavaScript 中的关键概念之一.当我阅读了<高性能的JavaScript>这本书后,我才完全理解这两个概念.所 ...

  9. 笔记JavaScript基本概念

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

最新文章

  1. bzoj千题计划262:bzoj4868: [六省联考2017]期末考试
  2. 易想商城V4.7.1 过期到期解决办法
  3. ASP.NET Core使用Nacos SDK访问阿里云ACM
  4. Highcharts隐藏网格线
  5. 如何带领团队“攻城略地”?优秀的架构师这样做
  6. 在html中写js提示错误,页面HTML标签优化
  7. vs如何显示arcgis 二次开发工具控件
  8. C语言小知识---递归函数的使用
  9. linux如何安装阵列卡驱动程序,Linux安装阵列卡驱动及档.doc
  10. [项目回顾]基于Redis的在线用户列表解决方案
  11. java实现语法分析实验报告_[源码和文档分享]基于JAVA实现的基于LL(1)语法分析构造表...
  12. android与相机连接电脑,使用Android手机或平板电脑控制dSLR相机
  13. Android中TextView中文字体粗体的设置方法
  14. 论文阅读_ICD编码_MSATT-KG
  15. java int转byte_JAVA中怎么将int数据转换为byte数据?
  16. OneNote的同步问题
  17. Linux 网络编程socket错误分析
  18. 2020春季学期哈工大软件构造学习心得一
  19. HTML是什么?—零基础自学网页制作
  20. 量子前沿英雄谱|引领量子科技三十年:斯坦福NTT教授Yoshihisa Yamamoto

热门文章

  1. 零氪科技撤回IPO申请:年亏近5亿 中概股上市已停滞9个月
  2. DLR in Silverlight
  3. iOS icon图标尺寸
  4. 优雅的99乘法表以及format使用
  5. 双十一最后一天该怎么入手,几款必备的好物分享
  6. 我的世界古代战争模组介绍java版_我的世界古代战争2mod教程零基础到专属军队...
  7. 华硕FL5900U如何关闭ahci_实战华硕B360主板RX580显卡安装苹果macOS 10.14 Mojave
  8. freescale imx6 编译 linux ltib,i.MX6 Ltib编译报错,请版主解决一下。(已解决)
  9. Stata分位数回归I:理解边际效应和条件边际效应
  10. 穷人最缺少的不是金钱,而是野心