ECMA-262第5版(ES5)定义的ECMAScript是目前使用最广泛的一个版本。

ECMA-262第6版(ES6)在浏览器中的实现程度次之,2017年底,大多数主流浏览器都支持了ES6。

本文所介绍的语法基于ECMAScript第6版。

语法

区分大小写

与大多数编程语言一样,严格区分标识符大小写。abc和ABC是完全不同的两个变量名。

标识符

标识符,即变量名、函数名、属性或参数。

  • 首字符必须是字母、下划线(_)、美元符号($)。
  • 剩下的字符可以是字母、下划线、美元符号或数字。

按照惯例,ECMAScript推荐使用驼峰命名法:第一个单词首字母小写,后面每个单词首字母大写。

注意:关键字、保留字、true、false和null不能用作标识符。

注释

ECMAScript采用C语言风格注释,包括单行注释和块注释。

1
2
3
4
5
//单行注释

/*
块注释
*/

严格模式

ECMAScript5增加了严格模式(strict mode)的概念。

严格模式是为了不破坏ECMAScript3语法,ES3中一些不规范的写法会被处理,对于不安全的活动抛出错误。

开启严格模式:

1
2
3
4
5
6
7
"use strict"	//在脚本开头加入,对整个脚本起作用。

//在函数开头,对整个函数起作用。
function doSomething(){
"use strict"
//函数体
}

语句

语句采用C风格,每个语句以分号结束。

并且使用花括号({})来标识代码块。

1
2
3
4
5
6
let sum = a + b;
let diff = a - b;
if (test){
test = false;
console.log(test);
}

注意:分号不是强制性的,但是规范的语句应当以分号结尾,这样会减少很多可能发生的错误。

关键字与保留字

ECMAScript262描述了一组保留的关键字。这些关键字有特殊用途,不能用作标识符或属性名:

1
2
3
4
5
6
7
8
9
break		do			in			typeof
case else instanceif var
catch export instanceof void
class extends return while
const finally super with
continue for switch yield
debugger function this
default if throw
delete import try

规范中也描述了一组未来保留字,以后可能会有特殊用途:

1
2
3
4
5
6
7
8
9
10
始终保留:
enum

严格模式下保留:
implement package public
interface protected static
let private

模块代码中保留:
await

变量

ECMAScript变量是松散类型的(弱类型),即变量可以保存任何类型的数据。

有三个关键字可以声明变量:var、const和let。

其中,var在ECMAScript所有版本中都可以使用。

而const和let只能在ECMAScript6及更晚的版本中使用。

var关键字

定义格式:var + 变量名

1
2
3
var test;	//声明变量,默认会保存特殊值undefined。
var message = "hi"; //声明变量同时初始化。
message = 100; //合法,但不推荐。

var声明作用域

在函数体外声明的变量默认为全局变量。

在函数体内声明的变量默认为局部变量。

1
2
3
4
5
function test(){
var message = "hi";
}
test();
console.log(message); //报错,test函数中局部变量message已经被销毁。

如果在函数体内部省略var关键字,声明的则是全局变量:

1
2
3
4
5
function test(){
message = "hi";
}
test();
console.log(message); //正确,message是全局变量。

但是,不推荐省略var,因为不确定省略var是不是有意为之。严格模式下给这样未声明的变量赋值会报错。

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

var声明提升

1
2
3
4
function test(){
console.log(message); //正确,message被提升到函数顶部。
message = "hi";
}

所有声明的变量会自动被拉升到作用域的顶部。但是还是建议在最顶部声明变量。

var多次声明同一变量

反复多次使用var声明同一个变量也没有问题:

1
2
3
4
5
function test(){
var age = 16;
var age = 60;
console.log(age); //结果:60
}

let声明

let和var作用差不多,但有着很重要的区别。

最明显的区别是:let声明的范围是块作用域,而var声明的范围是函数作用域。

1
2
3
4
5
if (true){
var name = 'Matt';
console.log(name); //Matt
}
console.log(name); //Matt
1
2
3
4
5
if (true){
let age = 26;
console.log(age); //26
}
console.log(age); //ReferenceError:age没有定义。

