一、ES6简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
二、新特性
1. let、const
新特性注意点let1. 不存在变量提升 2. 暂时性死区(块级作用域) 3. 不允许重复声明const声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。 其只保证指针不发生改变,因此可以修改保存的对象的值
- ES6 声明变量的6种方法:var function let const import class
// 取顶层对象
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
2. 解构赋值
代码语言:javascript复制// 数组
let [x, y='b'] = ['a'] // x=''a, y='b'
// 对象
let { foo:f, bar:l=2 } = { foo: 'aaa', bar: undefined } // f,l才是变量名
f // aaa
l // 2
// 字符串
let {length: len} = 'hello'
len // 5
// 数组和布尔值的解构赋值
// 只要等号右边的值不是对象或数组,就先将其转为对象。
// 由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
// 函数参数解构
function move({x = 0, y = 0} = {}) {
return [x, y];
}
- 作用:
(1) 交换变量的值:
[x, y] = [y, x];
(2) 从函数返回多个值:let { foo, bar } = example();
(3) 函数参数的定义:function f([x, y, z]) { ... }
(4) 提取 JSON 数据:let { id, status, data: number } = jsonData;
(5) 函数参数的默认值 (6) 遍历 Map 结构const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key " is " value); } // first is hello // second is world // 获取键名 for (let [key] of map) { // ... } // 获取键值 for (let [,value] of map) { // ... } (7) 输入模块的指定方法:const { SourceMapConsumer, SourceNode } = require("source-map");
3. 字符串的扩展
(1) 字符的 Unicode 表示法 (2) 字符串的遍历接口 (3) 直接输入U 2028和U 2029 (4) Json的stringify()的改造 (5) 模版字符串
4. 字符串的新增方法
(1) String.fromCodePoint():String.fromCharCode(0x20BB7) // "ஷ"
(2) String.raw():该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法
(3) codePointAt(): 能够正确处理 4 个字节储存的字符,返回一个字符的码点
(4) normalize(): 用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化
(5) includes():返回布尔值,表示是否找到了参数字符串/
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
(6) 'x'.repeat(3) // "xxx"
(7) padStart()、padEnd(): ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全
(8) trimStart()、trimEnd():消除字符串头、尾部的空格
(9) matchAll(): 返回一个正则表达式在当前字符串的所有匹配
5. 正则的扩展
6. 数值的扩展
Number.isNaN()
Number.isFinite()
Number.isInteger():是否为整数
Number.EPSILON:极小的常量
Math.trunc(): 去除小数部分,返回整数部分
Math.sign()判断正负、0,其他值返回NaN、指数运算符**,2**3 // 8
7. 函数的扩展
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
箭头函数
- 使用注意点: (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替 (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
- 不适用场合: (1)定义对象的方法,且该方法内部包括thisconst cat = { lives: 9, jumps: () => { this.lives--; } } // 对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。 (2) 需要动态this的时候,也不应使用箭头函数var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); }); (3) 如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。
尾调用优化、尾递归
8. 数组的扩展
- 扩展运算符:任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组,背后调用的是遍历器接口(Symbol.iterator)
- Array.from(): 用于将两类对象转为真正的数组: 类似数组的对象(array-like object,比如 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象,本质是具有length属性)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)// 对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代 const toArray = (() => Array.from ? Array.from : obj => [].slice.call(obj) )(); // Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。 Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
- Array.of()方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
- copyWithin():
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // 将3号位复制到0号位 [4, 2, 3, 4, 5]
- find(): 用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined----
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
- findIndex()与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1,弥补了indexOf的不足,可以发现NaN
[NaN].findIndex(y => Object.is(NaN, y)) // 0
- fill():['a', 'b', 'c'].fill(7) // [7, 7, 7] new Array(3).fill(7) // [7, 7, 7] // 还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。 ['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
- entries()、keys()、values(): keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
- includes(): 表示某个数组是否包含给定的值,与字符串的includes方法类似
[1, 2, NaN].includes(NaN) // true
- flat(): 将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]
- flatMap(): 对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
9. 对象的扩展
代码语言:javascript复制let propKey = 'foo';
let obj = {
[propKey]: true,
['a' 'bc']: 123
};
属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
- 属性的遍历方法有5种: (1)for...in for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。 (2)Object.keys(obj) Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。 (3)Object.getOwnPropertyNames(obj) Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。 (4)Object.getOwnPropertySymbols(obj) Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。 (5)Reflect.ownKeys(obj) Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象
- Object.is() :用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是 0不等于-0,二是NaN等于自身。
- Object.assign():用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。注意点:
(1)浅拷贝
(2)同名属性的替换
(3)数组的处理
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
(4) 取值函数的处理:只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。 - Object.getOwnPropertyDescriptors() 返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
- __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf() 、Object.keys(),Object.values(),Object.entries() 、Object.fromEntries():Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 }
10. Symbol:
- ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
- Symbol 值不能与其他类型的值进行运算,会报错。 但是,Symbol 值可以显式转为字符串。另外,Symbol 值也可以转为布尔值,但是不能转为数值。 可用来消除魔法字符串、模块的Singleton模式
11. Set、Map 数据结构
Set类似于数组,但是成员的值都是唯一的,没有重复的值。
代码语言:javascript复制const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 去除数组的重复成员
[...new Set(array)]
[...new Set('ababbc')].join('')
// "abc"
在 Set 内部,两个NaN是相等。另外,两个对象总是不相等的。
- 方法: Set.prototype.add(value):添加某个值,返回 Set 结构本身。 Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。 Set.prototype.clear():清除所有成员,没有返回值。 Set.prototype.keys():返回键名的遍历器 Set.prototype.values():返回键值的遍历器 Set.prototype.entries():返回键值对的遍历器 Set.prototype.forEach():使用回调函数遍历每个成员
- Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
- WeakSet:WeakSet 结构与 Set 类似,也是不重复的值的集合。 但是,它与 Set 有两个区别: (1)WeakSet 的成员只能是对象,而不能是其他类型的值。其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 (2)WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏
- Map: 类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
- WeakMap: 首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
12. Proxy
在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,很合适用来写 Web 服务的客户端。
13. Reflect
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。 (2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。 (3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。 (4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
- 方法: Reflect.apply(target, thisArg, args) Reflect.construct(target, args) Reflect.get(target, name, receiver) Reflect.set(target, name, value, receiver) Reflect.defineProperty(target, name, desc) Reflect.deleteProperty(target, name) Reflect.has(target, name) Reflect.ownKeys(target) Reflect.isExtensible(target) Reflect.preventExtensions(target) Reflect.getOwnPropertyDescriptor(target, name) Reflect.getPrototypeOf(target) Reflect.setPrototypeOf(target, prototype)
14. Promise
- 特点: (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- 缺点: 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
- Promise.all(): 用于将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.all([p1, p2, p3]);
- p的状态由p1、p2、p3决定,分成两种情况。 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
- Promise.race():
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 - Promise.resolve()、Promis.reject()、Promise.try()
15. Iterator
任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
- 作用: (1)为各种数据结构,提供一个统一的、简便的访问接口; (2)使得数据结构的成员能够按某种次序排列; (3)ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
16. Generator
- Generator 函数是一个普通函数,但是有两个特征: (1)function关键字与函数名之间有一个星号; (2)函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
- 调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
- 异步编程: (1)回调函数 (2)事件监听 (3)发布/订阅 (4)Promise 对象 (5)Generator 函数
17. async
- async函数对 Generator 函数的改进,体现在以下四点。 (1)内置执行器。 (2)更好的语义。 (3)更广的适用性: co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。 (4)返回值是 Promise。
18. Class
- 与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。类的属性名,可以采用表达式。
- 父类Foo有一个静态方法,子类Bar可以调用这个方法。 静态方法也是可以从super对象上调用的。 为class加了私有属性。方法是在属性名之前,使用#表示。
- extends 继承,子类的构造函数必须执行一次super函数
19. Module
- import、export
- ES6模块的好处: (1)不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。 (2)将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。 (3)不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
- import(): 类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。 (1)按需加载。(2)条件加载(3)动态的模块路径
- defer是“渲染完再执行”,async是“下载完就执行”
- 浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。
三、ES6 最佳实践
https://es6.ruanyifeng.com/#docs/style
四、辅助链接、工具
- 查看各浏览器对 ES6 的支持:https://kangax.github.io/compat-table/es6/
- 检查各种运行环境对 ES6 的支持情况:ruanyf.github.io/es-checker ,es-checker命令查看支持程度
- ES6 在线编译器:http://google.github.io/traceur-compiler/demo/repl.html#
参考阮一峰 ECMAScript 6 入门