ES6 是 ECMAScript 的第 6 个版本。它与 2015 年 6 月正式发布,正式名称为 ECMAScript 2015,通常 ES6 泛指 ECMAScript 2015 及其后续的版本

ECMAScript 是一种由 ECMA 国际(前身为欧洲计算机制造商协会,英文:European Computer ManufacturersAssociation)通过 ECMA-262 标准化的脚本程序设计语言,它定义了该脚本语言的各种元素、结构、语法、运算符等等。而 JavaScript 则是该标准的具体实现

let 关键字

let 允许你声明一个作用域被限制在( {} )中的变量。与 var 关键字不同的是,let 声明的变量不会在作用域中被提升

let 声明与 var 声明的区别:

特点var 声明let 声明
变量提升YesNo
作用域函数级作用域块级作用域
重复声明YesNo
全局对象YesNo

变量提升:

1
2
3
/* 无变量提升 */
console.log(age); //报错:ReferenceError: age is not defined
let age = 38;

作用域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 块级作用域 */
/* 例1 */
{
let a = 666; // 我是局部变量a
{
let b = 'hello'
}
console.log(a); // 666
console.log(b); // 报错:ReferenceError: b is not defined
}
/* 例2 */
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
f1()

重复声明:

1
2
3
4
/* 同一个变量不可重复声明(同一作用域内) */
let num = 10;
let num = 20;
console.log(num) //报错:SyntaxError: Identifier 'num' has already been declared

全局对象:

1
2
3
/* 不会在全局对象中创建属性或方法 */
let a = 666;
console.log(window.a, a) //输出:undefined 666

const 关键字

const 关键字用于声明一个常量,其特点和 let 声明一样(存在块作用域),但常量的值是无法(通过重新赋值)改变的

例:

1
2
3
4
5
6
7
8
9
10
11
const fav = 7;
fav = 20; //报错:TypeError: Assignment to constant variable
//如果重新声明也会报错
const fav = 20;
var fav = 20;
let fav = 20;
//SyntaxError: Identifier 'fav' has already been declared

const obj = {name: 'Tom'};
obj.name = 'Bob'; //可以读写对象或数组中的属性
obj = {}; //改变obj的值(原始值 or 引用值)还是会报错

