JavaScript权威指南--语句

时间:2022-08-27 08:14:13

知识要点

在javascript中,表达式是短语,那么语句(statement)就是整句或命令。表达式计算出一个值,但语句用来执行以使某件事发生

1.表达式语句

具有副作用的表达式是JavaScript中最简单的语句:赋值语句、递增递减、delete运算符、函数调用(alert(greet);)等等。

2.复合语句和空语句

将多条语句联合在一起,形成一个复合语句(compound statement)。只需花括号将多条语句括起来即可。下面几行代码可以当成一条单独的语句:

{
         x = Math.PI; cx = Math.cos(x); console.log("cos(π)=" + cx); }

关于语句块有几点需要注意:第一,语句块不需要分号。第二,语句块中的行都有缩进,为了可读性。第三,javascript没有块级作用域,在语句块中声明的变量并不是语句块所私有的。

将很多条语句合并成一个大语句块的做法在javascript编程中非常常见。类似的表达式通常包含子表达式一样,很多javascript包含其它子语句,从形式来讲,javascript通常允许一个语句块包含一条子语句。例如:while循环的循环体就可以只包含一条语句。使用语句块,可以将任意数量的语句放到这个块中,这个语句块可以当做一条语句来使用。

空语句(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都是声明语句,它们声明或定义变量或函数。有一个重要意义:通过创建变量和函数,可以更好的组织代码的语义。

3.1.var

var语句用来声明一个或者多个变量。关键字var之后跟随的是要声明的变量列表,列表中的每一个变量都可以带有初始化表达式,可用于指定它的初始值。

和其它全局对象属性不同的是,var声明的变量是无法通过delete删除的。

如果var语句中的变量没有指定初始化表达式,那么这个变量的值初始为undefined。所以,在声明语句之前的变量值就是undefined。

需要注意的是,var语句同样可以作为for循环或者for/in循环的组成部分。(在循环之前声明的变量声明一样,这里声明变量也会"提前")

注意,多次声明同一变量是无所谓的。

3.2.function

关键字function用来声明函数的,有两种写法:函数表达式和函数声明。

函数体是由javascript语句组成的,语句数量不限,且用花括号括起来。在定义函数时,并不执行函数体内的语句,它和调用函数时待执行的新函数对象相关联。注意,function函数语句里的花括号是必须的,这和while循环和其它一些语句锁使用的语句块是不同的,即使函数体只有一条语句,仍然需要花括号将其括起来。

函数的声明通常出现在javascript代码的最顶部,也可以嵌套在其他函数体内。但在嵌套时,函数声明只能出现在所嵌套的函数顶部。也就是说:函数定义不能出现在if、while、或其他语句中。正是由于函数声明位置的这种限制,ECMAScript标准规范并没有将函数声明归类为真正的语句。

和变量声明提前一样,函数声明也有声明提前。但是,使用var的话,只是变量声明提前了,变量的初始化代码仍然在原来位置。函数声明就不一样了,函数名称和函数体均提前,也就是说,可以在声明一个JavaScript函数之前调用它。

和var语句一样,函数声明语句创建的变量也是不可删除的。但是这些变量不是只读的,变量值可以重写。

4.条件语句

条件语句是通过判断指定的表达式的值是否来执行或跳过某些语句。

4.1.if

if (expression)
statement

需要注意的是,if语句括住expression的园括号是必须的。

javascript语法规定,if关键字和带园括号的表达式之后必须跟随一条语句。但可以使用语句块将多条语句合成一条。

if (expression)
    statement1
else
    statement2

当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"); //错误!!

上述代码javascript解释器是这么理解的:

if (i == j) {
   if (j == k)
        console.log("i equs k");
    else
        console.log("i dosent equal j");
}

和大多编程语言一样,javascript中的if、else匹配规则是,else总是和就近的if语句匹配,为了让个例子的可读性更强,更容易理解,更方便维护和调试,应当使用花括号。

4.2.else if

if/else语句通过判断一个表达式的计算结果来选择两条分支中的一条。

else if并不是真正的javascript语句,它只不过是多条if / else语句连接在一起的写法。

4.3.switch

当所有的分支都依赖同一个表达式的值时,else if并不是最佳的解决方案。在这种情况下,重复计算多条if语句中的表达式是非常浪费的做法。

switch语句适合处理这种情况。关键字switch之后紧跟着园括号括起来的一个表达式。随后是花括号括起来的代码块。

switch (expression) {
    statements
}
switch (n) {
    case 1: //如果n ===1从这里开始
        //执行代码块1
        break;  //停止执行switch语句
    case 2: //如果n ===2从这里开始
        //执行代码块2
        break;
    case 3:
        //执行代码块3
        break;
    default: //如果所有条件都不匹配
        //执行代码块4
        break;
}

如果在函数中使用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);
    }
}

