在javascript中,表达式是短语,那么语句(statement)就是整句或命令。正如英文语句以句号结尾,javascript以分号结尾。
表达式计算出一个值,但语句使某件事发生。
“使某件事发生”的一个方法是计算带有副作用的表达式。诸如赋值和函数调用这些有副作用的表达式,是可以作为单独的语句的。这种把表达式当做语句的用法也称做表达式语句(expression statement)。类似的语句还有声明语句(declaration statement),声明语句用来声明新变量或者定义新函数。
javascript程序就是一系列的可执行语句的集合,默认情况下,javascript解释器依照编写顺序依次执行。另一种“使某件事情”发生的方法就是改变语句的默认执行顺序:
- 条件语句(conditional)语句:javascript解释器可以根据一个表达式的值来判断;来执行还是跳过这些语句,例如if和switch语句。
- 循环语句(loop)语句:可以重复执行的语句,例如while和for语句
- 跳转(jump)语句:可以让解释器跳转至程序的其它部分继续执行、例如break、return和throw语句
接下来本文将介绍javascript中各式各样的语句和其语法。本章最后对这些语句做了总结。一个javascript程序无非是以分隔分割的语句集合,所以一旦掌握了javascript语句,就可以编写javascript程序了。
1.表达式语句
赋值语句是一种比较重要的表达式语句,它的作用就是改变一个变量的值,就像执行一条赋值语句一样:例如
greet = "hello" + name;
i *= 3;
递增运算符(++)和递减运算符(--)和赋值语句有关。它们的作用是改变一个变量的值,就像执行一条赋值语句一样。
counter++;
delete运算符的重要作用就是删除一个对象的属性(或数组的元素),所有它一般作为语句使用,而不是作为复杂表达式的一部分。
delete o.x;
函数调用是表达式语句的另外一个大类,例如
alert(greet);
window.close();
虽然这些客户端函数都是表达式,但它们对web浏览器造成了一定的影响。所以我们认为也是语句,调用一个没有副作用的函数是没有意义的,除非它是复杂的表达式或赋值语句的一部分,例如。不可能随便把一个余弦值丢弃;
Math.cos(x);
相反,得出余弦值就得把它赋值给一个变量,以便将来使用这个值:
var cx = Math.cos(x);
再次提醒各位,每行代码就是以分号结束的。
2.复合语句和空语句
可以用逗号运算符将几个表达式连接在一起,形成一个表达式。同样,javascript还可以讲多条语句联合在一起,形成一个复合语句(compound statement)。只需花括号将多条语句括起来即可。因此,下面几行代码可以当成一条单独的语句,使用在javascript任何希望使用一条语句的地方。
{
x = Math.PI;
cx = Math.cos(x);
console.log("cos(π)=" + cx);
}
关于语句块有几点需要注意:第一,语句块不需要分号。块中的元素语句必须以分号结尾,但语句块不需要。
第二,语句块中的行都有缩进,这不是必须的,但整齐的缩进能使代码可读性更强,更容易理解。
第三,javascript没有块级作用域,在语句块中声明的变量并不是语句块所私有的。(参考3章10节第一小节)
将很多条语句合并成一个大语句块的做法在javascript编程中非常常见。类似的表达式通常包含子表达式一样,很多javascript包含其它子语句,从形式来讲,javascript通常允许一个语句块包含一条子语句。例如:while循环的循环体就可以只包含一条语句。使用语句块,可以将任意数量的语句放到这个块中,这个语句块可以当做一条语句来使用。
在javascript中,当希望多条语句被当做一条语句使用时,使用符合语句来替代。空语句(empty statement)则恰好相反,它允许包含0条语句。空语句如下所示:
;//分号
javascript解释器在执行空语句时显然不执行任何动作,但实践证明:当创建一个具有空循环体的循环时,空语句有时是很有用的,例如下面的for循环
//初始化一个数组a
for (i = 0; i < a.length; a[i++] = 0);
在这个循环中,所有的操作都在表达式a[i++]=0中完成,这里并不需要任何循环体。然而javascript需要循环体中至少包含一条语句,因此这里只使用了一个单独的分号来表示一条空语句。
注意,在for循环、while循环或if语句的右边园括号的分号很不起眼,这很可能造成 一些致命的bug,而这些bug很难定位到。例如下面的代码的执行结果很可能就是作者不想要的效果:
if((a==0)||(b==0)); //这一行代码什么也没做....
o = null; //这一行代码总会执行
如果有特殊目的使用空语句,最好在代码中添加注释,这样能更清楚的说明这条空语句是有用的
for (i = 0; i < a.length; a[i++] = 0) /*empty*/;
3.声明语句
var和function都是声明语句,它们声明或定义变量或函数。这些语句定义标识符(变量名和函数名)并给其赋值,这些标识符可以在程序任意地方使用。声明语句本身什么也不做,但它有一个重要意义:通过创建变量和函数,可以更好的组织代码的语义。
接下几节将讲述var语句和function语句,但并不包含变量和函数的全部内容。
i.var
var语句用来声明一个或者多个变量,它的语法如下:
var name_1[ = value_1][, ..., name_n[ = value_n]]
关键字var之后跟随的是要声明的变量列表,列表中的每一个变量都可以带有初始化表达式,可用于指定它的初始值。例如:
var i; //一个简单的变量
var j = 0; //一个带有初始值的变量
var p, q; //两个变量
var greet = "hello" + name; //更复杂的初始化表达式
var x = 2.34,y = Math.cos(0.75),r, theta; //很多变量
var x = 2,y = x * x; //第二个变量使用了第一个变量
var x = 2,
f = function(x) {return x * x}, //每个变量都独占一行
y = f(x)
如果var语句出现在函数体内,那么定义的是一个局部变量,其作用域就是这个函数。如果在顶层代码中使用var语句,那么它声明的是全局变量,在整个javascript中,都是可见的。在第三章10节提到:全局变量是全局对象的属性,然后和其它全局对象属性不同的是,var声明的变量是无法通过delete删除的。
如果var语句中的变量没有指定初始化表达式,那么这个变量的值初始为undefined。所以,在声明语句之前的变量值就是undefined。
需要注意的是,var语句同样可以作为for循环或者for/in循环的组成部分。(在循环之前声明的变量声明一样,这里声明变量也会"提前"),例如:
for (var i = 0; i < 10; i++) console.log(i);
for (var i = 0, j = 10; i < 10; i++, j--) console.log(i * j);
for (var i in o)console.log(i);
注意,多次声明同一变量是无所谓的。
ii.function
关键字function用来声明函数的,我们已经学过函数表达式(4.3).函数定义可以写成语句的形式。例如:下面示例代码中的两种定义写法:
var f = function f(x) {return x + 1;} //将表达式赋值给一个变量
function f(x){return x + 1;} //含有变量名的语句
函数声明的语法如下:
function funcname([arg1[, arg2[..., argn]]]) {
statements
}
funcname是要声明的函数的名称标识符。函数名之后是参数列表,参数之间使用逗号隔开。当调用函数的时候,这些标识符则指代传入函数的实参。
函数体是由javascript语句组成的,语句数量不限,且用花括号括起来。在定义函数时,并不执行函数体内的语句,它和调用函数时待执行的新函数对象相关联。注意,function函数语句里的花括号是必须的,这和while循环和其它一些语句锁使用的语句块是不同的,即使函数体只有一条语句,仍然需要花括号将其括起来。
function hyteus(x, y) {
return Math.sqrt(x * x + y * y);
}
hyteus(1, 2) //=>2.23606797749979 function facial(n) { //一个递归函数
if (n <= 1) return 1;
return n * facial(n - 1);
}
facial(11) //=>39916800
函数的声明通常出现在javascript代码的最顶部,也可以嵌套在其他函数体内。但在嵌套时,函数声明只能出现在所嵌套的函数顶部。也就是说:函数定义不能出现在if、while、或其他语句中。
和var语句一样,函数声明语句创建的变量也是不可删除的。但是这些变量不是只读的,变量值可以重写。
4.条件语句
条件语句是通过判断指定的表达式的值是否来执行或跳过某些语句。这些语句是代码的”决策点“,有时称为”分支“。如果javascript解释器是按照代码的”路径“执行的。条件语句就是这条路上的分叉点。程序到达这里必须选择一条路径来继续执行。
i.if语句
if语句是基本的控制语句,准确的说,它让程序有条件的执行,这种语句有两种形式:第一种是
if (expression)
statement
这种形式中,判断expression 的值,如果是真,执行statement语句,如果是假值,就不执行statement.例如
if (username == null) //如果username是null或undefined
username = "jack wong"; //对其进行定义
需要注意的是,if语句括住expression的园括号是必须的。
javascript语法规定,if关键字和带园括号的表达式之后必须跟随一条语句。但可以使用语句块将多条语句合成一条。因此,if语句的形式如下所示:
if (!address) {
address = "";
message = "please mailing address"
}
if语句的第二种形式引入了else从句,当expression的值是false值时执行else 逻辑
if (expression)
statement1
else
statement2
例如以下代码
if (n == 1)
console.log("1 new message");
else
console.log("you have" + n + "new message");
当if/else语句中,嵌套使用if语句时,必须注意确保else语句匹配正确的if语句。考虑如下代码:
i = j = 1;
k = 2;
if (i == j)
if (j == k)
console.log("i equs k");
else
console.log("i dosent equal j"); //错误!!
这个实例中,内层的if语句构成了外层if语句所需要的子句。但是,if和else的匹配关系不清晰(只有缩进给了一点暗示)而且在这个例子里,缩进给出的暗示是错误的,因为javascript解释器是这么理解的。
if (i == j) {
if (j == k)
console.log("i equs k");
else
console.log("i dosent equal j");
}
和大多编程语言一样,javascript中的if、else匹配规则是,else总是和就近的if语句匹配,为了让个例子的可读性更强,更容易理解,更方便维护和调试,应当使用花括号
if (i == j) {
if (j == k) {
console.log("i equs k");
} else { //花括号使代码的结果更清晰
console.log("i dosent equal j");
}
}
许多程序员都将有if和else语句主体用花括号括起来的习惯(就像类似while循环这样的符合语句中一样),即使每条分支只有一条语句,但这样做能避免刚才的程序歧义问题。
ii.else if
if/else语句通过判断一个表达式的计算结果来选择两条分支中的一条。当代码中有许多条分支的时候应该怎么办呢?一种解决的办法是使用else if语句。else if并不是真正的javascript语句,它只不过是多条if / else语句连接在一起的写法。
if (n == 1) {
//执行代码块 1
} else if (n == 2) {
//执行代码块2
} else if (n == 3) {
//执行代码块3
} else {
//之前的条件都为false,则执行代码块4
}
这种代码没有什么特别之处,它由多条if语句组成,每条if语句的else的从句又包含另外一条if语句。可以用if语句的嵌套形式来完成语法上的等价代码,但与此相比,显然else if的写法更加清晰也更可取。
iii.switch
if语句在程序执行的过程中,创建一支分支,并且可以使用else if来处理多条分支。然后,当所有的分支都依赖同一个表达式的值时,else if并不是最佳的解决方案。在这种情况下,重复计算多条if语句中的表达式是非常浪费的做法。
switch语句适合处理这种情况。关键字switch之后紧跟着园括号括起来的一个表达式。随后是花括号括起来的代码块。
switch (expression) {
statements
}
然而switch语句完整的语法要比这更复杂一些。case之后是一个表达式和冒号,case和标记语很类似,只是这个标记语并没有名字。
它只和他后面的表达式关联在一起。当执行执行这条switch语句时,它首先计算expression的值,然后查找case子句的表达式是否和expression的值相同。(这里的相同是按照“===”运算符进行比较的),如果匹配case,它将会执行对应的代码。如果找不到匹配的case,它将会执行"default:"标签中的代码块。如果没有“default:”标签,switch将跳过所有的代码块。
switch语句是非常容易混淆的,用例子介绍会比较清晰一点,下面的switch语句和方才的if/else语句是等价的
switch (n) {
case 1: //如果n ===1从这里开始
//执行代码块1
break;
case 2:
//执行代码块2
break;
case 3:
//执行代码块3
break;
default:
//执行代码块4
break;
}
需要注意的是,每个case语句的结尾处都使用了关键字break。我们将后面介绍break语句,break语句可以使解释器跳出switch语句或循环语句。在switch中,case只是指明了要执行的代码起点,但没有指明终点。如果没有break语句,那么switch语句就从expression的值的匹配的case标签处代码开始执行,依次执行后续的语句,一直到整个switch代码块结束。当然,如果在函数中使用switch语句,可以使用return来替换break,return和break都用于终止switch语句,也会防止一个case语句执行完继续执行下一个case语句块。
下面的语句贴近实战,它根据值的类型将该值转换为字符串。
function convert(x) {
switch (typeof x) {
case 'number': //将数字转换为16进制
return x.toString(16);
case 'string':
return '"' + x + '"'; //返回两段带双引号的字符串。
default: //使用普通方法转换其它类型
return String(x);
}
}
console.log(convert(100255114)) //=>5f9c58a
注意,在上面的两个例子中,case关键字后跟随的是数字和字符串直接量,在实际中这是switch最常见的用法,但是ECMAScript标准允许每个关键字跟随任意的表达式。
switch语句首先计算switch 关键字后的表达式,然后按照从上到下的顺序计算每个case后的表达式,知道执行到case的表达式的值和switch的表达式的值相等时为止。由于对每个case的匹配操作实际上是“===”恒等运算符比较,而不是“==”,因此表达式和case的匹配并不会做任何类型转换。
每次执行switch语句的时候,并不是所有的case表达式都能执行到,因此,应当避免带有副作用的case表达式,比如函数调用的表达式和赋值表达式。最安全的做法就是在case表达式中使用常量表达式。
前面提到过,switch表达式与所有的case表达式都不匹配,则执行标记为“default:”的语句块,如果没有"default:"标签,则switch整个语句都跳过。在之前的例子中,“default:”标签都出现在switch末尾,位于所有case标签之后,当然这是最合理也是最常用的写法。实际上,“default:”标签可以放在switch语句内任何地方。
5.循环。
为了理解条件语句,可以将javascript中的代码想成一条条分支路径。循环语句(looping statement)就是程序路径的一个回路,可以让一部分代码重复执行。javascript中有四种循环语句:while、do/while、for、for/in下面几节会一次讲解他们。其中最常用的循环就是数组元素的遍历,(7.6会详细讨论这种循环和使用数组类定义的特殊循环方法。)
i.while
if语句是一种基本的控制语句,用来选择执行程序的分支语句。和if一样,while语句也是一个基本的循环语句,它的语法如下:
while (expression)
statement
在执行while语句之前,javascript解释器首先计算expression的值,如果它的值是假值,那么程序将跳过循环体中的逻辑statement转而执行程序中的下一条语句。如果它的值是真值,则执行循环体statement内的逻辑,然后再计算表达式expression的值,种循环会一直持续下去,知道expression的值为假值为止。换一种说法 就是表达式为expression是真值的时候则循环执行statement,注意,使用while(true)则会创建一个死循环。
通常来说,我们不想让javascript反复执行同一操作。在几乎每一次循环中,都会有一个或多个变量随着循环而迭代改变。正是由于改变了变量这些变量,因此每次循环执行的statement的操作也不尽相同,而且,如果改变变量在expression中用到,那么每次循环表达式的值也不同。这一点非常重要,负责初始值为真值的表达式永远是真值,循环也不会结束,下面的这个示例所示while循环输出0-9值。
var count = 0;
while (count < 10) {
console.log(count);
count++;
}
可以发现,在这个例子中,变量count的初始值为0,在循环的过程中,它的值每次都递增1,当循环执行了十次。表达式的值就编程了false,这时while就会结束,javascript解释器将执行程序下一条语句。大多循环都有一个像count这样的计数器变量。尽管计数器常用i j k这样的变量名,但如果想让代码的可读性更强,就应当使用更具体的语法名。
ii.do/while
do/while循环和while循环非常相似,只不过它是在循环的尾部而不是顶部检测循环表达式,这就意味这循环体至少执行一次。do/while循环的语法如下:
do
statement
while(expression);
do/while循环并不像while循环那么常用。这是因为在实践中想要循环至少执行一次的情况并不常见。下面是一个do/while循环的例子
function printArray(a) {
var len = a.length,
i = 0;
if (len == 0)
console.log("空数组");
else
do {
console.log(a[i]);
} while (++i < len); }
printArray([1,5,2,6])
在do/while循环和普通while循环之间有两点语法方面的不同之处。首先,do循环要求必须使用关键字do来标识循环的开始,用while变标识循环的结尾并进入循环条件判断;其次,和while循环不同,do循环使用分号结尾的。如果while的循环体使用花括号括起来,则while循环也不使用分号结尾。
iii.for
for语句提供了一种比while更方便的循环语句控制结构。for语句对常用的循环模式做了一些简化。大部分的循环都具有特定的计数器变量。在循环开始之前要初始化这个变量,然后在每次循环之前检查下它的值。最后,计数器变量做自增操作,否则就在循环结束后、下一次判断前做修改。在这类循环中,计数器的三个关键操作是初始化、检测和更新。for语句就将这三部操作明确声明为循环语法的一部分,各自使用一个表达式来表示。for语句的语法如下:
for (initialize; test; increment)
statement
intialize、test、increment三个表达式之间使用分号分隔,他们负责初始化操作、循环条件判断和计数器变量的更新。将它们放在循环的第一行会更容易理解for循环正在做什么,而且也可防止忘记初始化或者递增计数器变量。
要解释for循环是怎么样工作的,最简单方法就是列出一个与之等价的while循环
initialize
while (test) {
statement
increment;
}
换句话说,initialize表达式只在循环 开始之前执行一次。初始化表达式应当具有副作用(通常是一条赋值语句)。javascript同样允许初始化表达式中带有var变量声明语句,这样的话就可以声明并初始化一个变量。每次循环之前会执行test表达式,并判断表达式的结果来决定是否执行循环体。每次循环之前会执行test表达式,并判断其结果是否来执行循环体,如果test结果为真值,则执行循环体中的statement。最后,执行increment表达式。同样为了有用起见,这里的increment表达式也必须有副作用。通常来讲,它不是一个赋值表达式就是一个由“++”、“--”运算符构成的表达式。
上文的while循环可以使用for循环来从写
for (var count = 0; count < 10; count++)
console.log(count)
当然,有些循环更加复杂,而且循环中一次迭代多个变量。在javascript,这种情况必须用到逗号运算符,它将初始化表达式和自增表达式合并入一个表达式中以用于for循环。
var i, j;
for (i = 0, j = 10; i < 10; i++, j--)
console.log(i * j);
到目前为止,在示例代码中的循环变量都是数字。当然是数字是最常用的,但不是必须的。下面这段代码就使用for循环来遍历表数据结果,并返回链表中最后一个对象(也就是第一个不包含next属性的对象)
function tail(o) { //返回链表的最后一个节点对象
for (; o.next; o = o.next) /*empty*/ //根据判断o.next是不是真值来执行遍历
return o;
}
需要注意的是,这段代码不包含initialize表达式,for循环中的那三个表达式中的人和一个都可以忽略,但两个分号必不可少。如果省略test表达式,那么将是一个死循环。同样和while(ture)类型,死循环的令一种写法是for(;;)。
iiii.for/in
for/in语句使用for关键字,但它和常规的for循环是不同的一类循环。for/in循环的语法如下
for (variable in object)
statement
variable通常是一个变量名,也可以是一个可以产生左值的表达式或者一个通过var语句声明的变量。总之是一个适用于赋值表达式左侧的值。object是一个表达式,这个表达式的计算结果是一个对象。同样,statement是一个语句或语句块,它构成了循环的主体。
使用for循环来遍历数组元素是非常简单的
var a = [1, 3, 5, "44"];
for (var i = 0; i < a.length; i++) //i代表了数组元素的索引
console.log(a[i]) //输出每个数组的元素
而for/in循环则是用来方便的遍历对象成员属性
for (var p in o) //将属性的名字赋值给变量p
console.log(o[p]); //输出每一个属性的值
在执行 for/in语句的过程中,javascript解释器首先计算object表达式。如果表达式为null或undefined,javascript解释器将跳过循环并执行后续的代码。如果表达式等于一个原始值,这个原始值将会转换为与之对于的包装对象(wapper object)(3.6节)。否则,expression本身已经是对象了。javascript会依次枚举对象的属性来执行循环。然而在每次循环之前,javascript都会计算variable表达式的值,并将属性名(一个字符串)赋值给它。
需要注意的是,只要for/in循环中,varibale的值可以当做赋值表达式的左值,它可以是任意表达式。每次循环都会计算这个表达式,也就是说每次循环它计算的值可能不同。例如,可以使用下面的这段代码将所有对象属性复制到一个数组中:
var o = {x: 1,y: 2,z: 3};
var a = [],i = 0;
for (a[i++] in o) /*empty*/;
document.write(a)//=> x,y,z
javascript数组只不过是一种特殊的对象,因此,for/in循环可以像枚举对象属性一样枚举数据索引。例如在上面的代码之后添加这段代码,就可以枚举数据索引0,1,2:
var o = {x: 1,y: 2,z: 3};
var a = [],i = 0;
for (a[i++] in o) /*empty*/;
document.write(a)//=> x,y,z将对象属性复制到一个数组中
for(i in a)
document.write(i) //=>枚举数据索引 0 1 2
其实,for/in循环并不会遍历对象的所有属性,只有“可枚举”(enumerable)的属性才会遍历到(参照6.7)。由于javascript语言核心所定义的内置方法就不是“可枚举的”。比如,所有的对象都有toString(),但for/in循环并不枚举toString()这个属性。除了内置的方法之外,还有很多内置对象的属性是不可枚举的(nonenumberable)。而代码中定义的所有属性和方法都是可枚举的(6.7节会讲到,但ECMAScript5中有特殊手段可以使属性变为不可枚举)。
对象可以继承其它对象的属性,那行继承自定义属性(6.2.ii)也可以使用for/in枚举出来。
如果for/in的循环体删除了还未枚举的属性,那么这个属性将不会再枚举。如果循环体定义了对象的 新属性,这些属性通常也不会枚举到(不过。javascript有些实现可以枚举那么些在循环体中增加的属性)。
属性枚举的顺序
ECMAScript规范并没有指定for/in循环按照何种顺序来枚举对象的属性。但实际上,主流的浏览器厂商javascript实现是按照属性定义的先后顺序来枚举简单对象的属性,先定义的属性先枚举。如果使用对象直接量的形式创建对象,则将按照直接量中属性的出现顺序枚举。(有一些网和javascript库是依赖这种枚举顺序的,而浏览器厂商大多不修改这个顺序),在下面的情况下,枚举顺序取决于具体的实现(并非交互)
- 对象继承了可枚举属性
- 对象具有整数数组索引的属性
- 使用delete删除了对象已有的属性
- 使用Object.defineProperty()或者类似的方法改变了对象属性
6.跳转
javascript中令一类语句是跳转语句(jump statement)。从语句理解,它可以使javascript执行从一个位置跳转到令一个位置。
break语句是跳转到循环或其他的语句结束。continue语句是终止本次循环的执行并开始下一次循环的执行。javascript中的语句可以命名或带有标签,break和continue可以标识目标循环或者其它语句标签。
return语句可以让解释器跳出函数体的执行。并提供本次调用的返回值。throw语句触发或者抛出一个异常,它是与try/catch/finally语句一同使用的,这些语句指定了处理异常代码逻辑。这是一种复杂的跳转语句,当抛出一个异常的时候,程序将跳至最近的闭合异常辰星,这个异常程序可以是在同一个函数中或者更高层的调用栈中。
接下来,描述每一种跳转语句
i.标签语句
语句是可以添加标签的,标签是由语句前的标识符和冒号组成:
identifier:statement
通过给语句定义标签,就可以在程序中任何地方通过标签名来引用这条语句。可以对多条语句定义标签,尽管只有给语句块定义标签时它才有更有用,比如循环语句或条件判断语句。通过给循环定义一个标签名,可以在循环体内部使用break和continue来退出循环或者直接挑战到下一个循环开始。break和continue是javascript中唯一可使用语句标签的语句(本章接下来会讲述)。下面的例子,其中while循环定义了一个标签,continue语句使用了这个标签:
mainloop: while (token != null) {
//忽略这里代码...
continue mainloop; //跳转到下一次循环
//忽略这里的代码...
}
这里做标签的indentifier必须是一个合法的javascript标识符,而不能是一个保留字。标签的命名空间和变量或函数的命名空间是不同的,因此可以使用同一个标识符作为语句标签和作为变量名或函数名。语句标签只在它所起作用的语句(当然可以在它的子句)内是有定义的。一个语句标签不能和它内部的语句标签重名,但在两个代码不相互嵌套的情况下是可以出现同名语句标签的。带有标签的语句还可以带有标签,也就是说,任何语句可以有很多个标签。
ii.break
单独使用break语句的作用是立即退出最内存的循环或switch语句。它的语法如下:
break;
由于它能够使循环和switch语句退出,因此这种形式的break只能出现在这类语句中才是合法的。
我们在switch语句的例子中已经见到果break语句。在循环中,无论出于什么原因,只要不想继续执行整个循环,就可以用break提前退出。当循环终止条件非常复杂时,要函数体内使用break语句实现这样些条件判断的做法要比直接在循环表达式中写出这个复杂的终止条件做法简单的多。
下面的例子中循环遍历整个数组元素来查找某个特定的值,当整个数组遍历完成后正常退出循环,如果找到 了需要查找的数组元素,则使用break语句退出循环:
for (var i = 0; i < a.length; i++) {
if (a[i] == target) break;
}
javascript中同样允许break关键字后跟随一个语句标签,(只有标识符,没有冒号)
break labelname;
当break和标签一块使用时,程序将跳转到这个标签所识别的语句块的结束,或者直接终止这个闭合语句块的执行。当没有任何闭合语句块指定break所用的标签,这时会产生一个语法错误。当使用这种形式的break语句时,带标签的语句不应该是循环或者switch语句,因为break语句可以“跳出”任何闭合的语句块。这里的语句可以是由花括号组起来的一组语句,使用同一个标签来识别一组语句。
break关键字和labelname之间不能换行。因为javascript可以给语句自动补全省略掉的分号,如果break关键字和标签之间有换行,javascript解释器会认为你在使用break不带标签的最简形式,因此会在break后补充分号.
当你希望通过break来跳出非就近的循环体或者switch语句时,就会用到带标签的break语句。下面是示例代码:
var matrix = getData(); //从某处获得一个二维数组
//将矩阵中所有元素进行求和
var sum = 0,
success = false;
//从签名处开始,以便在报错时推出程序。
compure_sum: if (matrix) {
for (var x = 0; x < matrix.length; x++) {
var row = matrix[x];
if (!row) break compure_sum;
for (var y = 0; y < row.length; y++) {
var cell = row[y];
if (isNaN(cell)) break compure_sum;
sum += cell;
}
}
success = true;
}
//break语句跳转至此
//如果success =false条件到达这里,说明我们给出的矩阵中有错误
//否则对矩阵中所有的元素进行求和
最后,需要注意的是,不管break语句带不带标签,它的控制权都无法越过函数的边界。比如:对于一条带标签的函数定义语句来说,不能通过函数内部通过这个标签来跳转到函数外部.
iii.continue语句
continue语句和break语句非常类似,但它不退出循环,而是转而执行下一次循环。continue语句的语法和break的语句语法一样简单
continue;
continue语句会也会带有标签
continue lebname;
不管continue语句带不带标签,它只能在循环体使用,在其它地方使用将会 报语法错误。
当执行到continue语句的时候,当前的循环逻辑就终止了,随即执行下一次循环,在不同类型的循环中,continue的行为也有区别
- 在while循环中,在循环开始处指定expression会重复检测,如果检测结果为true,循环体会从头执行。
- 在do/while循环中,程序的执行至今跳转到循环的结尾处,这时会重新判断循环条件,之后才会继续下一次循环。
- 在for循环中,首先会计算自增表达式,然后再检测test表达式,用以判断是否执行循环体。
- 在for/in循环中,循环开始遍历下一个属性名,这个属性名赋给了指定的变量。
需要注意continue语句在while和for循环中的区别,while循环直接进入下一轮的循环条件判断,但for循环首先计算器increment表达式,然后判断循环条件。之前的章节讨论了和while循环“等价”的for循环行为。但由于continue在这两种循环中行为表现不同,因此使用while循环不可能完美的模拟等价的for循环。
下面这段代码展示了不带标签的continue语句,产生一个错误的时候跳过当前循环的后续逻辑
for (i = 0; i < data.length; i++) {
if (!data[i]) continue; //不能处理undefined数据
total += data[i];
}
和break语句类似,带标签的continue语句可以用在嵌套的循环中,用以跳出层次嵌套的循环体逻辑。同样和break语句类似,在continue语句和labname之间不能有换行。
iiii.return
回想一下,函数调用的一种表达式,而且所有的表达式都有值。函数中的return语句即是指函数调用后的返回值。这里是return语句的语法:
return expression;
return语句只能在函数体内出现,如果不是的话会报语法错误。当执行到return语句的时候,函数终止执行,并返回expression的值给调用程序。例如:
function square(x) {return x * x} //一个包含return的语句函数
square(4) //执行为16
如果没有return语句,则函数调用仅依次执行函数体内的每一条语句直到函数结束,最后返回调用程序。这种情况下,调用表达式的结果是undefined。return语句经常作为函数内最后的一条语句出现,但并不是说一定一定要放在函数的最后,即使在执行return语句的时候还有很多代码没有执行到,这时候函数也还返回调用程序。
return语句可以单独使用而不必带有expression,这样的话函数也会想调用程序返回undefined.例如:
//如果参数是null或者undefined则立即返回
if (!o) return;
//其它逻辑
由于javascript可以自动插入分号,因此,return关键字和它后面的表达式之间不能有换行。
iiiii.throw语句
所谓异常(excepion)是当发生了某种异常情况或错误时产生的一个信号。抛出异常,就是用信号通知发生了错误或异常状况。捕获异常是指处理这个信号,抛出异常,就是用信号通知发生了错误或异常状况。捕获异常是指处理这个信号,即采取必要的手段从异常中汇丰。在javascript中,当产生运行时错误或者程序使用throw语句时就会显式的抛出异常。使用try/catch/finally语句可以捕获异常,下一节会对它作详细介绍。
throw语句的语法如下:
throw expression
expression的值可以是任意类型的。可以抛出一个代表错误码的数组,或者包含可错误消息的字符串。当javascript解释器抛出异常的时候,通常采用Eeeor类型或其子类型,当然也可以使用它们。一个error对象有一个那么熟悉表示错误类型,一个message属性用来传递构造函数的字符串(参照第三部分的Error类),在下面的例子中,当使用非法参数调用函数时就抛出一个Error对象:
function fa(x) {
//如果输入的参数是非法的,则抛出一个异常
if (x < 0) throw new Error("x不能是负数。");
//否则计算出一个值,正常地返回它
for (var f = 1; x > 1; f *= x, x--) /*empty*/;
return f;
}
当抛出异常时,javascript解释器会立即停止当前正在执行的逻辑,并跳转至就近的异常处理程序。异常处理程序用try/catch/finally语句的catch从句编写的。如果抛出的异常没有一条关联catch从句 ,解释器会检测更高层的闭合代码块,看它是否关联相关的异常处理程序。以此类推 ,直到扎到一个异常处理的程序为止。
如果抛出的异常函数没有处理它的try/catch/finally语句,异常将向上传播到调用该函数的代码。这样的话,异常就会沿着javascript方法的词法结构和调用栈向上传播。如果没有找到任何异常处理的程序,javascript将吧异常当成程序错误来处理,并报告给用户。
iiiiii.try/catch/finally语句
try/catch/finally语句是javascript的异常处理机制。其中try从句定义了需要处理的异常所在代码块。catch语句跟随在try从句之后,当try块从某处发送了异常时,调用了catch内的代码逻辑。catch从句跟随finnlly块,后者防置了清理代码,不管try块中是否产生了异常,finnally块内的逻辑总会执行。尽管catch和finally都是可选的,但try从句只杀二者之一与组成完整的语句。try、catch和finally语句块都需要花括号括起来,这里的花括号是必须的。即使从句中只有一条语句也不能省略花括号。
下面的代码说明了try/catch/finlly的语法和使用目的:
try{
//通常来讲,这里的代码会从头执行到尾而不会产生任何问题,
//但有时会抛出一个异常,要么是由throw语句直接抛出异常
//要么通过调用一个方法间接抛出异常
}
catch(e){
//当且仅当try抛出了异常,才会执行这里的代码
//这里可以通过局部变量e来获得对Error对象或者抛出的其它值的引用
//这里的代码可以基于某种原因处理这个异常 ,也可以忽略这个异常。
//还可以通过throw语句重新抛出异常
}
finally{
//不管try语句块是否抛出看异常,这里的逻辑总会执行,终止try的语句块方式有:
//1)正常终止,执行完语句块的最后一条语句
//2)通过break,continue或return语句终止
//3)抛出一个异常,异常被catch从句捕获
//4)抛出一个异常,异常未被捕获,继续向上传播
}
我们注意到,关键字catch后跟随了一对圆括号,圆括号内是一个标识符。这个标识符和函数参很像。当捕获一个异常时,把这个异常相关的值(比如Error对象)赋值给这个参数。和普通的变量不同,这条catch子句中的标识符具有块级作用域,它只在catch语句块 内有定义。
这里有一个关于try/catch语句更实际的例子,这里使用了前面章节中提到factorial()方法,并使用客户端javascript方法prompt()和alert()来输入和输出
try {
//要求用户输入一个数字
var n = Number(prompt("请输入一个正整数", ""));
//假设输入是合法的,计算这个阶乘
var f = factorial(n);
//显示结果
alert(n + "!=" + f);
} catch (ex) {
//如果输入不合法,将执行这里的逻辑
document.write(ex); //告诉用户发送了什么。
}
这里的try/catch语句并不包含finally从句。尽管finally不像catch那样经常使用,但有时候它还是非常有用。然而,我们需要更详尽的解释它的行为。不管try语句块中的代码执行完成了多少,只要try语句中有一部分代码执行了,finally从句就会执行。它通常在try从句的代码后用于清理工作。
关注下面这个例子
try {
print("Outer try running..");
try {
print("Nested try running...");
throw "an error";
} catch (e) {
print("Nested catch caught " + e);
throw e + " re-thrown";
} finally {
print("Nested finally is running...");
}
} catch (e) {
print("Outer catch caught " + e);
} finally {
print("Outer finally running");
}
// Windows Script Host 作出该修改从而得出 WScript.Echo(s)
function print(s) {
document.write(s);
}
输出:
Outer try running..
Nested try running...
Nested catch caught an error
Nested finally is running...
Outer catch caught an error re-thrown
Outer finally running
7.其它语句类型。
本节讨论剩余的三种javascript语句:width,debugger和use strict
i.with语句
3.10讨论了作用域链(scope chain),一个可以按序检索的对象列表,通过它可以进行变量名的解析。width语句可以用来临时扩展作用域链:它具体有如下语法:
with (object)
statement
这条语句将object添加到作用域链头部,然后执行statement,最后把作用域链恢复到原始状态。
在严格模式下(5.7.iii)是禁止使用width的,在非严格模式下也是不推荐使用width语句的,尽可能的避免使用width语句。那些使用width语句的javascript非常难优化,而且比没有使用width的语句,它运行速度更慢。
在对象嵌套层次很深的时候,常会使用with语句来简化代码的编写。例如客户端javascript中,可能使用下面的这种表达式来访问表单的一个html元素
document.forms[0].address.value
如果这段代码多次出现,则可以使用with将form对象添加至作用域链的顶层。
with(document.forms[0]){
//直接访问表单元素
name.value="";
address.value="";
email.value ="";
}
这种方法简化了大量的输入,不用再为每个变量添加document.forms[0]前缀。这个临时对象挂载在作用域链上,当javascript需要解析诸如address标识符时,就会在这个对象中查找。当然,不使用with的语句代码可以写成这样。
var f = document.forms[0];
f.name.value = "";
f.adress.value = "";
f.email.value = "";
不要忘记,只有在查找标识符的时候才能用到作用域链,创建新的变量时候不使用它,看一下下面的代码:
with(o) x = 1;
如果对象o有一个属性x,那么这行代码给这个属性赋值1。如果o没有定义属性x,这段代码和不使用with的代码x=1是一模一样的。它给一个局部变量或者全局变量x赋值,或者创建全局对象的一个新属性。with语句提供了一种读取o属性的快捷方法,但并不会创建o的属性。
ii.debugger语句
debugger语句通常什么也不做。然而,在调试程序可用并运行的时候,javascript解释器将会(非必须)以调试模式运行。实际上,这条语句产生一个断点(breakpoint),javascript代码执行会停止在断点的位置,这时可用使用调速器输出变量的值,检查调用栈等。
例如加上调用函数f()的时候使用了未定义的参数,因此f()抛出一个异常,但无法定位到到底哪里出了异常。为了有助于调试这个问题,需要修改f():
function f(o){
if (o === undefined) debugger; //这段代码用来临时调试
console.log(1) //函数的其它部分
}
f();
这时候,当调用f()没有传入参数,程序将停止执行,这时候通过调用调速器检测调用栈并找出错误的原因。
在ECMAScirpt5中,debugger语句已经正式加入到专门语言里,但在很长的一段时间里,主浏览器的厂商已经将其实现了。注意,可用的调速器是远远不够的,debugger语句不会启动调试器。但如果调试器已经在运行,这条语句才会正在产生断点。例如,使用Firefox插件firebug,首先启动firebug,这样debugger语句才能工作。
iii.“use strict”
“use strict”是ECMASCript5引入的一条指令。指令不是语句(但非常接近于语句),“use strict”和普通语句之前有两个重要区别:
- 它不包含任何语言的关键字,指令仅仅是一个包含一个特殊字符串直接量的表达式(可以是使用单引号也可以是双引号)。
- 它只能出现在脚本代码的开始或者函数体的开始、任何实体语句之前。但它不必一定出现在脚本的首行或者函数体内的首行。因为“use strict”指令之前之后或之前都可能有其它字符串直接量的表达式语句,并且javascript的具体实现可能将它们解析为解释器自有的指令。在脚本或者函数体内第一条常规语句之后,字符串直接量表达式语句只当做普通的表达式语句对待,它们不做指令解析,它们也没有任何副作用。
使用“use strict”指令的目的是说明(脚本或函数中)后续代码解析为严格代码(strict code)。如果顶层(不在任何函数内)代码使用了“use strict”指令,那么它们就是严格代码。如果函数体定义处的代码是严格代码或者函数体使用了“use strict”指令,那么函数体的代码也是严格代码。如果eval()调用所处的代码是严格代码或者eval()要执行的字符串使用了“scrict code”指令,则eval()内的代码是严格代码。
严格代码以严格模式执行。ECMAScript5中的严格模式是该语言的一个受限的子集。它修正了语言的重要缺陷,并提供健壮的差错功能和增强安全机制。严格模式和非严格模式区别如下(前三条尤其重要)
- 严格模式中禁止使用with语句
- 严格模式中,所有的变量要先声明,如果给一个未声明的变量、函数、函数参数、catch从句参数或全局的对象的属性赋值。就会抛出一个引用错误异常(在非严格模式中,这种隐式声明全局变量的方法是给全局变量新添加一个新属性)
- 严格模式中,调用的函数(不是方法)中的一个this值是undefined。(在非严格模式中,调用的函数中的this值总是全局变量)。可以利用这种特性来判断javascript实现是否支持严格模式。
var hasStrictMode = (function() {
"use strict";
return this === undefined
}());
- 同样,在严格模式中,当通过call()和apply()来调用函数时,其中的this值就是通过call()或apply()传第一个参数(在非严格模式中,null和undefined值被全局对象转换为对象的非对象值锁代替)
- 在严格模式中,给只读属性赋值和给不可扩展的对象创建成员都将抛出一个类型错误异常(在非严格模式中,这些操作只是简单的操作失败,不会报错)。
- 在严格模式中,传入eval()代码不能再调用辰星所在的上下文中声明变量或定义函数,在非严格模式中是可以这样做的。相反,变量和函数的定义是在eval()创建的作用域中,这个作用域在eval()返回时就弃用了。
- 在严格模式中,函数里的arguments对象拥有传入函数值的静态副本。在非严格模式中,agreements对象具有“魔术般”的行为,arguments里的数组元素和函数都指向同一个值的引用。
- 在严格模式中,当delete运算符后面跟随非法的标识符(比如变量、函数、函数参数时)将会抛出一个语法错误,(在非严格模式下,这种delete什么也没做,并返回false)
- 在严格模式中,在一对象直接量中定义两个或多个同名属性将产生一个语法错误(非严格模式下不会报错)
- 在严格模式下,不允许八进制整数直接量。(以0为前缀,而不是0x为前缀)在非严格模式中是允许直接八进制直接量的
- 在严格模式下,标识符eval和arguments当做关键字,他们的值是不能更改的。不能给这些标识符赋值,也不能把它们声望为变量,用做函数名,用做函数参数或用做catch块的标识符。
- 在严格模式中限制了对调用栈的检测能力,在严格的模式的函数中,arguments,caller和arguments.callee都会抛出一个类型错误异常。严格模式的函数同样具有caller和arguments属性,当访问这两个属性时抛出类型错误异常。
8.javascript语句小结:
javascript语句语法:
语句 | 语法 | 用途 |
break | break[label]; | 退出最内侧循环或者退出switch语句,又或退出label指定的语句 |
case | case expression: | 在switch语句标记一条语句 |
continue | continue [label]; | 重新开始最内层的循环或从新开始label指定的循环 |
debugger | debugger; | 断点器调试 |
default | default; | 在switch标记默认语句 |
do/while | do statement while(expression); | while循环的一种替代形式 |
empty | ; | 什么都不做 |
for | for(init;test;incr)statement | 一种简写的循环 |
for/in | for(var in object)statement | 遍历一个对象属性 |
function | function name([param[],...]){body} | 声明一个函数 |
if/else | if (expr)statement1[else statement2] | 执行statement1或者statement2 |
label | label:statement | 给statement指定一个名字:label |
return | return [expression]; | 从函数返回一个值 |
switch | switch(expression){statements} | 用case或者“default:”语句标记多个分支语句 |
throw | throw expression | 抛出异常 |
try | try {statements} [catch {hander satements}] [finally {cleanup satements}] |
捕获异常 |
use strict | "use strict" | 对脚本和函数使用严格模式 |
var | avr name=[=expr][,...] | 声明并初始化一个或多个变量 |
while | while (expression) statement | 基本的循环结构 |
with | with(object) statement | 扩展作用域链(不赞成使用) |
(本文完结。请大家关注第六章解读:Javascript对象)