语句

语句

JavaScript语句以分号结束,但书写代码时却可以省略分号,因为JavaScript引擎会自动补上分号。在C++中,语句必须以分号结尾。
分号将语句隔开,可以省略,会自动补上分号,但并不是在所有换行处都填补:只有在少了分号就无法正确解析代码的时候,才会填补分号。
通常,一条语句以[,(,/,+,-开始,那么它极有可能与前一条语句合在一直解析。
如果当前语句和下一行语句无法合并解析,则在当前语句后填补分号。但是有两个例外:
1. return, break, continue关键字后紧跟换行。
2. ++ ,–,如果用作后缀表达式,应当于表达式同行,否则行尾将填补分号,++,–将作为下一行的前缀操作。

表达式语句

我理解的表达式语句是只有表达式的语句。(废话,偷笑)
赋值语句是类比较重要的表达式语句:

greeting = "hello";
i *= 3;

自增(+)和自减(-)运算符用于改变一个变量的值,就像赋值语句一样:

counter++;

delete运算符一般用作表达式语句,而不是作为复杂表达式的一部分。

delete o.x;

函数调用是表达式语句的另一个大类。

alert(greeting);
window.close();

复合语句和空语句

复合语句

用花括号括起来的语句就是复合语句,也叫语句块。

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

需要注意的是:

  • 语句块的结尾不需要分号
  • 块中原始语句必须以分号结束
  • 语句块中的行都有缩进,但不是必需的
  • JavaScript中没有块级作用域

没有块级作用这一点确实需要注意,刚开始用JavaScript的时候,我还不知道这个特点,掉过坑。(尴尬,不过掉过坑印象深刻,恐怕一辈子都忘不掉!哈哈!)

空语句

只有一个分号的语句。
主要用于空循环:

for (var i = 0; i < a.length; a[i++] = 0) ;

但是,在圆括号后面的分号很不起眼,有可能造成一些致使的bug,而这些bug很难定位到。例如:

if (a == 0 || b == 0);  // 后面有个分号,一般是手误造成的
    c = null;       // 这行代码总是会执行

这段代码的效果可能不是程序作者想要的,所以如果有特殊目的需要使用空语句,最好在代码中添加注释:

for (var i = 0; i < a.length; a[i++] = 0) /*empty*/;

声明语句

var语句和function语句都是声明语句,用于声明或定义变量和函数。

var语句

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

var name1 [ = value1] [, ..., nameN [ = valueN]];

关键字var后跟随的是要声明的变量列表,第一变量都可以带有初始化表达式。例如:

var i;
var j = 0;
var p, q;
var x = 2, y = x * x;

如果var语句出现在函数体内,则定义的是一个局部变量,其作用域就是这个函数。
如果在顶层代码中使用var语句,则声明的是全局变量,其在整个JavaScript程序中都是可见的。全局变量是全局对象的属性,但是var声明的变量不能通过delete删除。
如果var语句中的变量没有指定初始化表达式,则其初始化值为undefined。变量在声明它们的脚本或函数中都是有定义的,相当于被“提前”至脚本或函数的顶部。但是初始化的操作还是在原来var语句的位置执行,在此之前变量的值是undefined。
var语句也可以作为for循环的组成部分(和在循环之外声明的变量声明一样,也会被“提前”):

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

多次声明同一个变量是无所谓的。

function语句

关键字function用来定义函数。
有两种定义写法:

var f = function (x) { return x; };
function f(x) { return x; }

函数声明的语法:

function funcName([arg1 [, arg2 [..., argn]]]) {
    statements;
}

需要注意的是,即使函数体只包含一条语句,仍然必须使用花括号将其括起来。
函数声明语句通常出现在JavaScript代码的最顶层,也可以嵌套在若何函数体内。但在嵌套时,函数声明只能出现在所嵌套函数的顶层,不能出现在if语句、while语句或其他任何语句中。
函数声明语句中的函数名是一个变量名,指向函数对象。
两种函数定义的写法都创建新函数对象,但仍然有不同之处:

  • 使用var的话,只有变量声明提前了,初始化仍然在原来的位置;
  • 使用函数声明语句的话,函数名和函数体均提前。所以,可以在声明一个JavaScript函数之前调用它。
    同样,函数声明语句创建的变量也是无法删除的,但可以重写。
function foo() {
    console.log("foo");
}
console.log(foo);
foo = 1;
console.log(foo);
//foo();    // Uncaught TypeError: foo is not a function

声明语句小结

  • 声明的变量,在其作用域内可以提前访问,但其值是undefined。
  • 声明的函数,由于被“提前”,在声明之前可以调用。
  • 通过函数声明语句定义的函数,函数名是一个变量名,可以任意修改其值
  • 声明出来的变量或者函数无法删除

这和C++ 中的相关概念截然不同,在C++中变量必须先声明,才能访问;同样,函数也是必须先声明才能调用。

条件语句

条件语句是通过判断指定表达式的值来决定执行还是路过某些语句。
条件语句在C++中叫做流程控制语句。

if语句

基本语法:

if (expression)
    statement1

if (expression)
    statement1
else
    statement2

计算expression的值,如果是真值则执行statement1,否则执行statement2。
JavaScript语法规定,if关键字和带圆括号的表达式之后必须一条语句,或者语句块。
else总是就近地匹配if。

i = j = 1;
k = 2;
if (i == j)
    if (j == k)
        console.log("i equals k");
else
    console.log("i doesn't equal j");

执行结果是:“i doesn’t equal j”,这显示是错误的。因为上面的代码实际被解释为:

if (i == j) {
    if (j == k)
        console.log("i equals k");
    else
        console.log("i doesn't equal j");
}

因此,为了避免这种情况,让代码可读性更强、更易理解、更易维护和调试,应当适当地使用花括号:

if (i == j) {
    if (j == k) {
        console.log("i equals k");
    }
} else {
    console.log("i doesn't equal j");
}

加上花括号之后,之前的歧义问题就避免了。
我的习惯是,不管是不是只有一条语句,都使用花括号,这是一个好习惯,建议大都这么做,不要懒。如果你没有这么做,相信我,这真是一个好习惯!

else if语句

if/else语句用于选择两条分支中的一条,但是有多条分支的时候,怎么办?一种办法是使用else if语句。
else if语句并不是真正的JavaScript语句,它只是多条if/else语句连在一直时的一种惯用写法。

if (n == 1) {
    // do something 1
} else if (n == 2) {
    // do something 2
} else {
    // do something 3
}

上面的代码等价于:

if (n == 1) {
    // do something 1
} else {
    if (n == 2) {
        // do something 2
    } else {
        // do something 3
    }
}

else if写法看起来更清晰,也更可取。

switch语句

switch 语句用来处理多分支情况。
语法:

switch (expression) {
    statements
}

代码块中可以使用多个由case关键字标识的代码片段,case之后是一个表达式和一个冒号。

switch (n) {
    case 1:
        //do something 1
        break;
    case 2:
        // do something 2
        break;
    default:
        // do something 3
        break;
}

JavaScript的switch语句和C++的差不多,首先计算expression的值,然后查找case子句中的表达式的值,找到则执行那个case的代码块;找不到则执行default标签的代码块;如果没有default标签,则将跳过所有代码块。
case只是指明了要执行的代码起点为,并没有指明终点。如果没有break语句,就会从匹配的case标签处开始依次执行,直到整个switch语句的结尾。
可以使用return来代替break。

switch (typeof x) {
    case "number":
        return x.toString(16);
        break;
    case "string":
        return '"' + x + '"';
    default:
        return String(x);
}

对每个case的匹配操作实际上是“===”恒等运算符比较,因此,表达式和case的匹配不会做任何类型转换。
由于每次执行switch语句时,并非所有case表达式都能执行到,比较的安全的做法就是在case表达式中使用常量表达式。
default:标签一般都出现在末尾,实际上,它可以出现在switch语句内的任何位置。这个在C++中不知道也是这样,回头去确认一下。不过,虽然可以在任意位置,但放在最后比较符合我们的思维习惯。

循环语句

循环语句就是程序路径的一个回路,可以让一部分代码重复执行。

while

语法:

while (expression)
    statement

示例:

var count = 0;
while (count < 10) {
    console.log(count);
    count++;
}

while(true)将导致死循环。

do…while

语法:

do
    statement
while (experssion);

do…while和while非常相似,do…while至少执行一次循环体。
do…while用分号结尾。这和C++中的do…while如出一辙。
示例:

function printArray(arr) {
    var len = arr.length;
    var i = 0;
    if (len === 0) {
        console.log("Empty Array");
    } else {
        do {
            console.log(a[i]);
        } while (++i < len);
    }
}

for

语法:

for (initialize; test; increment)
    statement

要解释for循环是如何工作的,列出一个与之等价的while循环即可:

initialize;
while (test)
    statement
    increment;

示例:

for (var count = 0; count < 10; count+++)
    console.log(count);

如果将test省略,那么将是一个死循环。和while(true)一新,for(;; )也是死循环。

for/in

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

for (variable in object)
    statement

variable通常是一个变量名,也可以是一个能产生左值的表达式或者一个通过var语句声明的变量。
object是一个表达式,其计算结果是一个对象。
for/in循环用来更方便地遍历对象属性成员:

for (var p in o)
    console.log(o[p]);

首先计算object表达式,如果得到null或者undefined,将路过循环;如果得到一个原始值,则这个值将会转换为与之对应的包装对象;否则表达式本身已经是对象了,则会依次枚举对象的属性来执行循环。
然而,在每次循环之前,都会先计算variable表达式的值,将将属性名(一个字符串)赋值给它。
JavaScript数组不过是一种特殊的对象,所以for/in也可以用来遍历数组,枚举数组索引。例如:

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

需要注意的是:得到的索引是字符串,在需要做算术运算的时,需要先转换为数字。
for/in循环并不会遍历对象的所有属性,只有“可枚举”的属性才会遍历到。由JavaScript语言核心所内置的方法就不是“可枚举”的,还有很多内置对象的属性也是“不可枚举的”。而代码中定义的所有属性和方法都是可枚举的。对象可以继承其他对象的属性,那些继承的自定义属性也可以枚举出来。
如果for/in的循环体删除了还未枚举的属性,那么这个属性将不会再枚举到。如果循环体定义了对象的新属性,这些属性通常也不会枚举到。
属性枚举的顺序
通常是按时属性定义的先后顺序来枚举简单对象的属性。如果使用对象直接量的形式创建对象,则按照直接量中属性的出现顺序枚举。
在下列情况下,枚举的顺序取决于具体的实现(并且是非交互的):

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

除了非继承的“自有”属性以外的继承属性往往都是可枚举的,而且可以按照它们定义的顺序进行枚举。如果对象属性继承自多个“原型”,那么原型链上的每一个原型对象的属性的遍历也是依照特定顺序执行的。JavaScript的一些(但不是全部)实现依照数字顺序来枚举数组属性,而不是某种特定的顺序。但当数组元素的索引是非数字或数组是稀疏数组(数组索引是不连续的)时它们则按照特定顺序枚举。

跳转语句

跳转语句使JavaScript的执行从一个位置跳转到另一个位置。

标签语句

语法:

identifier: statement

通过给语句定义标签,就可以在程序的任何地方通过标签名引用这条语句。
用作棱的identifier必须是一个合法的JavaScript标识符,而不能是一个保留字。
标签的全称昨变量或函数的命名空间是不同的,因此可以使用同一个标识符作为语句标签和作为变量名或函数名。
语句标签只有在它所起作用的语句(当然也可以在它的子句)内是有定义的。
一个语句标签不能和它内部的语句标签重名,但在两个代码段不相互嵌套的情况下是可以出现同名的语句标签的。
带有标签的语句还可以带有标签,也就是说,任何语句都可以有很多个标签。

break语句

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

break;

允许break关键字后面跟随一个语句标签(只有标识符,没有冒号):

break labelName;

当break和标签一块使用时,程序将跳转到这个标签所标识的语句块的结束,或直接终止这个闭合语句块的执行。
break和labelName之间不能换行,因为JavaScript可以给语句自动补全省略的分号。
不管是否带标签,break语句都无法越过函数的边界。

continue语句

continue语句不是退出循环,而是结束当前循环,执行下次循环。

continue;

同样可以带有标签:

continue labelName;

continue和labelName之间不能有换行。
不管带不带标签,它只能在循环体内使用。

return语句

return语句只能出现在函数内部,用于结束函数调用。
语法:

return;
或者
return expression;

不带expression的话,函数调用会返回undefined。
同样,return和expression之间不能有换行。

throw语句

所谓异常(exception),就是当发生了某种异常情况或错误时产生的一个信号。
抛出异常就是用信号通知发生了错误或异常状况。
捕获是指处理这个信号,即采取必要的手段从异常中恢复。
使用throw语句显式地抛出异常。
语法:

throw expression;

expression的值可以是任意类型的。
当JavaScript解释器抛出异常时,通常采用Error类型和其子类型。一个Error对象有一个name属性表示错误类型,一个message属性用来存放传递给构造函数的字符串。

function factorial(x) {
    if (x < 0) throw new Error("x cannot be nagitive");
    for (var f = 1; x > 1; f *= x, x--) /*empty*/;
    return f;
}

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

try/catch/finally语句

try/catch/finally语句是JavaScript的处理机制。其中try从句定义了目不暇接 异常所在的代码块。catch从句跟随在try从句之后,当try块内某处发生了异常时,调用catch内的代码逻辑。catch从句后跟随finally块,后者中旋转清理代码,不管try块中是否产生异常,finally块内的逻辑总是会执行。尽管catch和finally都是可靠的,但try从句需要至少二者之一与之组成完整的语句。try、catch和finally语句块都需要使用花括号括起来,而且是必须的。

try {
    //
} catch (e) {
    //
} finally {
    //
}

catch后跟随一对圆括号,圆括号内是一个标识符,这个标识符和函数参数很像。当捕获异常时,把和这个异常相关的值赋值给这个参数。和普通变量不同,catch子句中的标识符具有块级作用域,只在catch语句块内有定义。

try {
    var n = Number(prompt("Please input a positive integer", ""));
    var f = factorial(n);
    alert(n + "! = " + f);
} catch (ex) {
    alert(ex);
}

不管try语句块中的代码造势完成了多少,只要try语句中有一部分代码执行了,finally从句就会执行,它通常用于清理工作。
如果没有处理异常的局部catch从句,解释器会首先执行finally中的逻辑,然后身上传播这个异常,直到找到能处理这个异常的catch从句。

其他语句

with语句

with语句用于临时扩展作用域链,语法如下:

with (object)
statement

上面的代码将object添加到作用链的头部,然后执行statement,最后把作用域链恢复到原始状态。
在严格模式中,禁止使用with语句,在非严格模式下也不推荐使用。使用with语句的代码非常难优化,并且运行更慢。
在对象嵌套层次很深的时候通常会使用with语句来简化代码编写。例如:

document.forms[0].address.value
// 可以使用with语句将form对象添加到作用域链的顶层:
with (document.forms[0]) {
    // 直接访问元素,例如:
    name.value = "";
    address.value = "";
}

不要忘记,只有在查找标识符的时候都会乃至作用域链,创建新的变量时不使用它。

debugger语句

debugger语句是ECMAScript 5加入的语句,它通常什么也不做,在调用模式下,它用来产生一个断点。
在Node.js中,这样启用调试模式:

node debug xxx.js

“use strict”

“use strict”是ECMAScript 5引入的一条指令,非常接近语句,但不是语句。
“use strict”指令和普通的语句之间的区别:

  • 它不包含任何语言的关键字,仅仅是一个包含一个特殊字符串直接量的表达式
  • 它只能出现在脚本代码的开始或者函数体的开始、任何实体语句之前。不一定出现在脚本的首先或函数体内的首行

使用”use strict”指令的目的是说明后续的代码将会解析为严格代码。
严格代码以严格模式执行。ECMAScript 5中的严格模式是该语言的一个受限制的子集,它修正了语言的重要缺陷,并提供健壮的查错功能和增强的安全机制。
严格模式和非严格模式之间的区别:

  • 严格模式禁止使用with语句
  • 严格模式下,所有的变量都要先声明,如果给一个未声明的变量、函数、函数参数、catch从句参数或全局对象的属性赋值,将会抛出一个引用错误异常;在非严格模式下,则是给全局变量新添加一个属性
  • 严格模式下,调用的函数(不是方法)中的一个this值是undefined;非严格栻上,调用的函数的this值总是全局对象。可以利用这个特性来判断是否支持严格模式:
var hasStrictMode = (function () { "use strict"; return this === undefined;}());
  • 严格模式下,当通过call()或apply()调用函数时,其中的this值就是通过call()或apply()传入的第一个参数;在非严格模式下,null和undefined值被全局对象和转换为对象的非对象值所代替。
  • 严格模式下,给只读属性赋值和给不可扩展的对象创建新成员都将抛出一个类型错误异常;在非严格模式中,这些操作只是简单的操作失败,不会报错。
  • 严格模式下,传入eval()的代码不能在调用程序所在的上下文中声明变量或定义函数,而在非严格模式下可以。相反,变量和函数的定义是在eval()创建的新作用域中,这个作用域在eval()返回时就弃用了。
  • 严格模式下,函数里的arguments对象拥有传入函数值的静态副本;在非严格模式下,arguments对象的数组元素和函数参数都是指向同一个引用。
  • 严格模式下,当delete运算符后跟随非法的标识符时,将会抛出一个语法错误异常;在非严格模式下,则什么都不做,并返回false。
  • 严格模式下,试图删除一个不可配置的属性将抛出一个类型错误异常;非严格模式下,delete操作失败,并返回false。
  • 严格模式下,在一个对象直接量中定义两个或多个同名属性将产生一个语法错误;非严格模式下不会报错。
  • 严格模式下,函数声明中存在两个或多个同名的参数将产生一个语法错误;非严格模式下不会报错。
  • 严格模式下,不允许使用八进制整数直接量;非严格模式下允许使用。
  • 严格模式下,标识符eval和arguments当作关键字,它们的值是不能更改的。
  • 严格模式下,限制了对调用栈的检测能力,arguments.caller和arguments.callee都会抛出一个类型错误异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值