注意,在上面的两个例子中,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中有四种循环语句:while、do/while、for、for/in。

5.1.while 

while (expression)
       statement

在执行while语句之前,javascript解释器首先计算expression的值。表达式为expression是真值的时候则循环执行statement,注意,使用while(true)则会创建一个死循环。

5.2.do/while

它是在循环的尾部而不是顶部检测循环表达式,这就意味这循环体至少执行一次。

 do 
       statement 
 while(expression);

在do/while循环和普通while循环之间有两点语法方面的不同之处。首先,do循环要求必须使用关键字do来标识循环的开始,用while变标识循环的结尾并进入循环条件判断;其次,和while循环不同,do循环使用分号结尾的。如果while的循环体使用花括号括起来,则while循环也不使用分号结尾。

5.3.for

在这类循环中,计数器的三个关键操作是初始化、检测和更新。for语句就将这三部操作明确声明为循环语法的一部分,各自使用一个表达式来表示。

for (initialize; test; increment)
     statement

当然,有些循环更加复杂,而且循环中一次迭代多个变量。在javascript,这种情况必须用到逗号运算符,它将初始化表达式和自增表达式合并入一个表达式中以用于for循环。

var i, j;
for (i = 0, j = 10; i < 10; i++, j--)
    console.log(i * j);

需要注意的是,for循环中的那三个表达式中的人和一个都可以忽略,但两个分号必不可少。如果省略test表达式,那么将是一个死循环。同样和while(ture)类型,死循环的令一种写法是for(;;)。

5.4.for/in

for (variable in object)
    statement

variable通常是一个变量名,也可以是一个可以产生左值的表达式或者一个通过var语句声明的变量。总之是一个适用于赋值表达式左侧的值。object是一个表达式,这个表达式的计算结果是一个对象。同样,statement是一个语句或语句块,它构成了循环的主体。

for/in循环则是用来方便的遍历对象成员属性:

for (var p in o) //将属性的名字赋值给变量p
    console.log(o[p]); //输出每一个属性的值

在执行 for/in语句的过程中,javascript解释器首先计算object表达式。如果表达式为null或undefined,javascript解释器将跳过循环并执行后续的代码。如果表达式等于一个原始值,这个原始值将会转换为与之对于的包装对象(wapper object)。否则,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*/;

javascript数组只不过是一种特殊的对象,因此,for/in循环可以像枚举对象属性一样枚举数据索引。例如在上面的代码之后添加这段代码,就可以枚举数据索引0,1,2:

for(i in a)console.log(i)

其实,for/in循环并不会遍历对象的所有属性,只有“可枚举”(enumerable)的属性才会遍历到(参照6.7)。由于javascript语言核心所定义的内置方法就不是“可枚举的”。比如,所有的对象都有toString(),但for/in循环并不枚举toString()这个属性。除了内置的方法之外,还有很多内置对象的属性是不可枚举的(nonenumberable)。而代码中定义的所有属性和方法都是可枚举的。对象可以继承其它对象的属性,那些继承自定义属性也可以使用for/in枚举出来。

如果for/in的循环体删除了还未枚举的属性,那么这个属性将不会再枚举。如果循环体定义了对象的 新属性,这些属性通常也不会枚举到(不过。javascript有些实现可以枚举那么些在循环体中增加的属性)。

属性枚举的顺序

ECMAScript规范并没有指定for/in循环按照何种顺序来枚举对象的属性。但实际上,主流的浏览器厂商javascript实现是按照属性定义的先后顺序来枚举简单对象的属性,先定义的属性先枚举。如果使用对象直接量的形式创建对象,则将按照直接量中属性的出现顺序枚举。

在下面的情况下,枚举顺序取决于具体的实现(并非交互):

  1. 对象继承了可枚举属性
  2. 对象具有整数数组索引的属性
  3. 使用delete删除了对象已有的属性
  4. 使用Object.defineProperty()或者类似的方法改变了对象属性

6.跳转

它可以使javascript执行从一个位置跳转到另一个位置。

javascript中的语句可以命名或带有标签,break和continue可以标识目标循环或者其它语句标签。

return语句可以让解释器跳出函数体的执行。并提供本次调用的返回值。throw语句触发或者抛出一个异常,它是与try/catch/finally语句一同使用的,这些语句指定了处理异常代码逻辑。这是一种复杂的跳转语句,当抛出一个异常的时候,程序将跳至最近的闭合异常辰星,这个异常程序可以是在同一个函数中或者更高层的调用栈中。

