JavaScript语句以分号结束。表达式被计算以产生值,但是语句被执行以使某些事情发生。

表达式语句

JavaScript中最简单的语句是具有副作用的表达式,比如赋值语句、自增自减运算符、delete操作符和函数调用:

1
2
3
4
5
greeting = "Hello " + name;
i *= 3;
counter++;
delete o.x;
window.close();

复合语句和空语句

复合语句是包含在大括号中的语句序列,可以用在任何需要单个语句的地方。

1
2
3
4
5
{
    x = Math.PI;
    cx = Math.cos(x);
    console.log("cos(π) = " + cx);
}

空语句是只包含一个分号的语句。语法上需要一个语句,但逻辑上不需要,这个时候就用空语句。当你有意使用空语句时,最好注释一下,使别人可以明白你的意图。

声明语句

var

var语句声明一个或多个变量,语法如下:

1
var name_1 [ = value_1],[ ,..., name_n [= value_n]]

多次声明同一个变量是允许的。

function

函数声明语句具有以下语法:

1
2
3
function funcname([arg1 [, arg2 [..., argn]]]) {
    statements
}

函数声明语句可以出现在顶层JavaScript代码中,也可以嵌套在其他函数中。当嵌套时,函数声明只能出现在它们所嵌套的函数的顶层。也就是说,函数定义不会出现在if语句、while循环或任何其他语句中。

函数声明语句与函数定义表达式的不同之处在于它们包含一个函数名。这两种形式都创建了一个新的函数对象,但是函数声明语句也将函数名声明为变量并将函数对象赋值给它。与用var声明的变量一样,用函数定义语句定义的函数被隐式地“提升”到包含脚本或函数的顶部,这样它们在整个脚本或函数中都是可见的。对于var,只有变量声明被挂起——变量初始化代码保留在放置它的位置。然而,在使用函数声明语句时,函数名和函数体都会被提升:在运行任何其他代码之前,脚本中的所有函数或函数中的所有嵌套函数都会被声明。这意味着您可以在声明一个JavaScript函数之前调用它。

与var语句一样,函数声明语句创建的变量不能被删除。然而,这些变量不是只读的,它们的值可以被重写。

条件语句

条件语句根据指定表达式的值执行或跳过其他语句。这些语句是代码的决策点,有时也称为“分支”。

if

if语句是基本的控制语句,允许JavaScript有条件地执行语句。它有两种形式,第一种是:

1
2
if (expression)
    statement

if语句的第二种形式引入了else子句,该子句在表达式为false时执行:

1
2
3
4
if (expression)
    statement1
else
    statement2

else子句默认匹配最近的if语句。

switch

语法如下:

1
2
3
switch(expression) {
    statements
}

switch先计算表达式的值,然后查找和表达式值相等(使用===运算符)的case标签。如果找到一个,就执行case标记处的代码块。如果没有匹配的标签,则使用default标签。如果default标签,则完全跳过代码块。

break语句使解释器跳出switch语句继续执行后面的语句。switch语句的case子句只指定代码执行的起点,没有指定任何结束点。在没有break语句的情况下,switch语句在case标签处开始执行它的代码块,继续执行语句直到语句块的末尾。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function convert(x) {
    switch(typeof x) {
        case 'number': // Convert the number to a hexadecimal integer
            return x.toString(16);
        case 'string': // Return the string enclosed in quotes
            return '"' + x + '"';
        default: // Convert any other type in the usual way
            return String(x);
    }
}

ECMAScript标准允许case后面是任意表达式。

循环

JavaScript有四种循环语句:while、do/while、for和for/in。

while

语法如下:

1
2
while (expression)
    statement

do/while

语法如下:

1
2
3
do
    statement
while (expression);

for

语法如下:

1
2
for(initialize; test; increment)
    statement

在for循环中可以省略这三个表达式中的任何一个,但分号不能少。如果省略测试表达式,循环将永远重复。for(;;)相当于while(true),都是无限循环。

for/in

语法如下:

1
2
for (variable in object)
    statement

variable可以是任何结果为左值的表达式或声明单个变量的var语句。object是结果为对象的表达式。

JavaScript解释器首先计算object表达式。如果它的为null或undefined,解释器跳过循环并继续执行下一个语句。如果表达式的值为一个原始值,这个值被转换为其等效的包装器对象。然后解释器为对象的每个可枚举属性执行一次循环体。在每次迭代之前,解释器会将属性的名称赋给varaible。这意味着每次迭代它的值可能不同:

