Promise
Promise同样也是ECMAScript2015当中提供的一个内置对象,那他提供了一种全新的异步编程解决方案,通过链式调用的方式解决了我们在传统异步编程过程中回调函数嵌套过深的问题。
不过关于Promise的细节有很多内容,所以说我们这里先不做详细介绍在JavaScript异步编程的文章中已经专门针对Promise进行了详细的分析。这里简短介绍的目的是为了让你对ECMAScript2015新增的所有的特性有一个系统化的认识。
class类
在此之前ECMAScript中都是通过定义函数以及函数的原型对象来去实现的类型,例如我们这里想要定义一个Person的类型,我们需要先定义一个叫做Person的函数,然后作为这个类型的构造函数。
在构造函数中我们可以通过this去访问当前的实例对象,如果我们需要在这个类型所有的实例间去共享一些成员,可以借助于函数对象的prototype, 也就是原型去实现。
function Person (name) {
this.name = name;
}
Person.prototype.say = function() {
console.log(this.name);
}
自从ECMAScript2015开始我们就可以使用一个叫做class的关键词,来去声明一个类型,那这种独立定义类型的语法,相比较之前函数的方式,要更容易理解。结构也会更加清晰一些。
我们这里通过class来重构一下刚刚的Person,通过class关键字加上类型名称, 然后跟上一对{}, 这就定义了一个Person类型。
class Person {
}
这种语法与一些老牌面向对象语言当中class是非常相似的。如果说我们需要在构造函数当中做一些额外的逻辑,我们可以添加一个叫做constructor的方法,那这个方法就是当前这个类型的构造函数。
我们同样可以在这个函数中使用this去访问当前类型的实例对象。如果我们想要为这个类型定义一些实例方法我们只需要在这个类型里面去添加对应的方法成员就可以了。例如我们这里再来添加一个叫做say的方法, 在这个方法中也可以通过this拿到当前的实例对象。
class Person {
constructor (name) {
this.name = name;
}
say() {
console.log(this.name);
}
}
创建实例的方式和function的方式一样,都是通过new关键字的方式来创建。通过创建的实例也可以调用类中的say方法。
const p = new Person('yd');
p.say();
静态方法
在我们类型当中的方法一般分为实例方法和静态方法,那实例方法就是需要通过这个类型构造的实例对象去调用,而静态方法是直接通过类型本身去调用,以前我们去实现静态方法我们是直接在构造函数函数对象上去挂载方法来去实现因为在JS当中函数也是对象。他也可以去添加一些方法成员。
而在ECMAScript2015中他就多了一个专门用来添加静态方法的关键词,叫做static,下面我们来看具体的用法。这里我们来给当前的Person类型添加一个create的静态方法,用来去创建Person类型的实例。
这个方法的内部逻辑就是直接return一个Person实例。
class Person {
constructor (name) {
this.name = name;
}
say() {
console.log(this.name);
}
static create (name) {
return new Person(name);
}
}
调用静态方法就是直接通过类型然后通过成员操作符调用方法名字。
const yd = Person.create('yd');
不过这里需要注意,因为我们静态方法是挂载到类型上面的,所以说在静态方法内部他不会指向某一个实例对象,而是当前的类型,如果说你之前对this这样一个特性不太清晰这一点是尤其需要注意的。
类的继承
继承是面向对象当中一个非常重要的特性,通过继承这种特性我们就能抽象出来,相似类型之间重复的地方,在ES2015之前,大多数情况我们都会使用原型的方式,去实现继承。而在ES2015中他实现了一个专门用于继承的关键词extends。
这里我们再定义一个Student类型,我们让他继承自Person,这样的话Student类型当中就会拥有Person类型里面所有的成员了。
在Student类型的构造函数当中,我们去接收两个参数,分别是name和number,number就是学号。name参数在我们父类当中也需要用到。所以这里我们需要用到super对象。
这个对象始终指向父类, 调用它就是调用了父类的构造函数,然后我们可以在这里定义一些这个类型所特有的成员。
例如我们这里添加一个叫做hello的方法,在这个方法中我们同样可以使用super对象对象去访问父类当中的成员。例如我们这里调用父类中的say方法。
class Student extends Person {
constructor(name, number) {
super(name);
this.number = number;
}
hello () {
super.say();
console.log(this.number);
}
}
完成以后我们就可以通过Student类型去创建一个对象。当我们调用这个方法的时候会先执行父类的say然后再打印自己的number。
const s = new Student('yd', '100');
s.hello();
这就是我们在class当中去使用extends去实现的继承,他相比于原型继承要更方便一点,也更清楚一点。
Set
ES2015中提供了一个叫做Set的全新数据结构,你可以把他理解为集合,他与传统的数组非常类似,不过Set内部的成员是不允许重复的。那也就是说每一个值在同一个Set中都是唯一的。
那他是一个类型,我们通过这个类型构造的实例就用来存放不同的数据。我们可以通过这个实例的add方法向集合当中去添加数据,由于这个add方法他会返回集合对象本身,所以我们可以链式调用。那如果我们在这个过程中添加了之前已经存在的值那所添加的这个值就会被忽略掉。
const s = new Set();
s.add(1).add(2).add(3).add(2);
想要遍历集合当中的数据,我们可以使用集合对象的forEach方法去传递一个回调函数。
s.forEach(i => console.log(i));
或者是我们也可以使用ECMAScript2015中所提供的for…of循环,这种循环是一个新的语法。他也可以去遍历我们普通的数组,他所遍历的i就是数组当中的每一个成员。
for (let i of s) {
console.log(i);
}
除此之外在Set对象当中还可以通过size属性来去获取整个集合的长度,这与数组当中的length是相同的道理。
console.log(s.size);
除此以外他还提供了其他几个常用的方法我们分别来看一下。首先是has方法,这个方法就用来判断集合当中是否存在某一个特定的值。
console.log(s.has(100)); // false
delete方法用来删除集合当中指定的值,删除成功将会返回一个true。
console.log(s.delete(3)); // true
最后还有一个clear方法,用于清除当前集合当中的全部内容。
s.clear()
集合的常用应用场景就是用来为数组中的元素去去重,例如我们这里定义一个有重复元素的数组。
const arr = [1, 2, 1, 3, 4, 1];
如果我们想去掉重复元素那现在最简单的方式就是通过new Set的方式去加工一下这个数组,Set的构造函数会接收一个数组,这个数组会作为Set里面的初始值,重复的值会被忽略掉。如果我们想得到一个数组的话可以使用ES2015中新增的一个Array.from方法来去把Set再次转换回数组。
当然你也可以使用...
这样的展开操作符在一个空的数组当中去展开Set, 那这样的话Set当中的成员就会作为我们这个空数组当中的成员了,这样也可以得到一个数组。
// const result = Array.from(new Set(arr));
const result = [ ...new Set(arr)]
console.log(result); // [1, 2, 3, 4]
Map
ECMAScript2015还多了一个叫做Map的数据结构,那这种结构与ECMAScript中的对象非常类似,本质上他们都是键值对集合但是这种对象结构中的键,他只能够是字符串类型,如果说用其他类型作为键会被转换成字符串,出现[object object]这种奇怪的键名。不同的对象转换成字符串可能会变成相同的键名[object object],导致数据覆盖丢失。
为了解决这个问题ES2015中提供么Map类型,他才算是严格上的键值对类型,用来映射两个任意类型之间键值对的关系,用法上也非常的简单。
首先我们需要通过Map构造函数创建一个实例,然后可以使用这个对象的set方法去存数据。这里的键就可以是任意类型的数据。最终也不需要担心他会被转换为字符串。
const m = new Map();
const key = {};
m.set(key, 18);
console.log(m);
如果我们想要获取其中的数据,我们可以使用get方法,同时他也可以使用has方法判断他里面是否存在每个键。然后delete方法去删除某个键。clear方法清空所有的键值。
console.log(m.get(key));
console.log(m.has(key));
m.delete(key);
m.clear();
如果我们需要遍历整个Map当中所有的键值我们可以使用实例对象的forEach方法。在这个方法的回调函数当中第一个参数就是被遍历的值,第二个参数是被遍历的键。
m.forEach((value, key) => {
console.log(value, key);
})
Map与对象最大的区别就是他可以用任意类型的数据去作为键,而对象它实际上只能使用字符串作为键。
Symbol
在ECMAScript2015之前,对象的属性名都是字符串,而字符串是有可能会重复的。如果重复的话就会产生冲突,比如我们在使用第三方模块时,如果需要扩展第三方模块,而这时就有可能把第三方模块的方法覆盖掉,导致代码执行异常。
以前解决这种问题最好的方式就是约定,但是约定的方式只是规避了问题并不是彻底解决了这个问题。如果在这个过程中有人不遵守约定那这个问题仍然会存在。
ES2015为了解决这个问题提供了一种全新的原始数据类型Symbol,翻译过来的意思叫做符号,翻译过来就是表示一个独一无二的值。
通过Symbol函数就可以创建一个Symbol类型的数据,而且这种类型的数据typeof的结果就是symbol,那这也就表示他确实是一个全新的类型。
const s = Symbol();
typeof s; // symbol类型
这种类型最大的特点就是独一无二,也就是说我们通过Symbol函数创建的每一个值都是唯一的。他永远不会重复。
Symbol() === Symbol(); // false
考虑到在开发过程中的调试Symbol创建时允许接收一个字符串,作为这个值的描述文本, 对于我们多次使用Symbol时就可以区分出是哪一个Symbol,但这个参数也仅是描述作用,相同的描述字段生成的值仍是不同的。
从ES2015开始,对象就已经允许使用Symbol的组织
const s1 = Symbol('foo');
const s2 = Symbol('foo');
s1 === s2; // false
从ES2015开始,对象就已经允许使用Symbol作为属性名。那也就是说现在对象的属性名可以是两种类型,字符串和Symbol。
const person = {
[Symbol()]: 123,
[Symbol()]: 456
}
Symbol除了用在对象中避免重复以外,我们还可以借助这种类型的特点来模拟实现对象的私有成员。假设我们有一个js对外暴露一个对象,以前我们私有成员都是通过约定,例如我们约定使用下划线开头就表示是私有成员。约定外界不允许访问下划线开头的成员。
现在有了Symbol就可以使用Symbol去作为私有成员的属性名了。在这个对象的内部可以使用创建属性时的Symbol。去拿到对应的属性成员。
const name = Symbol();
const person = {
[name]: 'yd',
say() {
return this[name];
}
}
这样在外部文件中就拿不到this[name]
这个私有成员,只能调用对象提供的say方法。这样就实现了所谓的私有成员。
以上就是Symbol的一些用法和一些典型的场景,这种类型的值目前最主要的作用就是为对象添加一个独一无二的属性标识符。
截止到2019标准,ECMAScript一共定义了6种基本数据类型,加上Object一共7种数据类型。
未来还会有BigInt, 用于去存放更长的数字只不过目前这个类型还处在stage-4阶段,预计在ES2020标准中会正式被标准化,到时候就一共是8中数据类型了。
Symbol在使用上还有一些值得我们注意的地方,首先是他的唯一性。不论传入的描述是不是相同的,每次调用Symbol得到的结果都是一个全新的值。
Symbol('foo') === Symbol('foo'); // false
如果我们需要在全局去复用一个相同的Symbol值,我们可以使用全局变量的方式去实现,或者是使用Symbol类型提供的一个静态方法去实现。具体就是Symbol的静态方法for,这个方法接收一个字符串作为参数,相同的参数一定对应相同的值。
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
s1 === s2; // true
这个方法维护了一个全局的注册表,为字符串和Symbol提供了一个对应关系。需要注意的是,在内部维护的是字符串和Symbol的关系,那也就是说如参数不是字符串,会转换为字符串。
const s1 = Symbol.for('true');
const s2 = Symbol.for(true);
s1 === s2; // true
在Symbol内部提供了很多内置的Symbol常量,用来去作为内部方法的标识,这些标识符可以让自定义对象去实现一些js内置的接口,例如我们这里定义一个Object对象,然后去调用这个对象的toString方法, 默认结果就是[object object];我们把这样的字符串叫做对象的toString标签。
const obj = {};
obj.toString(); // [object object];
如果我们想要自定义对象的toString标签,我们就可以在这个对象当中去添加一个特定的成员来去标识,考虑到如果使用字符串取添加这种标识符就有可能和内部的成员产生重复所以ECMAScript要求我们使用Symbol的值来去实现这样一个接口。
obj[Symbol.toStringTag] = 'test'
obj.toString(); // [object test];
这里的toStringTag就是内置的一个Symbol常量,这种Symbol我们在后面为对象去实现迭代器时会经常遇到。
最后呢,我们使用Symbol的值去作为对象的属性名那这个属性我们通过传统的for in循环是无法拿到的。而且我们通过Object.keys方法也是获取不到这样Symbol类型的属性名。
JSON.stringify去序列化,Symbol属性也会被隐藏掉。
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (var key in obj) {
console.log(key);
}
Object.keys(obj);
总之这些特性都使得我们的Symbol属性,特别适合作为对象的私有属性,当然想要获取这种类型的属性名也不是完全没有办法,我们可以使用Object.getOwnPropertySymbols(obj)方法。
Object.getOwnPropertySymbols(obj)
这个方法的作用类似于Object.keys, 所不同的是Object.keys他只能获取对象当中字符串属性名,而Object.getOwnPropertySymbols方法他获取到的全是Symbol类型的属性名。
for…of
在ECMAScript中遍历数据有很多种方法,首先就是最基本的for循环,他比较适用于去遍历普通的数组,然后是for…in循环,他比较适合去遍历键值对。再有就是一些函数式的遍历方法例如数组对象的forEach方法。
那这些各种各样遍历数据的方式都会有一定的局限性,所以ES2015借鉴了很多其他的语言,引入了一种全新的遍历方式,叫做for…of循环,
for…of 是ECMAScript2015之后新增的遍历方式。未来会作为遍历所有数据结构的统一方式。那换句话说只要你明白for…of内部实现的原理,那你就可以用for…of去遍历任何自定义的结构,我们先来了解一下for…of循环的基本用法。
const arr = [1, 2, 3, 4];
for (const item of arr) {
console.log(item); // 1, 2,3,4
// break; // 终止循环
}
不用于传统的for…in循环,for…of循环拿到的就是数组中的每一个元素,而不是对应的下标。这种循环方式就可以取代我们之前常用的数组实例当中的forEach方法。
而且相比于forEach方法for…of循环他可以使用break关键词随时去终止循环。而forEach方法是无法去终止遍历的。
以前我们为了随时去终止遍历我们必须去使用数组实例的some或者every方法,在some方法的回调函数中我们去返回true在every方法的回调函数中去返回false都可以用来去终止遍历。
而在forEach方法中无论返回true还是false都不会去终止遍历。那现在在for…of中我们就可以使用break随时去终止循环。
除了数组可以直接被for…of循环去遍历,一些伪数组对象也是可以直接被for…of去遍历的,例如arguments,set,map。
for…of在遍历Map时, 可以直接拿到键和值。键和值是直接以数组的形式返回的,也就是说数组的第一个元素就是当前的键名,第二个元素就是值。我们这里就可以配合数组的解构语法,直接拿到键和值。
const m = new Map();
m.set('foo', '123');
m.set('bar', '345');
for (const item if m) {
console.log(item); // ['foo', '123'];
}
for (const [key, value] if m) {
console.log(key, value); // 'foo', '123'
}
for…of是不能直接遍历普通对象的,他要求被遍历的对象必须存在一个叫做Iterable的接口。
可迭代接口
ECMAScript中能够表示有结构的数据类型越来越多,从最早的数组和对象到现在新增了Set和Map,而且我们开发者还可以组合使用这些类型去定义一些符合自己业务需求的数据结构,为了提供一种统一的遍历方式,ES2015提出了一个叫做Iterable的接口,意为可迭代的。如果你不理解编程语言中接口的概念可以理解为一种规格标准,例如ECMAScript中任意一种类型都有toString方法这就是因为他们都实现了统一的规格标准。在编程语言中更专业的说法是他们都实现了统一的接口。
那可迭代接口就是一种可以被for…of循环统一遍历访问的规格标准,换句话说只要这个数据结构实现了可迭代接口他就能够被for…of循环遍历,那这也就是说我们之前尝试的那些能够直接被for…of循环去遍历的数据类型他都已经在内部实现了这个接口。
这里我们脱离掉for…of循环的表象,来看一看这个叫做iterable的接口,到底约定了哪些内容。
在控制台打印arr可以发现在arr的原型对象上存在一个叫做Symbol.iterable的属性,他的值是一个函数。
const arr = [1, 2, 3];
console.log(arr);
iterable约定对象中必须要挂载一个叫做Symbol.iterable的方法,这个方法返回一个对象,对象上存在一个next方法,next方法也返回一个对象,对象中存在value和done两个属性,value的值就是数组中的第一个元素,done的值是false。
const iterable = arr[Symbol.iterable]();
iterable.next(); // 返回一个对象 { value: 1, done: false }
实际上value是当前遍历到的值,done为是否为最后一个。每调用一次next就会后移一位。
总结起来就是所有被for…of 遍历的数据类型必须包含一个叫做iterable的接口,也就是内部必须挂载一个Symbol.iterable方法,这个方法需要返回一个带有next方法的对象,不断调用这个next方法就可以实现对内部所有成员的遍历。这就是for…of循环的内部原理。
实现可迭代接口
了解了for…of循环的内部原理过后我们就应该理解为什么说for…of循环可以作为遍历所有数据结构的统一方式了,因为他内部就是去调用被遍历对象的iterable方法得到一个迭代器,从而去遍历内部所有的数据,这也就是iterable接口所约定的内容。
换句话说只要我们的对象也实现了iterable接口,那我们就可以实现使用for…of循环去遍历我们自己的对象。我们这里定义一个obj对象,然后定义一个叫做Symbol.iterable的属性,然后他的值是一个函数,这个函数需要返回一个对象。
在这个对象中需要提供一个next方法用于实现向后迭代的逻辑。
在next方法中需要返回一个迭代结果对象,这个对象需要有两个成员分别是value和done,这里我们先用固定值,让语法可以通过,然后进行测试。
const obj = {
[Symbol.iterable]: function() { // 约定内部存在一个可迭代的iterable方法
return { // 返回一个对象,对象包含一个next方法。
next: function() {
return { // 返回value和done, value 为当前值,done为是否遍历结束
value: 'yd',
done: true
}
}
}
}
}
for (const item of obj) {
console.log(item);
}
这里并不会打印任何东西,因为第一次遍历的时候done就返回了true,表示迭代结束了,所以并不会进入循环体,也就什么都打印不出来。
我们再来修改一下这个对象,我们在这个对象中方一个数组store用来存放值得被遍历的数据,然后我们在next方法中去迭代这个数组,我们需要去维护一个下标index,我们让他默认等于0;
由于next中的函数并不是obj对象,所以我们使用self去存储一下当前的this供下面使用,在next方法中value就是self.store[index]
,done就是index >= self.store.length
。完成以后我们需要让index++, 也就是让指针后移一位。
const obj = {
store: [1, 2, 3, 4, 5],
[Symbol.iterable]: function() {
let index = 0;
const self = this;
return {
next: function() {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++;
return result;
}
}
}
}
for (const item of obj) {
console.log(item); // 1, 2, 3, 4, 5
}
此时obj就可以正常被for…of遍历了。
迭代器模式
实现可迭代接口,其实这就是设计模式中的迭代器模式,这里我们通过一个小案例来理解这种模式的优势。
假设我们需要设计一个任务清单应用,首先我们要设计一个用于存放所有任务的对象,其他人的任务是把这个对象当中所有的任务项全部去罗列呈现到界面上,为了更有结构的去记录每个数据,可以设计一个对象结构。
在这个对象中定义两个数组分别去存放生活类和学习类的任务
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
}
此时对于其他人而言就必须要了解这个对象当中的数据结构是怎么样的,才能够有可能去遍历到这个对象当中全部的数据内容。
对其他人而言,他们可能需要分别去遍历这两个数组。从而去呈现内部所有的任务。
for (const item of todos.life) {
console.log(item);
}
for (const item of todos.learn) {
console.log(item);
}
那如果这个时候数据结构发生了变化,例如添加了一个全新的类目,但这里的遍历是和之前的数据结构严重耦合的,所以也需要跟着一起去变化。
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['编码']
}
那如果说这个定义的数据结构如果能对外提供一个统一的遍历接口,对于调用者而言就不用去关心对象内部的结构是怎么样的了。更不用关心数据结构改变过后所产生的影响。
例如这里可以在对象内部定义一个each方法,这个方法接收外部的一个回调函数参数,然后在这个函数内部去遍历所有的数据,并且将每个数据都交给这个回调函数。
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['编码'],
each: function (cb) {
[...this.life, ...this.learn, ...this.work].forEach(cb);
}
}
这样一来就相当于对外提供了一个统一遍历的接口,对于其他人就会省心很多,因为根本不用关心内部是什么情况,只管调用这里的each这样一个统一的遍历接口就可以了。
实现可迭代接口也是相同的道理,我们可以使用迭代器来实现这个接口。
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['编码'],
[Symbol.iterator]: function() {
const all = [...this.life, ...this.learn, ...this.work];
let index = 0;
return {
next: function() {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
这样我们在外部就可以使用for…of循环统一去遍历这个todos对象了。这就是实现迭代器的意义,那迭代器这样一个模式他的核心就是对外提供统一遍历接口,让外部不用关心这个数据内部结构是怎样的。只不过我们这里使用的each方法他只适用于当前这个数据结构而ES2015中的迭代器他是语言层面去实现的迭代器模式。所以说他可以去适用于任何数据结构。只需要你通过代码去实现iterable实现他的逻辑就可以了。
这种模式在很多地方都会用到,只不过很多时候我们的观察都是停留在表象上认为我们知道某一个api的使用就可以了,根本就不会去关心他内部做的这样一些事情或者忽略掉很多的为什么。
生成器
在ECMAScript2015中还新增了一种生成器函数,英文叫做generator,引入这样一个新特性的目的就是为了能够在复杂的异步编程中减少回调函数嵌套产生的问题,从而去提供更好的异步编程解决方案。
这里我们先来了解一下生成器函数的语法,以及他的基本应用。定义生成器函数就是在普通的函数function后面添加一个*,这样我们的函数就变成了一个生成器函数。函数执行之后会返回一个生成器对象。
function * foo() {
return 100;
}
const result = foo();
console.log(result);
并且在这个对象上也和迭代器一样有一个next方法,实际上生成器函数也实现了iterable接口,也就是迭代器接口协议。
一般生成器函数在使用中都会配合一个叫做yield的关键字,yield关键词与return关键词类似,但是又有很大的不同。
生成器函数会自动返回一个生成器对象,调用这个生成器的next才会让这个函数的函数体开始执行,执行过程中一旦遇到yield关键词,函数的执行就会被暂停下来,而且yield的值将会被作为next的接过返回,如果继续调用next函数就会从暂停的位置继续向下执行到下一个yield,直到这个函数完全结束。
const * foo() {
console.log(1111);
yield 100;
console.log(2222);
yield 200;
console.log(3333);
yield 300;
}
生成器函数最大的特点就是惰性执行,每调用一次next就会执行一次yield。
生成器应用
了解了生成器函数的基本用法过后这里我们先来看一个简单的应用场景,就是去实现一个发号器。
我们在实际业务开发过程中经常需要用到自增的id,而且我们每次调用这个id都需要在原有的基础上去+1,这里如果我们使用生成器函数去实现这样一个功能是最合适的了。
首先我们定义一个createId生成器函数,然后定义一个初始的id等于1,然后我们通过一个死循环不断的去yield id++。这里不需要担心死循环的问题,因为我们每次在yield过后这个方法会被暂停,循环自然也就会被暂停。直到下一次调用next再次去执行一次又会被暂停下来。
这样我们在外部就可以通过这个方法去创建一个生成器对象id,每次调用一下这个生成器的next方法就能够获取到自增的value,也就是id。
function * createId() {
let id = 1;
while(true) {
yield id++;
}
}
const id = createId();
id.next().value;
当然实现发号器是一个非常简单的需求,我们还可以使用生成器函数实现对象的iterator方法,因为生成器也实现了对象的iterator接口,而且我们使用生成器函数去实现iterator方法会比之前的方式简单很多。
我们这里修改一下之前的案例。我们不需要手动返回迭代器对象了,而是将Symbol.iterator定义为生成器函数,然后在函数内部直接遍历对象成员,然后通过yield去返回每一个被遍历到的对象就可以了。
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['编码'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work];
for (const item of all) {
yield item;
}
}
}
以上就是使用生成器函数的一些简单的用途,但他最总要的目的还是为了解决异步编程过程回调嵌套过深所导致的问题。
ES Modules
ES Modules是ECMAScript2015中标准化的一套语言层面的模块化标准规范,我之前写过一篇模块化发展历程的文章,里面有详细的介绍,里面和commonjs以及其他标准做了统一的对比,感兴趣的可以翻阅一下那篇文章。