五.JavaScript语句

157 阅读14分钟

表达式在JavaScript中是短语,那么语句(statement)就是JavaScript整句或命令。正如英文是用句号作结尾来分隔语句,JavaScript语句是以分号结束。表达式计算出一个值,但语句用来执行以使某件事发生。

1.复合语句和空语句

JavaScript中还可以将多条语句联合在一起,形成一条复合语句(compound statement)。只须用花括号将多条语句括起来即可。

image.png

在JavaScript中,当希望多条语句被当做一条语句使用时,使用复合语句来替代。空语句(empty statement)则恰好相反,它允许包含0条语句的语句。

;

JavaScript解释器执行空语句时它显然不会执行任何动作。但实践证明,当创建一个具有空循环体的循环时,空语句有时是很有用

image.png

在这个循环中,所有的操作都在表达式a[i++]=0中完成,这里并不需要任何循环体。然而JavaScript需要循环体中至少包含一条语句,因此,这里只使用了一个单独的分号来表示一条空语句。

2. 声明语句

var和function都是声明语句,它们声明或定义变量或函数。这些语句定义标识符(变量名和函数名)并给其赋值,这些标识符可以在程序中任意地方使用。声明语句本身什么也不做,但它有一个重要的意义,通过创建变量和函数,可以更好地组织代码的语义。

2.1 var

关键字var之后跟随的是要声明的变量列表,列表中的每一个变量都可以带有初始化表达式,用于指定它的初始值,例如:

var i; //一个简单的变量
var j = 0; //一个带有初始值的变量
var p, q; //两个变量
var greeting = "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程序中都是可见的。

变量在声明它们的脚本或函数中都是有定义的,变量声明语句会被“提前”至脚本或者函数的顶部。但是初始化的操作则还在原来var语句的位置执行,在声明语句之前变量的值是undefined。

2.2 function

关键字function用来定义函数。两种定义写法:函数定义表达式, 也可以写成语句的形式。

var f = function(x) {
  return x + 1;
}; //将表达式赋值给一个变量

function f(x) {
  return x + 1;
} //含有变量名的语句

在定义函数时,并不执行函数体内的语句,它和调用函数时待执行的新函数对象相关联。

函数声明语句通常出现在JavaScript代码的最顶层,也可以嵌套在其他函数体内。但在嵌套时,函数声明只能出现在所嵌套函数的顶部。也就是说,函数定义不能出现在if语句、while循环或其他任何语句中

尽管函数声明语句和函数定义表达式包含相同的函数名,但二者仍然不同。使用var定义函数的话,只有函数变量声明提前了——函数变量的初始化代码仍然在原来的位置。

image.png

然而使用函数声明语句的话,函数名称和函数体均提前:脚本中的所有函数和函数中所有嵌套的函数都会在当前上下文中其他代码之前声明。也就是说,可以在声明一个JavaScript函数之前调用它。

3. 条件语句

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

这些语句是代码的“决策点”,有时称为“分支”。如果说JavaScript解释器是按照代码的“路径”执行的,条件语句就是这条路径上的分叉点,程序执行到这里时必须选择其中一条路径继续执行。

3.1 switch

if语句在程序执行过程中创建一条分支,并且可以使用else if来处理多条分支。然而,当所有的分支都依赖于同一个表达式的值时,else if并不是最佳解决方案。switch语句正适合处理这种情况。

代码块中可以使用多个由case关键字标识的代码片段,case之后是一个表达式和一个冒号,case和标记语句很类似,只是这个标记语句并没有名字,它只和它后面的表达式关联在一起。

当执行这条switch语句的时候,它首先计算expression的值,然后查找case子句中的表达式是否和expression的值相同(这里的“相同”是按照“===”运算符进行比较的)。如果找到匹配的case,那么将会执行这个case对应的代码块。如果找不到匹配的case,那么将会执行"default:"标签中的代码块。如果没有"default:"标签,switch语句将跳过它的所有代码块。