6.1.标签语句

语句是可以添加标签的,标签是由语句前的标识符和冒号组成:

identifier:statement

通过给语句定义标签,就可以在程序中任何地方通过标签名来引用这条语句。可以对多条语句定义标签,尽管只有给语句块定义标签时它才有更有用,比如循环语句或条件判断语句。通过给循环定义一个标签名,可以在循环体内部使用break和continue来退出循环或者直接挑战到下一个循环开始。break和continue是javascript中唯一可使用语句标签的语句。

mainloop: while (token != null) {
    //忽略这里代码...
    continue mainloop; //跳转到下一次循环
    //忽略这里的代码...
}

这里做标签的indentifier必须是一个合法的javascript标识符,而不能是一个保留字。标签的命名空间和变量或函数的命名空间是不同的,因此可以使用同一个标识符作为语句标签和作为变量名或函数名。语句标签只在它所起作用的语句(当然可以在它的子句)内是有定义的。一个语句标签不能和它内部的语句标签重名,但在两个代码不相互嵌套的情况下是可以出现同名语句标签的。带有标签的语句还可以带有标签,也就是说,任何语句可以有很多个标签。

6.2.break语句

单独使用break语句的作用是立即退出最内层的循环或switch语句。它的语法如下:

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语句带不带标签,它的控制权都无法越过函数的边界。比如:对于一条带标签的函数定义语句来说,不能从函数内部通过这个标签来跳转到函数外部。

6.3.continue语句

continue语句和break语句非常类似,但它不退出循环,而是转而执行下一次循环。continue语句的语法和break的语句语法一样简单。

不管continue语句带不带标签,它只能在循环体使用,在其它地方使用将会 报语法错误。

在不同类型的循环中,continue的行为也有区别:

  1. 在while循环中,在循环开始处指定expression会重复检测,如果检测结果为true,循环体会从头执行。
  2. 在do/while循环中,程序的执行至今跳转到循环的结尾处,这时会重新判断循环条件,之后才会继续下一次循环。
  3. 在for循环中,首先会计算自增表达式,然后再检测test表达式,用以判断是否执行循环体。
  4. 在for/in循环中,循环开始遍历下一个属性名,这个属性名赋给了指定的变量。

需要注意continue语句在while和for循环中的区别,while循环直接进入下一轮的循环条件判断,但for循环首先计算器increment表达式,然后判断循环条件。之前的章节讨论了和while循环“等价”的for循环行为。但由于continue在这两种循环中行为表现不同,因此使用while循环不可能完美的模拟等价的for循环。

和break语句类似,带标签的continue语句可以用在嵌套的循环中,用以跳出层次嵌套的循环体逻辑。同样和break语句类似,在continue语句和labname之间不能有换行。

6.4.return语句

函数中的return语句即是指函数调用后的返回值。

return expression;

return语句只能在函数体内出现,如果不是的话会报语法错误。

如果没有return语句,则函数调用仅依次执行函数体内的每一条语句直到函数结束,最后返回调用程序。这种情况下,调用表达式的结果是undefined。return语句经常作为函数内最后的一条语句出现,但并不是说一定一定要放在函数的最后,即使在执行return语句的时候还有很多代码没有执行到,这时候函数也还返回调用程序。

return语句可以单独使用而不必带有expression,这样的话函数也会向调用程序返回undefined。

由于javascript可以自动插入分号,因此,return关键字和它后面的表达式之间不能有换行。

6.5.throw语句

所谓异常(excepion)是当发生了某种异常情况或错误时产生的一个信号。抛出异常,就是用信号通知发生了错误或异常状况。捕获异常是指处理这个信号,即采取必要的手段从异常中恢复。在javascript中,当产生运行时错误或者程序使用throw语句时就会显式的抛出异常。使用try/catch/finally语句可以捕获异常。

throw语句的语法如下:

throw expression

expression的值可以是任意类型的。可以抛出一个代表错误码的数组,或者包含可错误消息的字符串。当javascript解释器抛出异常的时候,通常采用Error类型或其子类型,当然也可以使用它们。一个error对象有一个name属性表示错误类型,一个message属性用来传递构造函数的字符串。

在下面的例子中,当使用非法参数调用函数时就抛出一个Error对象:

