ES6补档
数据结构 Set 和 Map
整体概述:
Set
是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set
通过new
关键字实例化,入参可以是数组 or 类数组的对象。
值得注意的是:在Set
中,只能存储一个NaN
,这说明在 Set 数据结构中,NaN
等于NaN
。
Set
实例的方法:
操作方法 add()、delete()、has()和 clear();
遍历方法:keys()、values()、entries()和 forEach();扩展运算符…、
数组方法 map()、filter()方法也可以用于 Set 结构。
由此它可以很方便的实现数组的交、并、差集。
WeakSet
类似于 Set,主要区别在于 1.成员只能是对象类型; 2.对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故 WeakSet 不可被遍历)
JavaScript 对象 Object 都是键值 K-V 对的集合,但 K 取值只能是字符串和 Symbol,Map 也是 K-V 的集合,然而其 K 可以取任意类型。
如果需要键值对的集合,Map 比 Object 更适合。Map 通过 new 关键字实例化。
Map
实例的方法:set()、get()、has()、delete()和 clear();
遍历方法同 Set。
Map
与其它数据结构的互相转换:Map <—> 数组| Map <—> 对象| Map <—> JSON。
WeakMap
类似于Map
,主要区别在于: 1.只接受对象作为键名; 2.键名所指向的对象不计入垃圾回收机制。
Set
Set 是一个类似数组,但是与数组不同,它具有唯一性.里面的元素都是不重复的,本身是一个构造函数.
1 | const SetArr = new Set(); |
实例属性
- constructor: 构造函数,就是 Set 函数
- size: 返回的是 Set 实例的长度
实例方法
- add(): 往 Set 里添加值,返回 Set 本身
- delete(): 删除某个值,返回布尔值判断是否成功
- has(value): 判断是否有 value,返回布尔值
- clear(): 清除 Set 中所有值,无返回值
遍历方法
- forEach(): 使用回调函数遍历元素
- entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组
- values(): 返回键值遍历器,用于遍历所有键值
- keys(): 返回键名遍历器,用于遍历所有键名
由于 Set 结构是只有键值的结构,所有 keys 方法与 values 方法返回一致。
forEach
1 | let s = new Set([1, 2, 3, 4, 5]); |
entries
1 | let s = new Set(["a", "b", "c"]); |
values
1 | let s = new Set(["a", "b", "c"]); |
keys
1 | let s = new Set(["a", "b", "c"]); |
使用 set 实现交并差
1 | let a = new Set([1, 2, 3, 4]); |
WeakSet
一个弱的 Set 结构,弱的体现在WeakSet
的成员只能是对象.WeakSet
的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用.WeakSet
没有 size 属性,无法遍历.只有实例方法:
- add(): 向
WeakSet
添加新成员 - delete(): 删除
WeakSet
的成员 - has(): 判断
WeakSet
是否包含某个元素
Map
Set 结构是没有键只有值,而 Map 结构是键值的组合.也就可以使用各种数据类型作为键(包括对象).
1 | let map = new Map(); |
实例属性
- constructor: Map 构造函数
- size: 返回的是 Map 元素的长度
实例方法
- set(key, value): 设置 key 所对应的 value 值
- get(key): 获取 key 所对应的 value 值,若无则返回 undefined
- delete(): 删除某个值,返回布尔值判断是否成功
- has(value): 判断是否有 value,返回布尔值
- clear(): 清除 Set 中所有值,无返回值
遍历方法
- forEach(): 使用回调函数遍历元素
- entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组
- values(): 返回键值遍历器,用于遍历所有键值
- keys(): 返回键名遍历器,用于遍历所有键名
keys
1 | let person = new Map([ |
values
1 | let person = new Map([ |
与其他数据结构转换
- Map 转数组
1 | let person = new Map([ |
- 数组转 Map
1 | let arr = [ |
- Map 转对象
1 | let person = new Map([ |
- 对象转 Map
1 | let person = { name: "allen", age: "20" }; |
WeakMap
WeakMap 与 Map 一样可以生成键值对的集合,但是也有不同的地方,主要是有以下的两点:
- WeakMap 只接受对象作为键名(不包括 null)
- ct
与 WeakSet 相似,WeakMap 也是没有遍历的操作,也没有 size 属性,没有办法列出所有键名(由于垃圾回收机制的运行),也不能清空。
实例方法
- get():
- set():
- delete(): 删除 WeakMap 的成员
- has(): 判断 WeakMap 是否包含某个元素
Array.from
将类数组对象转化为数组.
第二个参数可以对传入值进行处理.
模块化
方式有立即执行函数,AMD,CMD,CommonJS,ES6 模块化.
其中最常用的是 CommonJS 和 ES6 模块化
区别
- CommonJS 即使用
require()
,module.exports
,通常在 Node 中使用.支持动态导入.属于同步导入. - ES6 模块化即使用
import
,export default
.通常用于浏览器,属于异步导入. - CommonJS 在导出时是值拷贝,就算导出的值变了,导入的也不会变.如果想更新,必须重新导入.ES6 模块化采用实时绑定,导出导入都指向同一个地址,所以导入变化导出也会变化.
- ES6 模块会编译成
require/exports
来执行.
1 | // AMD |
Commonjs
1 | // a.js |
1 | var module = require("./a.js"); |
另外虽然 exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。
箭头函数
- 箭头函数继承而来的 this 指向永远不变.
使用call,apply,bind
都无法改变箭头函数 this 指向.
- 箭头函数不能作为构造函数使用.因为它没有自己的 this
- 箭头函数没有自己的
arguments
对象.
在箭头函数中访问 arguments 实际上获得的是外层局部(函数)执行环境中的值.
如果想访问箭头函数中的参数,可以使用rest
参数.
- 箭头函数没有原型
prototype
- 箭头函数不能用作
Generator
函数,不能使用 yeild 关键字
类和继承
ES6 的类,完全可以看作构造函数的另一种写法。
类的数据类型就是函数,类本身就指向构造函数。
1 | class Point { |
类的所有方法都定义在类的 prototype 属性上面。
1 | class Point { |
constructor 关键字
constructor()
方法就是构造方法,而this
关键字则代表实例对象。constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有
constructor()方法,如果没有显式定义,一个空的
constructor()`方法会被默认添加。
extends 关键字
作用:类继承
底层原理: 替换原型继承(不会覆盖子构造函数的原型)
extends 关键字替换原型的原型:s1.__proto__.__proto__ = Person.prototype
super 关键字
用于调用父类的方法.
子类中如果想使用constructor
,必须使用super
关键字调用父类的方法.super
这个关键字,既可以当作函数使用,也可以当作对象使用
在子类的构造函数中,只有调用 super()之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有 super()方法才能让子类实例继承父类。
如果子类没有定义 constructor()方法,这个方法会默认添加,并且里面会调用 super()。也就是说,不管有没有显式定义,任何一个子类都有 constructor()方法。
当做函数
1 | class A {} |
注意,super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 的实例,因此 super()在这里相当于 A.prototype.constructor.call(this)。
当做对象
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
1 | class A { |
上面代码中,子类 B 当中的 super.p(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.p()就相当于 A.prototype.p()。
注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
1 | class Person { |
类中的 getter 和 setter
私有属性无法在外部调用,可以利用getter
间接调用私有属性.使用setter
间接设置私有属性.
1 | class Person { |
单例
1 | //static表示挂载在类上,而不是类的实例上 |
抽象类
1 | // 使用abstract 关键字抽象类,将公共方法属性抽离出来 |
Proxy
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
Proxy 内置一系列陷阱,用于创建一个对象的代理,从而实现基本操作的拦截和自定义.
1 | let p = new Proxy(target, handler) |
1 | const obj = { |
1 | const parent = { |
虽然也可以直接使用target[key]
,但是 Reflect 可以用 receiver,更强大.
案例
1 | // 实现功能 |
深层遍历
1 | const handler = { |
Generator
Generator 即生成器,它会返回一个迭代器.Generator 最大的特点就是可以控制函数的执行。可以作为异步解决方案.
1 | function* foo(x) { |
Generator 函数需要手动调用next
执行,在遇到yield
会暂停,返回一个对象,包括执行结果和是否执行完毕.继续调用next
会从之前暂停的地方开始,如果传入参数,会覆盖之前的结果.到下一个yield
结束.
分析上面的代码:
- 第一次执行
next
,参数是 5,遇到yield
暂停,返回值为 5+1 => 6. - 第二次执行
next
,next
有参数 12, 覆盖上一次yield
处的值,就是y = 2*12
,要在这一次的yield
处暂停就要 24/3,得到 8 - 最后一次执行
next
,z 就被赋值为 13,结合第二次 y 是 24,第一次 x 是 5,最后返回 5+13+24=42.
Promise
Promise 是异步问题同步化解决方案.
Promise 本身不是异步,它是个构造函数.
Promise 中的函数是同步的.
Promise 有一个参数,即excutor
执行器,它有两个参数,resolve
和reject
.excutor
是同步执行,而then
是异步调用的.
如果想连续then
需要上一个promise
中 return 一个新的promise
.
Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止.
状态
promise 内部在resolve
之前的代码处于pending
状态,之后的是fulfilled
.
promise 在catch
中如果没有抛出错误,则会返回fufilled
链式调用
状态固化后,就不再捕获错误了.比如 resolve()返回后面有报错的内容,但是 catch 就不再捕获了.
案例:
当 promise 作为参数传递到另一个 promise 中,这个第二个 promise 中的状态就会失效.依赖于第一个 promise.
1 | const p1 = new Promise((reslove,reject)=>{ |
Promise.all()如果三个内容都失败,只返回第一个失败的信息.如果只有一个出错,也只会返回一个.而且不返回正常的.
Promise.race()是谁先完成返回谁,无论是成功还是失败.
1 | promise |
1 | // then中throw new Error()的情况 |
缺点
其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。
如何中断 promise
插入一个 pending 状态的 promise.
catch
catch 在 promise 的源码层面就是一个 then,如果 catch 中 return 的有值,可以在 catch 后跟 then.
既有 then 中的失败回调,又有 catch 的情况,会走最近的那个.
1 | const p1 = Promise.resolve(); |
案例
使用 promise 加载图片
1 | function loadIMg(src) { |
语法糖
Promise.resolve()
Promise.reject()
Promise.all
如果所有都正确执行,会按顺序返回.有一个失败,就失败.
Async/await
一个函数前加了async
,它就会返回 Promise.它的返回值会被Promise.resolve()
包裹.
缺点
因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
如果代码没有依赖性的话,完全可以使用 Promise.all 的方式
async/await 和 promise 的关系
await 是等待 promise 的返回,await 做不了 promise 返回状态的工作.
await 对应Promise.then
成功的情况.
await 后如果是函数,则会把函数返回的结果用Promise.then()
返回.
async 中的Promise.reject()
需要使用try/catch
进行捕获错误.
async 中的函数内部属于同步代码,在遇到 await 之前都可以先执行.
1 | let a = 0; |
- 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了
generator
,generator
会保留堆栈中东西,所以这时候 a = 0 被保存了下来 - 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成
Promise.reslove(返回值)
,然后会去执行函数外的同步代码 - 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。