首页 > 代码库 > JS基础知识回顾:ECMAScript的语法(一)

JS基础知识回顾:ECMAScript的语法(一)

任何语言的核心都必然会描述这门语言最基本的工作原理,而描述的内容通常都要涉及这门语言的语法、操作符、数据类型、内置功能等用于构建复杂解决方案的基本概念。

 

ECMAScript中的一切变量、函数名、操作符都区分大小写。

 

ECMAScript的标识符要符合下列规则:第一个字符必须是字母、下划线或美元符号;其他字符可以是字母、下划线、美元符号或数字。

标识符中的字母也可以包含扩展的ASCII或Unicode字母字符,但是并不推荐。

按照惯例,ECMAScript标识符采用驼峰大小写的格式来书写,尽管没有强制要求,但就经验看来这种写法是与内置函数和对象命名格式保持一致的最佳实践。

 

ECMAScript使用C风格的注释,包括单行注释和块级注释两种://单行注释、/*块级注释*/。

块级注释中也可以在每一行前面加入*号,来提高注释的可读性,这种格式在企业级应用中用得比较多。

 

ECMAScript5中引入了严格模式(strict mode)的概念,为JavaScript定义了一种不同的解析与执行模式。

在严格模式下,ECMAScript3中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。

在整个脚本的顶部添加代码"use strict";就可以针对整个脚本启动严格模式,在函数内部的上方写这句话用来指定函数在严格模式下执行。

这行代码看起来像是字符串,而且也没有赋值给任何变量,但其实它是一个编译指示(pragma),用于高速支持的JavaScript引擎切换到严格模式,这是为了不破坏ECMAScript3的语法而特意选定的语法。

在严格模式下JavaScript的执行结果会有很大的不同,支持严格模式的浏览器包括IE10+、Firefox4+、Safari5.1+、Opera12+和Chrome。

 

ECMAScript中的语句以一个分号结尾,如果省略分号,则由解析器确定语句和结尾。

虽然语句结尾的分号并不是必须的,但是加上分号可以避免不必要的错误,也能够让解析器省去判断在哪里插入分号的事件从而提高效率,开发人员也可以放心的通过删除多余空格来压缩代码。

可以使用C风格的语法把多条语句组合到代码块中,用大括号包裹,在控制语句中使用代码块可以让编码意图更加清晰,而且也能降低修改代码时出错的几率。

 

ECMA-262描述了一组具有特定用途的关键字,这些关键字可以用于表示控制语句的开始或结束,或者用于执行特定操作等。

按照规则,关键字也是语言保留的,不能用作标识符,一下就是ECMAScript的全部关键字(带*的是第五版新增的关键字):

break/case/catch/continue/debugger*/default/delete/do/else/finally/for/function

if/in/instanceof/new/return/switch/this/throw/try/typeof/var/void/while/with

ECMA-262还描述了另外一组不能用作标识符的保留字,尽管保留字在这门语言中还没有任何特定的用途,但他们有可能在将来会被用作关键字:

abstract/boolean/byte/char/class/const/debugger/double/enum/export/extends

final/float/goto/implements/import/int/interface/long/native/package/private

protected/public/short/static/super/synchronized/throws/transient/volatile

第五版把在费严格模式下运行时的保留字缩减为:class/const/enum/export/extends/import/super

在严格模式下,第五版还对以下保留字施加了限制:implements/interface/let/package/private/protected/public/static/yield

为了最大程度的保证兼容性,需要把第三版定义的保留字外加let和yield作为编程时的考量。

在实现ECMAScript3的JavaScript引擎中使用关键字做标识符,会导致"Identifier Expected"错误,而使用保留字做标识符可能会也可能不会导致相同的错误,具体取决于特定的引擎。

除了上述列出的保留字和关键字以外,ECMA-262第五版对eval和arguments还施加了限制,在严格模式下,这两个名字也不能作为标识符或属性名,否则会抛出错误。

 

ECMAScript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据,每个变量仅仅是一个用于保存值的占位符。