模版字符串(Template strings

模板字符串 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let mutLineStr = `string line1
string line2`;
console.log(mutLineStr);
/*
string line1
string line2
*/
let author = "李白";
let mustache = `
静夜思
${author}
xxx
`;
console.log(mustache)
/*
静夜思
李白
xxx
*/

对象字面量增强

相对于 ES5,ES6 的对象字面量得到了很大程度的增强。这些改进使 JavaScript 代码更加简洁同时更易于理解

对象属性简写

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* ES5 */
var type = 'rock';
var heat = '50%';
var music = {
type: type,
heat: heat
};
console.log(music); // Object {type: "rock", heat: "50%"}

/* ES6改写 */
var type = 'rock';
var heat = '50%';
var music = {
type,
heat
};
console.log(music); // Object {type: "rock", heat: "50%"}

对象方法简写

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* ES5 */
var type = 'rock';
var heat = '50%';
var music = {
type: type,
heat: heat,
description: function() {
return '当前音乐风格为' + this.type + ',' + this.heat + '都喜欢';
}
}
console.log(music.description()); // 当前音乐风格为rock,50%都喜欢

/* ES6改写 */
var type = 'rock';
var heat = '50%';
var music = {
type,
heat,
description() {
return `当前音乐风格为${this.type},${this.heat}都喜欢`; //使用模板字符串
}
}
console.log(music.description()); // 当前音乐风格为rock,50%都喜欢

PS:使用 ES6 简写的方法不能作为构造函数来调用( new ),否则将会报错

展开语法(Spread syntax

展开语法 可以在创建字面量对象/数组时,将对象/数组在语法层面展开

1.对象展开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 声明一个对象
let chinese = {
skin: "yellow",
hair: "black",
sayHi() {
console.log("Are you ok?");
},
};
let CXK = {
slill: "jump sing rap and play basketball",
song: "啊哈哈哈",
};
let linge = {
...chinese,
...CXK,
// 展开语法 等同于下面写法
/*
skin: "yellow",
hair: "black",
sayHi() {
console.log("Are you eat?");
},
slill: "jump sing rap and play basketball",
song: "啊哈哈哈",
*/
};
console.log(linge);

2.数组展开:

1
2
3
4
5
6
7
8
9
10
/* 例1 */
let arr1 = [10, 20, 30];
let arr2 = [40, 50, 60];
let arr3 = [...arr1, ...arr2, 70];
console.log(arr3); // [10, 20, 30, 40, 50, 60, 70]
/* 例2 */
// 求最大值
let arr1 = [10, 23, 54, 446, 56, 2];
let max = Math.max(...arr1);
console.log(max); // 446

解构赋值(Destructuring

解构赋值 可以将属性/值从对象/数组中取出,并赋值给其它变量

1.数组的解构赋值:

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 例1 */
let [a, b, c] = [1, 2, 3];
//a, b, c是从右边数组中按照 位置(,逗号分隔) 提取并赋值的3个变量(位置匹配)
console.log(a, b, c); //输出:1 2 3
let [ , y, z] = [4, 5];
console.log(y); // 5
console.log(z) // undefined (z在右边数组中没有元素对应,则会将 undefined 赋值给变量z)
let [a, [b], d] = [1, [2, 3], 4]; //等号(=)两边的结构要保持一致,才能取到嵌套的数组中的值
console.log(a, b, d) // 1 2 4
/* 例2 */
let x = 1;
let y = 2;
[x, y] = [y, x]; //交换变量的值
/* 例3 */
let [a=5, b=7] = [1]; //a=5, b=7为变量a, b的默认值
console.log(a); // 1
console.log(b); // 7 (b在右边数组中没有元素对应,所以使用默认值 7 赋值给变量b,而不是undefined)
/* 例4 */
let [v, m, n] = "大前端";
//如果等号右边的是一个字符串,则字符串会作为一个 类数组对象(String类型对象) 进行解构赋值
console.log(v, m, n); // 大 前 端

2.对象的解构赋值:

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 例1 */
let {x, y, z = 100} = {x: 10, y: 20}
//x, y, z是从右边对象中按照 名称 提取并赋值的3个变量(名称匹配)
console.log(x); // 10
console.log(y); // 20
console.log(z) // 100 (z在右边对象中没有属性对应,则会使用默认值 100 赋值给变量z)
/* 例2 */
//在上一个例子中变量必须与属性同名,才能取到正确的值,如果要单独指定变量名,可以使用下面的语法
var {a: num1 = 10, b: num2 = 5} = {a: 3}; //指定变量名并提供默认值
console.log(num1); // 3
console.log(num2); // 5
/* 例3 */
//对于嵌套结构的对象,可以使用下面的语法
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
console.log(x); // "Hello"
console.log(y); // "World"
console.log(p) // ["Hello", {y: "World"}]
/* 例4 */
//如果等号右边的是一个数值或布尔值,则会将其先转成对象,再进行解构赋值
let {toString: s} = 123; //Number类型对象
s === Number.prototype.toString // true
let {toString: s} = true; //Boolean类型对象
s === Boolean.prototype.toString // true

4.函数参数的解构赋值:

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 例1 */
function test2({ name, age, gender, height = 180 }) {
console.log(name, age, gender, height);
}
test2({
name: "波波",
age: 38,
gender: "男"
});
test2({
name: "波波",
age: 38,
gender: "男",
height: 160
});

PS:解构赋值语法实际上是一种 模式匹配 + 变量赋值 的混合,只要等号两边的模式相同,左边的变量就会被赋予对应的值

如:p: [x, { y }] 模式中 p 是模式匹配,但不是变量赋值,而 x 和 y 是模式匹配 + 变量赋值

箭头函数(Arrow function

箭头函数 是对函数表达式( 匿名函数 )的一种简写形式

语法:

1
2
3
4
5
6
7
8
9
10
11
12
let 函数名 = (形参1, 形参2 ... 形参N) => {
函数体
}
/*
简写规则:
1. function 关键字改成 =>
2. 如果只有一个形参,那就可以省略形参小括号 ()
3. 如果不只一个形参( 0 个或多个 ),那就不能省略形参小括号
4. 如果函数体只有一句话,那就可以省略函数体的大括号 {}
5. 如果函数体只有一句话,并且这一句话是 return 语句,那 return 关键字也可以省略
6. 如果函数体不是一句话,那就不能省略这个大括号
*/

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 例1 */
let fn1 = function (name) {
return name + "你好吗?";
};
// 改写
let fn1 = name => name + "你好吗?";
/* 例2 */
let fn5 = function (name, age) {
console.log(name + "你好吗");
return age + 10;
};
// 改写
let fn5 = (name, age) => {
console.log(name + "你好吗");
return age + 10;
};

参数默认值

函数形参可以指定一个初始值,当该形参没有传递实参时则该参数的值就是初始值( 默认值 )

例:

1
2
let fn = (a, b = 10) => console.log(a + b);
fn(10) // 20

剩余参数(Rest Parameters

如果函数的最后一个形参以...为前缀,则它将成为一个由剩余参数组成的数组

在箭头函数中不能通过 arguments 来获取实参

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 例1 */
function fun1(...theArgs) {
console.log(theArgs.length);
}
fun1(5, 6, 7); // 3
/* 例2 */
function multiply(multiplier, ...theArgs) {
return theArgs.map(function (element) {
return multiplier * element;
});
}
var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

this 指向

箭头函数中的 this 变量由上下文环境决定,而不是由 new 关键字来决定,因此箭头函数不能做为构造函数( new )来调用

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 例1 */
let Fn = (name, age) => {
this.name = name
this.age = age
}
var obj = new Fn('超哥', 20) // TypeError: Fn is not a constructor

/* 例2 */
var obj = {
name: 'obj',
persons: {
name: '波波',
sayHi: function () {
console.log('我的名字是1:', this.name) // 我的名字是: 波波
// 上文环境
setTimeout(() => {
console.log('我的名字是2:', this.name) // 我的名字是: 波波
setTimeout(() => {
console.log('我的名字是3:', this.name) // 我的名字是: 波波
}, 1000)
}, 1000)
// 下文环境
}
}
}
obj.sayHi()

class 关键字

ES6 提供了 class 关键字,用来定义一个类

语法:

1
2
3
4
5
6
7
8
9
/* 使用class声明语句来定义一个类 */
class 名称 [extends] {
// class body
}

/* 使用class表达式来定义一个类 */
const 名称 = class [名称] [extends] {
// class body
}

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class 声明
class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name); // Rectangle2

// class 表达式
let Rectangle = class { // 匿名类必须赋值到变量,变量名将成为匿名类的名称
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name); // Rectangle

类( class )

JavaScript 语言中,创建实例对象的传统方法是通过构造函数,如果要实现继承则需要借助原型

而 ES6 中的 class 依然是建立在原型上的,因此 ES6 中的 class 可以看作是一个语法糖,它使 JavaScript 在语法上更像面向对象

Js 中使用 class 声明的类,实质上是一种特殊的函数,它只能使用 new 关键字来调用,并且 class 声明不会有提升

例:

1
2
3
4
5
6
7
8
9
10
class Point {
// ...
}

console.log(typeof Point); // function
console.log(Point === Point.prototype.constructor) // true
/*
上面的案例可以看出Point类指向该类的构造函数
并且typeof检查Point类时也返回 function
*/

PS:class 中的代码都执行在严格模式( strict mode )下。比如,构造函数,静态方法,原型方法,getter 和 setter 都在严格模式下执行

构造方法( constructor )

constructor 是类中的一个特殊的方法,该方法用于创建和初始化一个由class创建的对象

一个类中只能有一个名为 “constructor” 的特殊方法。类中出现多个构造函数( constructor )方法将会抛出一个 SyntaxError 错误

如果没有指定构造函数( constructor )方法,则会使用一个默认的构造函数( constructor )来初始化实例

实例属性 / 原型方法

例:

1
2
3
4
5
6
7
8
9
class Foo {
bar = 'hello'; // 实例属性bar
baz = 'world'; // 实例属性baz

print() { // 原型方法,该方法会添加在原型prototype中
console.log(`${this.bar} ${this.baz}`);
}
}
(new Foo).print(); // hello world

在 ES5 中要给实例对象添加属性需要在构造函数或方法内使用 this 来添加,如果要添加方法则会将方法添加构造函数的原型prototype上。而上面的写法相比 ES5 的写法更加简洁,一眼就能看出,Foo类有两个实例属性和一个原型方法

getter & setter ( 存取值方法 )

在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 例1 */
class MyClass {
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();

inst.prop = 123; // setter: 123
console.log(inst.prop) // 'getter'

/* 例2 */
class Hero {
v = 100;
get getV() {
return this.v;
}
set setV(value) {
this.v = value;
}
}
var obj = new Hero();
console.log(obj.getV); // 100
obj.setV = 200;
console.log(obj.getV); // 200

静态属性 / 静态方法

如果在一个属性或方法前,加上static关键字,则该属性或方法将不会被添加到实例上,它是直接通过类来访问或调用

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* ES5 */
function Foo() {
//...
}
Foo.prop = 1 // 静态属性
Foo.classMethod = function () { // 静态方法
console.log(Foo.prop);
}
Foo.classMethod() // 'hello'

/* ES6 */
class Foo {
static prop = 1; // 静态属性
static classMethod() { // 静态方法
console.log(this.prop) // 静态方法中的 this 指向该类
}
}
Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod() // 报错:实例上找不到该方法

extends 关键字

extends 关键字 用于创建一个类作为另一个类的一个子类

例:

1
2
3
4
5
6
7
class Parent {} // 父类
class Child extends Parent { // 子类继承父类
constructor() {
super(); // 调用父类的构造函数constructor,用来初始化子类
this.age = 18;
}
}

类的继承

class 可以通过extends关键字实现继承,让子类继承父类的属性和方法

当子类继承某个父类时,子类必须先在constructor方法中使用super()来调用父类构造函数,否则就会报错

这是因为extends关键字在继承时,子类的实例需要通过父类得到。在调用super()方法时,会先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,因此如果不调用super()方法,子类就得不到自己的this对象

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point {
static s = 'static'
constructor(x, y) {
this.x = x;
this.y = y;
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
console.log(ColorPoint.s, ColorPoint.__proto__ === Point) // static true