之所以会报错是因为,let声明的范围是块作用域,只在if语句块中有效。

并且,let也不容易多次声明同一个变量。

暂时性死区

let与var的另一个重要区别是:let声明的变量不会被提升。

1
2
3
4
5
6
7
// name会被提升
console.log(name); //undefined
var name = 'Matt';

// name不会被提升
console.log(name); //ReferenceError:name
let name = 'Matt';

全局声明

与var关键字不同,使用let关键字在全局作用域中声明的变量不会成为window对象的属性。

1
2
3
4
5
var name = 'Matt';
console.log(window.name); //'Matt'

let age = 18;
console.log(window.age); //undefined

条件声明

不能使用let进行条件声明,因为let具有块作用域。

for循环中的let声明

在let出现之前,循环定义的迭代变量会渗透到循环体外部:

1
2
3
4
5
for (var i = 0; i < 5; i++){
//循环体
}

console.log(i); //5

使用let后,这个问题就消失了。因为let作用域是块作用域。

const声明

const声明与let基本相同,唯一一个重要区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
const age = 26;
age = 36; //TypeError:给常量赋值

//const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; //SyntaxError

//const声明的作用域也是块
const name = 'Matt';。
if (true){
const name = 'Nicholas';
}
console.log(name); //Matt

const声明的限制只用于它指向的变量的引用。

如果const变量指向一个对象,这个对象的内部属性是可修改的,只是这个变量只能指向这个对象。

推荐的声明风格

不使用var

限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置。

const优先 let次之

如果某个变量不需要修改,优先使用const,这样不会导致误修改,能够迅速排查错误。

数据结构

ECMAScript有6种简单数据类型(也称原始类型):

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol

Symbol类型是ECMAScript6新增的。

还有一种复杂数据类型 Object(对象) ,这是一种无序名值对。

ECMAScript中不能定义自己的数据类型,所有数据都可使用上述7种类型来表示。

typeof操作符

因为ECMAScript的类型系统是松散的,所以需要一种手段确定变量的类型。

typeof操作符会返回下列字符串之一:

  • “undefined”表示值未定义
  • “boolean”表示值为布尔值
  • “string”表示值为字符串
  • “number”表示值为数值
  • “object”表示值为对象(而不是函数)或null
  • “function”表示值为函数
  • “symbol”表示值为符号
1
2
3
4
let message = "some string";
console.log(typeof message);//"string"
console.log(typeof(message));//"string"
console.log(typeof 95); //“number”

Undefined类型

Undefined类型只有一个值,特殊值undefined。

当使用var或let声明变量但未初始化时,相当于赋初值undefined。

Null类型

Null类型只有一个值,特殊值null。

逻辑上讲,null表示一个空对象的指针。

Boolean类型

Boolean(布尔值)类型是ECMAScript中最常用的类型。

有两个字面值:true和false,不同于数值类型。

通过Boolean()函数可以将非空字符串、非0、任意对象转换成true。

Number类型

NaN

NaN是一个特殊值,意思是“不是数值”。

非法运算,如分子是0时,会返回NaN。

如果分子非0,分母是0,相应的会返回Infinity和-Infinity表示正负无穷。

可以使用isNaN()函数判断一个数是否可以转换成数值。

数值转换

有三个函数可以将非数值转换为数值:Number()、parseInt()、parseFloat()。

Number是转型函数,可以用于任何类型。后两个函数主要用于字符串转换。

Number

Number函数基于下列规则转换:

  • 布尔值,true为1,false为0。
  • 数值,直接返回。
  • null,返回0。
  • undefined,返回NaN。
  • 字符串:
    • 空字符串:返回0
    • 数值或十六进制:返回对应数值
    • 其它情况:返回NaN

paserInt

从第一个非空字符开始检测,若第一个非空字符不是数值或+、-则返回NaN。

依次检测每个字符直至数值字符末尾,如parseInt(“ 12345ABC”) = 12345。

遇到小数点也会停止检测。

paserFloat

