ES6
一、let 和 const
ES6中新增了let
和const
用于声明变量, 他们声明的变量都是块级作用域变量. 两者的区别是后者声明的是常量, 声明之后就不可以再改变的了.
1.1 只在当前代码块内有效
let
和const
声明的变量都是块级作用域的, 只在当前代码块内有效, 且外层代码块不受内层代码块影响:
1 | function testLetAndConst() { |
1.2 const声明常量, 值不能变
const
声明的变量是常量, 在声明变量的时候需要立刻赋值, 且值不能够再改变. 需要注意:
- 如果是基本数据类型, 那么就是他们的数值不能改变.
- 如果是引用类型, 那么这个变量的值表示的是引用类型的内存地址, 这个内存地址不能变. 但是这个内存地址指向的对象的属性是可以变的.
1 | const PI = 3.1415; |
1.3 全局环境的变量对象属性
全局环境的变量对象, 在浏览器环境指的是window
对象, 在Node指的是global
对象.
ES5中顶层全局环境的变量对象的属性和全局变量是等价的.
1
2
3
4
5
6
7
8
9
10
11window.a = 1;
// 1
console.log(a);
// 2
a = 2;
console.log(window.a);
var b = 1;
// 1
console.log(b);
console.log(window.b);在ES6中,
let
,const
声明的全局变量不属于全局环境的变量对象的属性.1
2
3
4
5
6
7var a = 1;
// 1
console.log(window.a);
let b = 1;
// undefinded
console.log(window.b);
二、解构赋值
按照一定的模式从数组或者对象中提取值, 对变量进行赋值, 这被称为解构.
2.1 数组的解构
可以使用解构赋值快速地从数组中拿出值赋予变量, 五点重要特性:
如果解构不成功, 那么值为
undefined
.如果右边不是可遍历结构(iterator), 那么会报错.
可以为变量设置默认值.
可以使用rest运算符
...
只要等号两边模式相同, 左边就会被赋予对应的值.
1 | // 不使用解构赋值, 需要使用下标来获取值 |
2.2 对象的解构
解构也可以用于从对象中获取属性值. 注意点:
- 对象解构赋值是根据属性名来赋值的(不像数组是按次序的), 要获取的变量的名字应该跟相应的属性名相同.
- 如果想重命名变量的名字(变量名取新的名字, 不跟属性名相同), 那么使用
:
隔开. - 对象解构可以用于嵌套解构的对象.
- 可以使用rest运算符
...
1 | let person = { |
2.3 函数参数的解构赋值
参数中使用到解构声明的函数在调用的时候会自动将实参进行解构传入函数体中执行.
1 | // 解构数组的函数参数 |
2.4 解构的用处
2.4.1 从函数返回多个值
有了数组解构后, 可以将多个值放在数组里面返回, 然后使用解构来获取这些值.
1 | function example() { |
2.4.2 提取JSON数据
解构赋值可以很方便地取出对象中的数据.
1 | let jsonData = { |
三、语言扩展
3.1 字符串的扩展
3.1.1 模板字符串
传统方式下的字符串都是在一行的, 对于一个很长的字符串要换行的话只能使用+
号进行连接. ES6模板字符串是增强版的字符串, 使用(`)标识. 它具有如下特性:
- 可以充当普通字符串.
- 可以用来定义多行字符串(所有换行和空格都会保存在输出中).
- 可以在字符串中嵌入表达式
${expression}
.
1 | // 普通字符串 |
3.1.2 标签模板
模板字符串跟在一个函数后面, 那么这个函数会被调用来处理这个模板字符串, 这被称为标签模板功能. 如果模板字符串里面有变量, 那么会将模板字符串先处理成多个参数, 然后再调用函数.
1 | alert`abc`; |
3.2 函数的扩展
3.2.1 函数参数默认值
ES6之前不能为函数的参数提供默认值. 在ES6中可以将值直接写在参数定义的后面为一个函数的参数定义默认值.
1 | function Point(x = 0, y = 0) { |
3.2.2 rest参数
ES6引入了rest参数(形为…变量名), 用于获取函数的多余参数, 这样就不需要使用arguments
对象了. rest参数搭配使用的变量是一个数组, 该变量将多余的参数放入数组中.
1 | function add(...values) { |
3.2.3 箭头函数
ES6中使用=>
来定义箭头函数. 注意点:
- 如果箭头函数的代码块部分有多于一条语句, 那么使用大括号括起来, 使用
return
语句返回. - 如果只有一条语句, 且这条语句是一个值, 那么不需要大括号, 不需要
return
, 这个语句的值会自动当成返回值返回. - 注意: 如果只有一条语句, 但是这条语句是一个对象(即该函数返回这个对象), 那么需要用小括号将这个对象括起来.
- 函数体内的
this
是定义时所在作用域的this
对象, 而不是使用该函数时所在的对象.
1 | // 多条语句, 需要加上大括号 |
3.2.4 双冒号运算符
当我们想为一个函数绑定this
对象的时候通常会使用call
, apply
, bind
方法. 现在我们可以使用::
运算符来取代这些方法了. 双冒号运算符::
左边是一个对象, 右边是一个函数, 运算符会自动将左边的对象作为上下文环境this
绑定到函数上面.
1 | foo::bar; |
3.3 对象的扩展
3.3.1 扩展运算符
对象的扩展运算符...
用于取出参数对象的所有可遍历属性, 拷贝到当前对象之中.
1 | let jeb = {name: 'jeb', age: 22}; |
四、对象新增方法
4.1 Object.is()
ES5中可以通过==
和===
来进行值想等的比较, ES6中新增了Object.is()
方法. Object.is(1, 1);
4.2 Object.assign()
Object.assign()
用于将源对象的所有可枚举属性赋值到目标对象.
1 | const target = { a: 1 }; |
五、Set和Map数据结构
5.1 Set
Set
的成员是唯一的, 初始化的时候可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数.
Set
的属性:
Set.prototype.constructor
Set.prototype.size
: 返回Set
实例的成员总数.
Set
的方法:
add(value)
: 添加某个值, 返回 Set 结构本身.delete(value)
: 删除某个值, 返回一个布尔值, 表示删除是否成功.has(value)
: 返回一个布尔值, 表示该值是否为Set
的成员.clear()
: 清除所有成员, 没有返回值.keys()
: 返回键名的遍历器.values()
: 返回键值的遍历器.entries()
: 返回键值对的遍历器.forEach()
: 使用回调函数遍历每个成员.
5.2 Map
1 | const map = new Map(); |
六、Promise对象
Promise
是一个容器, 里面保存着初始化Promise
对象时传入的executor
函数的操作状态, 如果我们为Promise
对象添加相应的状态处理器, 那么当状态发生变化的时候会触发相应处理器的异步执行. 共有三种状态: pending
, fulfilled
和rejected
. 状态的变化只有两种可能: 从pending
到fulfilled
和从pending
到rejected
, 一旦一个Promise
对象的状态改变了, 那么他就固定了, 不能够再次改变.
6.1 基础语法
基础语法使用包括:
- 使用带有
resolve
和reject
两个参数的executor
函数初始化Promise
对象. - 在
executor
函数中调用resolve
方法或者reject
方法改变当前Promise
对象的状态. - 使用
Promise.prototype.then()
给Promise
对象添加onfulfilled
,onrejected
处理器. - 使用
Promise.prototype.catch()
给Promise
对象添加onrejected
异常处理器. - 使用
Promise.prototype.finally()
来执行无论什么情况下都会执行的代码. - 使用
Promise.all()
方法将多个Promise
实例包装成一个新的Promise
实例.
1 | // 传入executor函数初始化Promise对象 |
6.2 注意点
- 定义一个
Promise
对象的时候, 传给它的executor
的代码是立即执行的. executor
函数里面, 要注意: 调用resolve
或者reject
之后, 他们后面的代码还是会执行的, 所以一般在调用这两个方法前面加上return
.- 无论是调用
reject
函数还是抛出异常, 状态都会变成onrejected
. Promise.prototype.catch
方法相当于.then(null, rejection)
, 也就是说仅当没有在then
中指定onrejected
处理器的时候catch
才起作用.- 可以使用
then
链:- 在
then
的resolve
函数里面返回一个值的话, 那么这个值会作为下一个then
的resolve
的值. - 如果返回的是一个
Promise
对象的话, 那么这个then
就是跟在这个Promise
对象之后的. - 如果没有返回值, 那么继续下一个
then
.
- 在
1 | // new Promise对象的时候, 会立即执行传入的函数. |
七、async, await
async
和await
是一种特殊的语法, 他们和Promise
协同工作. async
表示函数里有异步操作, await
表示紧跟在后面的表达式需要等待结果.
7.1 async函数
使用关键字async
修饰的函数执行的时候总是返回一个Promise
对象, 而我们在函数代码里面写的返回值则会被包装成Promise
的resolved的value.
1 | async function asyncFunc() { |
7.2 await
关键字await
会让JavaScript等到Promise
完成后(fulfilled
)才会返回结果. 它后面跟的是Promise
对象或者是普通值, 如果是普通值则返回这个值.
1 | const fs = require('fs'); |
7.3 错误处理
如果一个await
后面的Promise
的状态变为rejected
(调用reject方法或者抛出异常), 那么这个async
函数停止后面语句的运行, 抛出异常. 如果我们为这个async
函数定义了catch
回调函数的话会由这个回调函数进行处理.
1 | async function f() { |
7.4 注意
如果一条
await
发生了异常, 但是我们希望这个异常不会影响后面语句的执行, 那么可以将它放到try...catch
里面.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17async function f() {
let a1 = 0;
try {
a1 = await new Promise((resolve, reject) => {
reject('call the reject method.');
});
} catch (e) {
// 输出 call the reject method.
console.log(e);
}
// 这个语句是会执行的.
console.log('因为那个await语句放到了try块里面, 所以这里还会执行.');
}
// 这里的catch处理器就没有用到了.
f().catch(reason => console.log(reason));await
作用于promise.then
返回的promise
的两种情况: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
30
31
32
33// 第一种情况
async function f() {
let promise = new Promise((resolve, reject) => {
resolve('resolved...');
});
// 这里then里面的函数返回了一个值, await返回的值就是这个值
let newPromise = promise.then(value => [value, 'new value']);
let [value, newValue] = await newPromise;
// 'resolved' 'new value'
console.log(value, newValue);
}
f();
// 第二种情况
async function f1() {
let promise = new Promise((resolve, reject) => {
resolve('resolved...');
});
let newPromise = promise.then(value => new Promise((resolve, reject) => {
resolve('hello');
}));
let value = await newPromise;
// hello
console.log(value);
}
f1();
八、Class
8.1 基本语法
- 使用
class
关键字定义类. - 构造方法为
constructor()
: 通过new
命令生成对象实例时, 自动调用该方法. 一个类如果没有显示定义这个方法, 那么一个空的constructor
方法会被创建. 这个方法默认返回this
. - 定义属性.
- 定义方法.
- 取值函数
getter
, 设值函数getter
: 这两个函数会被自动设置到属性的描述符对象的get
和set
上面. - 定义静态属性.
- 定义静态方法.
- 继承
extends
. this
表示实例对象.super
1 | class Person { |
8.2 类和继承的原理
ES6中新增的class是一个语法糖, 它只是让对象原型的写法更加清晰, 更加接近面向对象编程的语法而已, 使用ES6定义的类的所有属性都存在于类的.prototype
属性中, 在类的实例中调用方法其实就是调用实例内部指针指向的原型对象上的方法, 也就是类的.prototype
属性指向的对象上的方法.
8.3 注意事项
8.3.1 Class.prototype
ES6的class本质上只是一个语法糖, 所以ES5中构造函数的prototype
属性在ES6的类上面也是存在的, 它等同与类的prototype
属性. 类的所有方法都定义在类的prototype
属性上面. 类的prototype
属性对应的对象的constructor
属性也是指向类本身, 和ES5中的行为是一致的.
1 | class Person { |
8.3.2 静态属性和静态方法
在类中定义的属性是放在类的prototype
属性上的, 类的所有实例都可以访问, 从上一节可以了解到. 但是如果我们想通过类来调用一个属性或者方法, 在ES5中我们是直接定义在构造函数上, 在类中我们使用static
关键字(属性不能使用static关键字, 而是使用类名.staticProName
的方式定义).
1 | class Person { |
8.3.3 this的指向
类方法内部如果含有this
, 它默认指向类的实例. 但是注意:
如果将这个方法赋值给另一个对象, 那么
this
就指向被赋值的对象.如果将这个方法提取出来单独使用,
this
会变为undefined
(一直以为是指向执行环境window或者global的).
1 | class Person { |
解决办法:
- 一般在构造方法中将
this
绑定到方法 - 或者使用箭头函数.
1 | class Person { |
8.3.4 constructor和super
如果子类继承父类且重写了constructor
构造方法, 那么在重写的构造方法的第一行必须调用super
执行一次父类的构造方法.
九、Module
ES6中的模块化提供了import
和export
两个关键字. 在ES6, 一个JavaScript文件被视为一个模块.
9.1 export
四种export的方式:
export
写在变量定义前.- 先定义变量, 然后用大括号将变量名括起来
export
出去. - 在大括号里面对变量重命名.
- 用
export default
输出默认变量, 这样在import
的时候可以自己定义名字.
1 | //写在变量定义前 |
9.2 import
当使用export
定义了模块对外的接口后, 既可以通过import
命令从这个模块加载内容了. 需要注意两点:
- 可以使用
as
来对变量重新取名. - 如果
export
出去的变量是默认变量, 那么这个名字我们可以任意取. - 可以使用
*
来整理加载一个模块导出的接口.
1 | // 常规用法 |