1. 一些核心功能
1.1 let const
ES5 通过 var 来申明变量,ES6 新添 let 和 const,且作用域是 块级作用域。
let 使用和 var 非常类似,let 不存在变量提升,也不允许重复申明,let 的声明只能在它所在的代码块有效
const 就是申明常量用的,一旦申明即被锁定,后面无法更改。
1.2 解构赋值(Destructuring)
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
ES6 解构赋值基本语法 var [a, b, c] = [1, 2, 3];,从数组中取值,并按照先后次序来赋值。如果解构赋值不成功,就会返回 underfined,解构赋值也允许指定默认值
|
|
除了数组,对象也可以解构赋值,但是数组是有顺序的,而对象没有顺序,如果想要成功赋值,必须与对象属性同名,才能成功赋值,否则返回 underfined:
|
|
字符串的解构赋值比较有意思,既可以把字符串当作可以迭代的数组,又可以当作对象,比如:
|
|
1.3 字符串模版(template string)
当我们要插入大段的html内容到文档中时,传统的写法非常麻烦
|
|
我们要用一堆的’+’号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:
|
|
1.4 set 集合和 Map 结构
set 集合
- ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
- Set 方法分为操作和遍历,操作方法有 add-添加成员, delete-删除成员, has-拥有判断返回布尔值, clear-清空集合。
- 遍历操作有 keys(),values(),entries(),forEach(),…,for of,map 和 filter 函数也可以用于 Set,不过要进行巧妙操作,先转换成数组,在进行操作
|
|
Map 结构
- Map 用来解决对象只接受字符串作为键名,Map 类似于对象,也是键值对集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
- Map 可以通过 [set、 get、 has、 delete] 方法来操作:
|
|
1.5 spread操作符(…)
用于函数调用:myFunction(…iterableObj);
用于数组字面量:[…iterableObj, 4, 5, 6]
函数传参
|
|
数据解构
数据构造
2. 箭头函数(=>)
2.1基本用法
ES6允许使用“箭头”(=>)定义函数。
|
|
上面的箭头函数等同于:
|
|
2.2使用注意点
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
2.3函数绑定
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。
函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
|
|
2.4箭头函数与常规函数对比
一个箭头函数与一个普通的函数在两个方面不一样:
- 下列变量的构造是词法的: arguments , super , this , new.target
- 不能被用作构造函数:没有内部方法 [[Construct]] (该方法允许普通的函数通过 new 调用),也没有 prototype 属性。因此, new (() => {}) 会抛出错误。
3.Class类
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。
|
|
上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。
Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
4.Module模块化
ES6 之前,JS 一直没有 modules 体系,解决外部包的问题通过 CommonJS 和 AMD 模块加载方案,一个用于服务器,一个用于浏览器。ES6 提出的 modules (import/export)方案完全可以取代 CommonJS 和 AMD 成为浏览器和服务器通用的模块解决方案。
关于模块,就只有两个命令,import 用于导入其他模块,export 用于输出模块。
|
|
export 可以输出的内容很多,包括变量、函数、类,貌似都可以输出,还可以借助 export default 来加载默认输出。
模块加载的实质
ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。
循环加载
循环加载也比较有意思,经常能看到 nodejs 中出现加载同一个模块,而循环加载却不常见,nodejs 使用 CommonJS 模块机制,CommonJS 的循环加载采用的是加载多少,输出多少,就像是我们平时打了断点一样,会跳到另外一个文件,执行完在跳回来。
5.Promise
5.1为什么产生
Promise的兴起,是因为异步方法调用中,往往会出现回调函数一环扣一环的情况。这种情况导致了回调金字塔问题的出现。不仅代码写起来费劲又不美观,而且问题复杂的时候,阅读代码的人也难以理解。例如:
|
|
假设有一个数据库保存操作,一次请求需要在三个表中保存三次数据。那么我们的代码就跟上面的代码相似了。这时候假设在第二个db.save出了问题怎么办?基于这个考虑,我们又需要在每一层回调中使用类似try…catch这样的逻辑。这个就是万恶的来源,也是node刚开始广为诟病的一点。
另外一个缺点就是,假设我们的三次保存之间并没有前后依赖关系,我们仍然需要等待前面的函数执行完毕, 才能执行下一步,而无法三个保存并行,之后返回一个三个保存过后需要的结果。(或者说实现起来需要技巧)
5.2解决回调深渊Promise
Promise对象是一个有限状态机。它三个状态:
- Pending(进行中)
- Resolved(已完成,又称 Fulfilled))
- Rejected(已失败)
状态转换关系为:pending->fulfilled,pending->rejected。
Promise形式
|
|
Promise数据流动
promise的then方法依然能够返回一个Promise对象,这样我们就又能用下一个then来做一样的处理。
- 假设第一个then的第一个回调没有返回一个Promise对象,那么第二个then的调用者还是原来的Promise对象,只不过其resolve的值变成了第一个then中第一个回调函数的返回值。
- 假设第一个then的第一个回调函数返回了一个Promise对象,那么第二个then的调用者变成了这个新的Promise对象,第二个then等待这个新的Promise对象resolve或者reject之后执行回调。
- 如果任意地方遇到了错误,则错误之后交给遇到的第一个带第二个回调函数的then的第二个回调函数来处理。可以理解为错误一直向后reject, 直到被处理为止。
控制并发的Promise
Promise有一个”静态方法”——Promise.all(注意并非是promise.prototype), 这个方法接受一个元素是Promise对象的数组。
将其他对象变为Promise对象
Promise.resovle方法,可以将不是Promise对象作为参数,返回一个Promise对象。
6.Generator
7.async
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
7.1async函数对 Generator 函数的改进
内置执行器
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
更好的语义
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
更广的适用性
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
返回值是 Promise
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
7.2async 函数与 Promise、Generator 函数的比较
假定某个 DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。
Promise 的写法:
|
|
虽然 Promise的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、catch等等),操作本身的语义反而不容易看出来
Generator 函数的写法
|
|
上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证yield语句后面的表达式,必须返回一个 Promise。
async 函数的写法
|
|
可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将Generator写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用Generator写法,自动执行器需要用户自己提供。