function factorial(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将吧异常当成程序错误来处理,并报告给用户。

6.6.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语句更实际的例子:

try {
    //要求用户输入一个数字
    var n = Number(prompt("请输入一个正整数", ""));
    //假设输入是合法的,计算这个阶乘
    var f = factorial(n);
    //显示结果
    alert(n + "!=" + f);
} catch (ex) {
    //如果输入不合法,将执行这里的逻辑
    document.write(ex); //告诉用户发送了什么。
}

尽管finally不像catch那样经常使用,但有时候它还是非常有用。然而,我们需要更详尽的解释它的行为。不管try语句块中的代码执行完成了多少,只要try语句中有一部分代码执行了,finally从句就会执行。它通常在try从句的代码后用于清理工作。

通常情况下,解释器执行到了try块的尾部,然后开始执行finally中的逻辑,以便进行必要的清理工作。由于return、continue或break语句使得解释器跳出了try语句块时,解释器在执行新的目标代码之前先执行finally块中的逻辑。

如果在try中产生了异常,而且存在一条与之相关的catch从句来处理这个异常,解释器会先执行catch中的逻辑,然后执行finally的逻辑。如果不存在处理异常的局部catch从句,解释器会首先执行finally中的逻辑。然后向上传播这个异常,知道找到能处理这个异常的catch从句。

如果finally块中使用了return、continue、break或者throw语句使程序发生跳转,或者通过调用了抛出异常的方法改变了程序执行的流程,不管这个跳转使程序挂起还是继续执行,解释器都会将其忽略。例如finally从句抛出一个异常,这个程序将替代正在抛出的异常。如果finally从句运行到了return语句,尽管已经抛出了异常且这个抛出的异常还没有处理,这个方法依然会正常返回。

之前讲到,我们无法完全精确地使用while循环来模拟for循环,因为continue语句在两个循环中的行为表现不一致。如果使用try/finally语句,能够使用while循环来正确模拟包含continue的for循环:

//模拟for(initialize;test;increment)body;
initialize;
while(test){
try{body;}
finally{increment;}
}

然而需要注意的是,当body包含break语句时,while循环和for循环便有了更微妙的区别(造成一次额外的自增运算),因此即便使用了finally从句,使用while来完全模拟for循环依然是不可能的。

7.其他语句类型

剩余的三种javascript语句:width,debugger和use strict。

7.1.with语句

作用域链(scope chain),一个可以按序检索的对象列表,通过它可以进行变量名的解析。width语句可以用来临时扩展作用域链:它具体有如下语法:

with (object)
statement

这条语句将object添加到作用域链头部,然后执行statement,最后把作用域链恢复到原始状态。

在严格模式下是禁止使用with的,在非严格模式下也是不推荐使用with语句的,尽可能的避免使用with语句。那些使用with语句的javascript非常难优化,而且比没有使用with的语句,它运行速度更慢。

在对象嵌套层次很深的时候,常会使用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的属性。

7.2.debugger语句

debugger语句通常什么也不做。然而,在调试程序可用并运行的时候,javascript解释器将会(非必须)以调试模式运行。实际上,这条语句产生一个断点(breakpoint),javascript代码执行会停止在断点的位置,这时可用使用调速器输出变量的值,检查调用栈等。

假如加上调用函数f()的时候使用了未定义的参数,因此f()抛出一个异常,但无法定位到到底哪里出了异常。为了有助于调试这个问题,需要修改f():

function f(o){
    if (o === undefined) debugger; //这段代码用来临时调试
    .....       //函数的其它部分
}

这时候,当调用f()没有传入参数,程序将停止执行,这时候通过调用调速器检测调用栈并找出错误的原因。

在ECMAScirpt5中,debugger语句已经正式加入到专门语言里,但在很长的一段时间里,主浏览器的厂商已经将其实现了。注意,可用的调速器是远远不够的,debugger语句不会启动调试器。但如果调试器已经在运行,这条语句才会真正产生一个断点。例如,使用Firefox插件firebug,首先启动firebug,这样debugger语句才能工作。

7.3.“use strict”

“use strict”是ECMASCript5引入的一条指令。指令不是语句(但非常接近于语句),“use strict”和普通语句之前有两个重要区别:

  1. 它不包含任何语言的关键字,指令仅仅是一个包含一个特殊字符串直接量的表达式(可以是使用单引号也可以是双引号)。
  2. 它只能出现在脚本代码的开始或者函数体的开始、任何实体语句之前。但它不必一定出现在脚本的首行或者函数体内的首行。因为“use strict”指令之前之后或之前都可能有其它字符串直接量的表达式语句,并且javascript的具体实现可能将它们解析为解释器自有的指令。在脚本或者函数体内第一条常规语句之后,字符串直接量表达式语句只当做普通的表达式语句对待,它们不做指令解析,它们也没有任何副作用。

注意:如果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权威指南--语句