switch (n) {
  case 1: //如果n===1,从这里开始执行
    //执行代码块1
    break; //停止执行switch语句
  case 2: //如果n===2,从这里执行
    //执行代码块2
    break; //在这里停止执行switch语句
  case 3: //如果n===3,从这里执行
    //执行代码块3
    break; //在这里停止执行switch语句
  default:
    //如果所有的条件都不匹配
    //执行代码块4
    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": //将数字转换为十六进制数
      return x.toString(16);
    case "string": //返回两端带双引号的字符串
      return '"' + x + '"';
    default:
      //使用普通的方法转换其他类型
      return String(x);
  }
}

switch语句首先计算switch关键字后的表达式,然后按照从上到下的顺序计算每个case后的表达式,直到执行到case的表达式的值与switch的表达式的值相等时为止。由于对每个case的匹配操作实际上是“===”恒等运算符比较,而不是“==”相等运算符比较,因此,表达式和case的匹配并不会做任何类型转换。

实际上,"default:"标签可以放置在switch语句内的任何地方。

4 for/in 循环

for/in语句也使用for关键字,但它是和常规的for循环完全不同的一类循环。

for(variable in object)
{
  statement
}

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

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

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

需要注意的是,只要for/in循环中variable的值可以当做赋值表达式的左值,它可以是任意表达式。每次循环都会计算这个表达式,也就是说每次循环它计算的值有可能不同。例如,可以使用下面这段代码将所有对象属性复制至一个数组中:

image.png

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

image.png

其实,for/i n循环并不会遍历对象的所有属性,只有“可枚举”(enumerable)的属性才会遍历到。由JavaScript语言核心所定义的内置方法就不是“可枚举的”。比如,所有的对象都有方法toString(),但for/in循环并不枚举toString这个属性。代码中定义的所有属性和方法都是可枚举的。对象可以继承其他对象的属性,那些继承的自定义属性也可以使用for/in枚举出来。

5. 跳转语句

  • break语句是跳转到循环或者其他语句的结束。
  • continue语句是终止本次循环的执行并开始下一次循环的执行。
  • return语句让解释器跳出函数体的执行,并提供本次调用的返回值。
  • throw语句触发或者“抛出”一个异常,它是与try/catch/finally语句一同使用的,这些语句指定了处理异常的代码逻辑

5.1 continue

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

不管continue语句带不带标签,它只能在循环体内使用。下面这段代码展示了不带标签的continue语句,当产生一个错误的时候跳过当前循环的后续逻辑:

for (i = 0; i < data.length; i++) {
  if (!data[i]) continue; //不能处理undefined数据
  total += data[i];
}

5.2 return

  • 函数中的return语句既是指定函数调用后的返回值。
  • return语句只能在函数体内出现,如果不是的话会报语法错误。当执行到return语句的时候,函数终止执行,并返回expression的值给调用程序。
  • 如果没有return语句,则函数调用仅依次执行函数体内的每一条语句直到函数结束,最后返回调用程序。这种情况下,调用表达式的结果是undefined。
  • return语句可以单独使用而不必带有expression,这样的话函数也会向调用程序返回undefined。

5.3 throw 语句

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

当JavaScript解释器抛出异常的时候通常采用Error类型和其子类型。

一个Error对象有一个name属性表示错误类型,一个message属性用来存放传递给构造函数的字符串,在下面的例子中,当使用非法参数调用函数时就抛出一个Error对象:

image.png

  • 当抛出异常时,JavaScript解释器会立即停止当前正在执行的逻辑,并跳转至就近的异常处理程序。异常处理程序是用try/catch/finally语句的catch从句编写的
  • 如果抛出异常的代码块没有一条相关联的catch从句,解释器会检查更高层的闭合代码块,看它是否有相关联的异常处理程序。以此类推,直到找到一个异常处理程序为止。
  • 如果抛出异常的函数没有处理它的try/catch/finally语句,异常将向上传播到调用该函数的代码。这样的话,异常就会沿着JavaScript方法的词法结构和调用栈向上传播。如果没有找到任何异常处理程序,JavaScript将把异常当成程序错误来处理,并报告给用户。