1
2
3
var o = {x:1, y:2, z:3};
var a = [], i = 0;
for(a[i++] in o) /* empty */;

JavaScript数组只是一种特殊的对象,数组索引是可以用for/in循环枚举的对象属性。

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

for/in循环不会枚举对象的所有属性,只有可枚举属性。用户定义的所有属性和方法都是可枚举的。用户定义继承的属性也是可枚举的。

如果在for/in循环的中删除了尚未枚举的属性,则该属性将不会被枚举。如果在循环中定义了新的属性,这些属性通常不会被枚举。

属性枚举顺序

ECMAScript规范没有指定for/in循环枚举对象属性的顺序。然而所有主流浏览器厂商的JavaScript实现都按照定义对象的顺序枚举对象的属性,先枚举旧的属性。对象字面值的枚举顺序与属性出现在字面值中的顺序相同。

一下情况枚举顺序依跟JavaScript不同实现有关:

  • 对象继承可枚举属性
  • 对象具有整数数组索引的属性
  • 使用delete删除对象的现有属性
  • 使用Object.defineProperty()或类似的方法来改变对象属性的属性

通常继承的属性在对象的所有非继承的“自有”属性之后枚举,按照定义属性的顺序。如果一个对象继承了多个“原型”的属性,即它的“原型链”中有多个对象,那么在枚举下一个对象的属性之前,按照创建顺序枚举链中的每个原型对象的属性。

跳转

语句标签

任何语句可以在前面加上标识符和冒号来标记:

1
identifier: statement

break和continue是唯一使用语句标签的JavaScript语句。

标签的命名空间与变量和函数的命名空间不同,因此语句标签可以和变量或函数名相同。语句标签只定义在它们所应用的语句中。语句标签本身也可以标记,这意味着任何语句都可能有多个标签。

break

单独使用的break语句导致循环或switch语句立即退出。JavaScript还允许break关键字后面跟着一个语句标签:

1
break labelname;

当与语句标签一起使用时,break跳转到指定标签语句的末尾。这种形式的break不需要用在循环或switch中,它可以跳出任何包围它的语句。break和标签名之间不允许有换行符。break语句不能跨越函数。

continue

语法如下:

1
2
continue;
continue labelname;

continue语句只能在循环体中使用。当执行continue语句时,当前迭代终止,下一个迭代开始。对于不同类型的循环,这意味着不同的东西:

  • 在while循环中,将再次测试循环条件。
  • 在do/while循环中,跳过循环体的底部,并再次测试循环条件。
  • 在for循环中,将计算递增表达式,并再次测试循环条件。
  • 在for/In循环中,将下一个属性名赋给指定的变量。

与break语句一样,continue语句的标记形式可以在嵌套循环中使用。同样,continue语句与其标签名之间不允许换行。

return

语法如下:

1
2
return;
return expression;

return语句只能出现在函数体中,出现在其他任何地方都是语法错误。当执行return语句时,包含该语句的函数将表达式的值返回给调用者。在没有return语句的情况下,函数调用依次执行函数体中的每个语句,然后返回undefined。单独的return语句也返回undefined。return语句与表达式之间不允许换行。

throw

语法如下:

1
throw expression;

expression可以是任何类型的值。

异常表示发生了某种异常情况或错误。抛出异常就是发出这样一个错误或异常信息的信号。捕获异常就是处理异常。在try/catch/finally语句中捕获异常。

当JavaScript解释器本身抛出一个错误时,使用Error类及其子类。Error对象有一个name属性,指定了错误的类型,以及一个message属性,用于保存传递给构造函数的字符串。

当抛出异常时,JavaScript解释器立即停止程序执行并跳到最近的异常处理代码。try/catch/finally语句的catch子句用于处理异常。如果抛出异常的代码块没有关联的catch子句,解释器将检查下一个包含代码块,看看它是否有关联的异常处理代码。这个过程一直继续,直到找到一个为止。异常在调用堆栈间向上传播。

try/catch/finally

语法如下:

1
2
3
4
5
6
7
try {
    code
} catch (e) {
    handler
} finally() {
    cleanup
}

catch和finally都是可选的,但是必须至少包含其中一个。finally中通常执行必要的清理工作,并保证会执行。

catch后面括号中的标识符类似于函数参数。当捕获异常时,与异常关联的错误对象被赋值给此参数。与普通变量不同,catch子句关联的标识符具有块作用域,它只在catch块中定义。