类似paserInt,遇到第一个小数点会继续检测,结果返回一个浮点数。

String类型

String类型标识零或多个16位Unicode字符序列。

字符串可以使用单引号(‘’)、双引号(“”)、反引号(``)进行标记。

字符串的长度可以通过length属性获取:

1
2
let text = "This is the letter sigma: \u03a3.";
console.log(text.length()); //28

转换字符串

有两种方式转换字符串:

第一种方式是使用toString()方法,可用于数值、布尔值、对象和字符串值。

这个方法返回一个副本,null和undefined没有toString()方法。

第二种方式是使用String()函数:

  • 如果值有toString()方法,则调用该方法不返回结果。
  • 如果值是null,返回”null”。
  • 如果值是undefined,返回”undefined”。

字符串插值

模板字面值是指用反引号(``)包围的字符串,会保留原有格式,可以跨行定义。

模板字面值支持将变量插入字符串中,进行格式化输出。

所有插入的值都会使用toString()强制转型为字符串。

1
2
3
4
5
6
let value = 5;
let exponent = 'second';

let str = '${ value } to the ${ exponent } power is ${ value * value }';

console.log(str); //5 to the second power is 25

模板中也可以调用函数和方法。

Symbol类型

Symbol(符号)是ES6新增的数据类型。

符号实例是唯一的、不可变的,符号作用的确保对象使用唯一标识符,不会发生属性冲突的危险。

符号基本用法

符号使用Symbol()函数初始化,符号本身是原始类型。

1
2
let sym = Symbol();
console.log(typeof sym); //symbol

Symbol函数允许接收String类型参数,这个字符串作为符号描述,将来通过这个String来调试程序。

但是,这个字符串参数和符号定义或标识完全无关。

全局符号注册表

如果运行时不同部分需要共享或重用符号实例,可以用字符串作为键,在全局符号注册表中创建并重用该符号。

为此,需要使用symbol.for()方法。

symbol.for()方法对每个字符串键执行幂等操作。

第一次使用某个字符串调用时,会创建一个新符号实例。

后续使用相同字符串调用时,会返回之前的符号实例。

1
2
3
4
let fooGlobalSymbol = Symbol.for('foo');	//在全局注册表中创建符号实例,键:'foo'
let globalSymbol = Symbol.for('foo'); //重用已有符号

console.log(gooGlobalSymbol == globalSymbol); //true

还可以使用Symbol.keyFor(符号实例)来查询对应的字符串键。

如果存在,则返回对应的字符串键。否则,抛出TypeError。

Object类型

ECMAScript中的对象其实就是一组数据和功能的集合。

对象通过new关键字和对象类型的名称来创建。

我们可以通过先创建Object类型的实例来创建自己的对象,然后加入属性和方法。

1
2
let o = new Object();
let o = new Object; //合法,但不推荐。

每个Object实例都有如下属性和方法:

  • constructor:用于创建当前对象的函数
  • hasOwnProperty(propertyName):用于判断当前对象实体上是否存在给定属性,参数是属性字符串。
  • isPrototypeOf(object):判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):判断给定的属性是否可以使用for-in枚举。
  • toLocalString():返回对象字符串表示,该字符串反映对象的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。

运算符

ES支持的运算符包括:

  • 自增/自减运算符、位运算符
  • 布尔运算符:逻辑与(&&)、逻辑或(||)、逻辑非(!)。
  • 加法、减法、乘法、除法、取模运算符
  • 相等运算符、关系运算符、赋值运算符
  • 条件运算符、逗号运算符

上述运算符和类C语言语法类似,不再详细说明。

全等运算符

有一类运算符需要特别注意:全等、不全等运算符。

相等运算符和类C语言一样,会将两边操作数类型转换一致后比较。

为了比较两数类型和数值都一致,ES增加了全等运算符:

1
2
3
4
5
let a = '666';
let b = 666;
console.log(a == b); //true,'666'被转换为666。
console.log(a === b); //false,不全等,因为二者类型不一致。
console.log(a!==b); //true,类型不一致,转换后值相等。