第五章:Javascript语句

在javascript中,表达式是短语,那么语句(statement)就是整句或命令。正如英文语句以句号结尾,javascript以分号结尾。

表达式计算出一个值,但语句使某件事发生。

“使某件事发生”的一个方法是计算带有副作用的表达式。诸如赋值和函数调用这些有副作用的表达式,是可以作为单独的语句的。这种把表达式当做语句的用法也称做表达式语句(expression statement)。类似的语句还有声明语句(declaration statement),声明语句用来声明新变量或者定义新函数。

javascript程序就是一系列的可执行语句的集合,默认情况下,javascript解释器依照编写顺序依次执行。另一种“使某件事情”发生的方法就是改变语句的默认执行顺序:

  1. 条件语句(conditional)语句:javascript解释器可以根据一个表达式的值来判断;来执行还是跳过这些语句,例如if和switch语句。
  2. 循环语句(loop)语句:可以重复执行的语句,例如while和for语句
  3. 跳转(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(;;)

(未完待续)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。