如果finally本身使用return、continue、break、throw语句,或者通过调用抛出异常的方法导致跳转,解释器将放弃正在发生的跳转并执行新的跳转。

其他语句

with

with语句用于临时扩展范围链。语法如下:

1
2
with (object)
    statement

该语句将对象添加到作用域链的前面,执行语句,然后将作用域链恢复到原来状态。

with语句在严格模式下是禁止的,在非严格模式下应该被认为是废弃的:尽可能避免使用它。使用with的JavaScript代码很难优化,并且可能比不使用with语句编写的等效代码运行得更慢。

1
2
3
4
5
with(document.forms[0]) {
    name.value = "";
    address.value = "";
    email.value = "";
}

作用域链仅在查找标识符时使用,不能用于创建新标识符:

1
with(o) x = 1;

如果对象o具有属性x,那么该代码将1赋给该属性。但是如果x没有在o中定义,那么这段代码与没有with语句的x = 1是一样的。

debugger

debugger语句通常什么也不做。这条语句就像一个断点,JavaScript代码的执行停止,您可以使用调试器打印变量的值,检查调用堆栈等等。

1
2
3
function f(o) {
    if (o === undefined) debugger; // Temporary line for debugging purposes
}

现在,当没有参数调用f()时,执行将停止,您可以使用调试器检查调用堆栈,并找出这个错误调用来自哪里。

“use strict”

“use strict"是ES5中引入的一个指令。“use strict"指令与常规语句有两个重要区别:

  • 它不包含任何语言关键字:这个指令只是一个表达式语句,由一个特殊的字符串字面值组成。没有实现ES5的JavaScript解释器只会看到一个没有副作用的表达式语句,什么也不做。未来版本的ECMAScript标准将引入use作为一个真正的关键字,并允许去掉引号。
  • 它只能出现在脚本的开头或函数体的开头,在任何实际语句出现之前。“use strict"指令前面或后面可以有其他字符串字面值语句,JavaScript实现允许将这些字符串字面值解释为各个实现自定义的指令。正常语句之后的字符串字面值语句只是普通的表达式语句,不会产生任何效果。

“use strict"指令的目的是指出后面的代码是严格的代码。严格的代码在严格模式下执行。ES5的严格模式是语言的一个受限子集,它修复了一些重要的语言缺陷,并提供了更强的错误检查和增强的安全性。严格模式和非严格模式的区别如下:

  • 在严格模式下不允许使用with语句。

  • 在严格模式下,必须声明所有变量。向未声明的标识符赋值,抛出ReferenceError。在非严格模式下,通过向全局对象添加新属性隐式地声明一个全局变量。

  • 在严格模式下,作为函调用的函数的this值为undefined。在非严格模式下,this值为全局对象。这个差异可以用来确定一个实现是否支持严格模式:

    1
    2
    
      var hasStrictMode = (function() { "use strict"; return this===undefined }());
      
    
  • 在严格模式下,当使用call()或apply()调用函数时,this值是传递给call()或apply()的第一个参数。在非严格模式下,null和undefined的值被替换为全局对象,非对象值被转换为对象。

  • 在严格模式下,对不可写属性的赋值和在不可扩展对象上创建新属性会抛出TypeError。在非严格模式下,悄悄失败。

  • 在严格模式下,eval()不能在包围它的作用域里面声明函数或变量。函数或变量的声明在eva()自己的作用域。在非严格模式下,eval()可以在调用者范围内声明变量或定义函数。

  • 在严格模式下,delete一个非限定标识符(例如变量、函数或函数参数),会抛出SyntaxError。在非严格模式下,这样的delete什么也不做,结果为false。

  • 在严格模式下,删除不可配置属性会抛出TypeError。在非严格模式下,delete返回false。

  • 在严格模式下,对象不能定义两个或多个相同名称的属性。在非严格模式下,不会发生错误。

  • 在严格模式下,函数声明不能有两个或多个相同名称的参数。在非严格模式下,不会发生错误。

  • 在严格模式下,不允许使用八进制整数。在非严格模式下,某些实现允许八进制数字。

  • 在严格模式下,标识符eval和arguments被视为关键字,不允许更改它们的值。

  • 在严格模式下,检查调用堆栈的能力受到限制。arguments.caller和arguments.callee都抛出TypeError。读取caller和arguments的属性也抛出TypeError。