定义变量时要用var操作符,后面跟变量名,例如:var message;

以上声明了的变量可以用来保存任何值,像这样未经过初始化的变量,会保存一个特殊的值undefined。

ECMAScript也支持直接初始化变量,因此在定义了变量的同时就可以设置变量的值,例如:var message="hi";

也可以在修改变量值的同时修改值的类型,但是并不推荐,例如:var message="hi";message=100;

用var操作符定义的变量将成为定义该变量的作用域中的局部变量,也就是说,如果在一个函数中用var定义了一个变量,那么这个变量在函数退出后就会被销毁。

在局部作用域中省略var操作符可以定义一个全局变量,但是由于这种全局变量很难维护,而且有一忽略var操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱,所以这种做法并不推荐。

另外,给未经声明的变量赋值在严格模式下会导致跑出ReferenceError错误。

可以用一条语句定义多个变量,只要在每个变量间用逗号分隔开即可,初始化或者不初始化均可,例如:var message="hi",found=false,age=29;

ECMAScript是松散类型的,因而使用不同类型初始化变量的操作可以放在一条语句中来完成。

在严格模式下,不能定义名为eval或arguments的变量,否则会导致语法错误。

 

ECMA-262规定了一组语句(也称为控制流语句),从本质上看,语句定义了ECMAScript中的主要语法,语句通常使用一个或多个关键字来给定任务。

if(condition) statement1 else statement2

if语句中的condition(条件)可以是任意表达式,而且对这个表达式求值的结果不一定是布尔值,ECMAScript会自动调用Boolean()转换函数将这个表达式的结果转换为一个布尔值。

do{statement}while(expression);

do-while语句是一种后测试循环语句,即只有在循环体的代码执行之后,才会测试出口条件,这种语句最常用于循环体中的代码至少要被执行一次的情形。

while(expression) statement

while语句是前测试循环语句,在循环体内的代码被执行之前就会对出口条件求值,因此循环体内的代码有可能永远不会被执行。

for(initialization;expression;post-loop-expression) statement

for语句也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。

使用while循环做不到的,使用for循环同样也做不到,for循环只是把与循环有关的代码集中在了一个位置。

在for循环的变量初始化表达式中,也可以不使用var关键字,该变量的初始化可以在外部执行。

由于ECMAScript不存在块级作用域,所以在循环内部定义的变量在循环外面也是可以被访问到的。

for语句中的初始化表达式、控制表达式和循环后表达式都是可选的,将这三个表达式全部省略,就会创建一个无限循环,而只给出了控制表达式实际上就把for循环转换成了while循环。

for(property in expression) statement

for-in语句是一种精准的迭代语句,可以用来枚举对象的属性,ECMAScript对象的属性是没有顺序的,因此通过for-in循环输出的属性名的顺序是不可预测的。

如果表示要迭代的对象的变量值为null或undefined,for-in语句会抛出错误,ECMAScript5更正了这一行为,对这种情况不在抛出错误而只是不执行循环体。

为了保证最大的兼容性,应该在使用for-in循环之前先检测对象的值不是null或undefined。

另外,Safari3以前的版本针对for-in语句有一个bug,该bug会导致某些属性值被返回两次。

label:statement

使用label语句可以用于在代码中添加标签,以便将来使用,加标签的语句一般都要与for语句等循环语句配合使用。

例如:start:for(var i=0;i<count;i++){alert(i);}这个例子中定义的start标签可以在将来由break或continue语句引用。

break和continue语句用于在循环中精确的控制代码的执行,其中,break语句会立刻退出循环强制继续执行循环后面的语句,而continue语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。

break和continue语句都能后和label语句联合使用,从而返回代码中的特定位置,这种联合使用的情况多发生在循环嵌套的情况下。

例如:var num=0; outermost:for(var i=0;i<10;i++){for(var j=0;j<10;j++){if(i==5&&j==5){break outermost;}num++;}} alert(num);//55

