YOU'VE MADE A BRAVE DECISION, WELCOME.

每一个不曾起舞的日子都是对生命的辜负。

学习笔记:ES6语法

1.let 和 const

let

ES6 引入了 let 用作声明变量,用法和之前的 var 类似,但是声明之后的效果却有很大的区别。

  • let 声明的变量作用域是块级的(block scope),var 声明名的变量是函数级的(function scope)。
  • let 声明的变量不存在作用域提升。不像 var,如果先使用后声明会取得值 undefined。如果先使用再用 let 声明,会报错。
  • let 在同一作用域下,不允许重复声明,而 var 可以。

const

const 声明一个只读的常量。

  • const 声明的常量,一旦声明,就必须立即初始化,不能留到以后赋值。
  • const 声明的常量作用域和 let 相同,也是块级的。
  • const 声明的常量也不存在作用域提升。
  • 在同一作用域下,和 let 一样,不能重复声明 const 常量。
  • 对于引用类型的变量,const 声明只是保证变量名所指向的地址不变,并不保证改地址保存的数据不变。

2. Destructuring

ES6允许按照一定模式,从数组或对象中提取值,对变量进行赋值,被称为解构(Destructuring)。其用法和 Ruby、Scala 等的模式匹配(Pattern Match)比较类似。

  • 数组

只要等号两边的模式相同,等号左边的变量就会被赋予相应的值。有两种特殊情况,一种是等号右边的值不能被解构,这时左边的变量值为 undefined;另一种是不完全解构,即等号左边的模式只匹配右边部分值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var [a, b, c] = [1, 2, 3];
//a = 1, b = 2, c = 3
let [e, f] = [4, 5];
//e = 4, f = 5
const [x, , y] = [1, 2, 3];
//x = 1, y = 3
let [head, ...tail] = [1, 2, 3, 4];
//head = 1, tail = [2, 3, 4]
let [x, y, ...z] = ['a'];
//x = 'a', y = undefined, z = []
  • 对象

字符串可以被转换成类似数组的对象,因此也可以解构赋值。

1
2
let [a, b, c, d] = 'peng';
//a = 'p', b = 'e', c = 'n', d = 'g'
  • 数值 & 布尔值

函数的参数支持结构赋值,其规则和数组、对象的解构规则一致。

1
2
3
4
5
6
7
8
9
10
11
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
function move({x, y}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]

3. Template String

在 ES6 里面引入了模板字符串,用反引号标识。它可以当作普通单行字符串使用,也可以作为多行字符串使用,可以嵌入变量,函数调用。模板字符串中嵌入变量,需要将变量名或函数调用写在${ }之中。

1
2
3
4
5
6
7
8
9
10
11
var [x, y] = [1, 2];
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
//foo Hello World bar

4. Class

JavaScript 在 ES6 之前,定义对象都是通过构造函数的方式进行的。如下

1
2
3
4
5
6
7
8
function Point(x,y){
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

ES6 引入了 extends 关键字, class 之间可以通过 extends 很方便的实现继承。

1
2
3
4
5
6
7
8
9
10
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

这里在 constructortoString 方法中都出现了 super 关键字。这个关键字有两种用法,一种是作为函数调用,此时 super 代表父类的 constructor 方法;另一种是作为对象调用,此时 super 代表的是父类,既可以应用父类实例的方法和属性,也可以引用父类的静态方法。如果子类定义了 constructor 方法,子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。这与 ES5 的继承有很大区别。如果子类没有显示定义 constructor 方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有 constructor 方法。

1
2
3
constructor(...args) {
super(...args);
}
  • 静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这种用法和其他的面向对象语言中的静态方法很类似。而且父类的静态方法可以被子类继承,在子类的定义中,可以通过 super 来调用;也可以通过子类直接调用该静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo {
static f() {
return 'hello';
}
}
class Bar extends Foo {
static b() {
return super.f() + ', too';
}
}
Foo.f(); //hello
Bar.f(); //hello
Bar.b(); //hello, too

6. Iterator

遍历器(Iterator)作为一种机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要支持Iterator,就可以完成遍历操作。概况地讲,Iterator 有三个作用:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令 for…of 循环,Iterator接口主要供 for…of 消费。

Iterator的遍历过程是这样的:

  • 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  • 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。
  • 第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。
  • 不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

7. Module

ES6 提出的 class 语法糖,在很大程度上方便了面向对象编程,但是并没有解决模块化的问题。追溯 JavaScript 的历史,一直没有添加对模块化的支持,导致在构建大型的复杂的系统时,拆分依赖,按需加载都不是很容易的事情。这也是为什么各大公司、社区争相放出各种规范的原因,比如 AMD、CMD。

ES6模块的设计思想,是尽量的静态化,使得在编译时就能确定模块的依赖关系,以及输入和输出的变量。而CommonJS和AMD模块,都只能在运行时确定这些东西,所以不能很好的实现按需加载。ES6的模块不是对象,而是通过 export 语句显式指定输出的代码,输入时也采用静态命令的形式。

1
2
// ES6模块
import {sin, cos} from 'Math';
  • export

模块功能主要由两个语句构成: export 和 import 。 export 语句用于规定模块的对外接口, import 语句用于输入其他模块提供的功能。一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export 语句输出该变量。下面是一个JS文件,里面使用 export 语句输出变量。

1
2
3
4
//profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

使用 export 语句定义了模块的对外接口以后,其他JS文件就可以通过 import 语句加载这个模块(文件)。

1
2
3
4
5
6
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}

引入变量的时候也可以进行重命名,比如:import { lastName as surname } from './profile';

  • 模块的整体加载

从前面的例子中,我们可以看到,想要 import 部分函数的时候,类库的使用者必须要知道其中到底包含了哪些函数。这个时候为了给使用者提供方便,我们可以使用 export default来为模块指定默认输出参数。

1
2
3
4
5
6
7
8
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面的 export 中声明了一个匿名函数,然后输出为默认值。所以在 import 的时候我们可以指定任意名字,因此这时 import 语句后面,不使用大括号。当然 export default 也适用于非匿名函数。

1
2
3
4
5
6
7
8
9
10
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;

上面例子中,函数名 foo 只在模块内部有效。加载的时候也会被视同为匿名函数。其实本质上,export default 就是输出一个叫做 default 的变量或函数,然后系统允许你为它取任意名字。


8. 其他

针对 ES6中的其他语法特性我就不在这里一一展开了,感兴趣的可以自行了解:Generator、Regular Expression、Symbol、Promise、Set、Map、Binary Array、Proxy、Reflect等。