5.4 try/catch/finally

try/catch/finally语句是JavaScript的异常处理机制。

  • 其中try从句定义了需要处理的异常所在的代码块。
  • catch从句跟随在try从句之后,当try块内某处发生了异常时,调用catch内的代码逻辑。
  • catch从句后跟随finally块,后者中放置清理代码,不管try块中是否产生异常,finally块内的逻辑总是会执行。
try {
  //通常来讲,这里的代码会从头执行到尾而不会产生任何问题,
  //但有时会抛出一个异常,要么是由throw语句直接抛出异常,
  //要么是通过调用一个方法间接抛出异常
} catch (e) {
  //当且仅当try语句块抛出了异常,才会执行这里的代码
  //这里可以通过局部变量e来获得对Error对象或者抛出的其他值的引用
  //这里的代码块可以基于某种原因处理这个异常,也可以忽略这个异常,
  //还可以通过throw语句重新抛出异常
} finally {
  //不管try语句块是否抛出了异常,这里的逻辑总是会执行,终止try语句块的方式有:
  //1)正常终止,执行完语句块的最后一条语句
  //2)通过break、continue或return语句终止
  //3)抛出一个异常,异常被catch从句捕获
  //4)抛出一个异常,异常未被捕获,继续向上传播
}

关键字catch后跟随了一对圆括号,圆括号内是一个标识符。这个标识符和函数参数很像。当捕获一个异常时,把和这个异常相关的值(比如Error对象)赋值给这个参数。

使用前面提到的factorial()方法,并使用客户端JavaScript方法prompt()和alert()来输入和输出:

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

不管try语句块中的代码执行完成了多少,只要try语句中有一部分代码执行了,finally从句就会执行。它通常在try从句的代码后用于清理工作。

image.png

6 其他语句类型

6.1 with语句

with语句用于临时扩展作用域链

// document.forms[0].address.value
with (document.forms[0]) {
  //直接访问表单元素,例如:
  name.value = "";
  address.value = "";
  email.value = "";
}

6.2 debugger语句

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

debugger语句不会启动调试器。但如果调试器已经在运行中,这条语句才会真正产生一个断点。

6.3 “use strict”

"use strict"是ECMAScript 5引入的一条指令。它不包含任何语言的关键字,指令仅仅是一个包含一个特殊字符串直接量的表达式(可以是使用单引号也可以使用双引号)

它只能出现在脚本代码的开始或者函数体的开始、任何实体语句之前。但它不必一定出现在脚本的首行或函数体内的首行

使用"use strict"指令的目的是说明(脚本或函数中)后续的代码将会解析为严格代码(strict code)。如果顶层(不在任何函数内的)代码使用了"use strict"指令,那么它们就是严格代码。如果函数体定义所处的代码是严格代码或者函数体使用了"use strict"指令,那么函数体的代码也是严格代码。

严格代码以严格模式执行。ECMAScript 5中的严格模式是该语言的一个受限制的子集,它修正了语言的重要缺陷,并提供健壮的查错功能和增强的安全机制。

严格模式和非严格模式之间的区别如下:

  • 在严格模式中,所有的变量都要先声明,如果给一个未声明的变量、函数、函数参数、catch从句参数或全局对象的属性赋值,将会抛出一个引用错误异常(在非严格模式中,这种隐式声明的全局变量的方法是给全局对象新添加一个新属性)。
  • 在严格模式中,调用的函数(不是方法)中的一个this值是undefined。(在非严格模式中,调用的函数中的this值总是全局对象)。可以利用这种特性来判断JavaScript实现是否支持严格模式

image.png

7 JavaScript语句小结

image.png

image.png