例如:var num=0; outermost:for(var i=0;i<10;i++){for(var j=0;j<10;j++){if(i==5&&j==5){continue outermost;}num++;}} alert(num);//95

虽然连用break、continue和label语句能够执行复杂的操作,但如果过度使用也会给调试带来麻烦,因此在使用label语句时一定要使用描述性标签并避免嵌套过多循环。

with(expression) statement;

with语句的作用是将代码的作用域设置到一个特定的对象中,定义with语句的目的主要是为了简化多次编写同一个对象的工作。

var qs=location.search.substring(1);var hostName=location.hostname;var url=location.href;

这几行代码都包含location对象,可以利用with语句改写为:with(location){var qs=search.substring(1);var hostName=hostname;var url=href;}

在上述例子中,使用with语句关联了location对象,这就意味着with语句的代码块内部,每个变量首先被认为是一个局部变量,而如果在局部环境中找不到该变量的定义,就会查询location对象中是否有同名的属性,如果发现了同名属性,则以location对象属性的值作为变量的值。

严格模式下不允许使用with语句,否则将视为语法错误,并且由于大量使用with语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序是,不建议使用with语句。

switch(expression){case value:statement break;case value:statement break;case value:statement break;default:statement}

switch语句与if语句的关系最为紧密,每个case可以解释为表达式的值等于value的时候执行其对应的statement并跳出,如果没有符合的value则执行default中的statement。

通过为每个case后面都添加 一个break语句,就可以避免同时执行多个case代码的情况,加入确实需要混合几种情形,则最好加入注释说明是有意省略了break关键字。

ECMAScript中的switch语句借鉴自其它语言,但这个语句也有自己的特色,即可以在switch语句中使用任何数据类型,而且每个case的值既可以是常量又可以是变量甚至是表达式。

另外,switch语句在比较值时使用的是全等操作符,因此不会发生类型转换。

 

函数对于任何语言来说都是一个核心的概念,通过函数可以封装任意多条语句,并且可以在任何地方任何时候调用执行。

ECMAScript中的函数使用function关键字来声明,后跟一组参数以及函数体:function functionName(arg0,arg1,...,argN){statements}

函数可以通过其函数来调用,后面还要加上一对圆括号和参数,如果圆括号中的参数有多个则用逗号隔开。

ECMAScript中的函数在定义时不必指定是否返回值,实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。

函数中位于return语句后面的语句永远都不会被执行,一个函数中也可以包含多个return语句。

return语句也可以不用带有任何返回值,这种情况下,函数在停止执行后将返回undefined值,这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下。

推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值,否则会给调试代码调试带来不便。

在严格模式中不能把函数命名为eval或arguments,不能把参数命名为eval或arguments,不能出现两个命名参数同名的情况否则会导致语法错误。

 

ECMAScript函数有一个重要的特点:命名的参数只提供便利,但不是必须的。

由于ECMAScript中的参数在内部是用一个数组来表示的,所以ECMAScript并不介意传递进来的参数有多少个分别是什么数据类型,就算命名的参数与传递进来的参数个数并不相符也无所谓。

在函数体中可以利用arguments对象来访问这个参数数组,从而获得传递给函数的每一个参数,即第一个参数为arguments[0]、第二个参数为arguments[1]、以此类推,使用length属性可以确定传递进来的参数的个数。

arguments对象也可以与命名参数一起使用,arguments对象与命名参数的内存空间是独立的,但是它们的值会同步,arguments对象的长度由传入的参数个数决定,并不由定义函数时的命名参数的个数决定。

没有传递值的命名参数将自动被赋予undefined值,就像定义了变量但未进行初始化一样。

严格模式下对arguments对象作出了一些限制,首先,如果命名参数并未被传递进来,那么利用arguments为其赋值后该参数的值仍为undefined,其次,重写arguments的值会导致语法错误,代码将不会执行。

ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。

由于ECMAScript中的函数不能够重载,即如果定义了名字相同的两个函数后一个会覆盖掉前一个,所以要通过检查传入函数中的参数的类型和数量并作出不同的反应来模仿方法的重载。