《JavaScript娓娓道来》系列文章属于JavaScript进阶知识,不同于《JavaScript面试大师》系列知识点+刷题的模式,该系列采取:实例+原理+代码的模式来展现写代码的思路,介绍JavaScript进阶过程中的难点,帮助初级工程师成长为中级和高级工程师。
实例:自定义一个数据结构myBook,该结构内部为(字符串-数组)键值对,表示我拥有的书本作者及对应的书名
let myBook = {
AUTHOR_A: ['A1', 'A2', 'A3', 'A4', 'A5'],
AUTHOR_B: ['B1', 'B2', 'B3', 'B4'],
AUTHOR_C: ['C1', 'C2', 'C3', 'C4']
}
现在需要获得我的所有书本,常用的方法是遍历它们,因此可以定义一个方法getBook,返回书名数组
let myBook = {
AUTHOR_A: ['A1', 'A2', 'A3', 'A4', 'A5'],
AUTHOR_B: ['B1', 'B2', 'B3', 'B4'],
AUTHOR_C: ['C1', 'C2', 'C3', 'C4'],
getBook: function () {
let keys = Object.keys(myBook);
let books = [];
for (let i = 0, key = keys[i]; i < keys.length; i++) {
books = books.concat(myBook[key]);
}
return books;
}
}
myBook.getAuthorBook(); // ['A1', 'A2', 'A3', 'A4', 'A5','B1', 'B2', 'B3', 'B4','C1', 'C2', 'C3', 'C4']
但是定义方法有两个问题:
- 任意自定义的数据结构,都必须添加一个方法(getBook)遍历该结构,使用结构必须了解名称不同的方法
- 如果需要的数据结构是{ name: ['AUTHOR_A', 'AUTHOR_B', 'AUTHOR_C' ], book: ['A1', 'A2',...,'C1',...,'C4'] }时,必须修改该方法
针对这两个问题,我们考虑:
- 任何数据结构的遍历方法采用同一方法名
- 遍历过程中返回统一的数据结构,开发者使用时可以按需包装该结构
let myBook = {
AUTHOR_A: ['A1', 'A2', 'A3', 'A4', 'A5'],
AUTHOR_B: ['B1', 'B2', 'B3', 'B4'],
AUTHOR_C: ['C1', 'C2', 'C3', 'C4'],
[uniqueMethod]: function () {
// 迭代过程返回统一的数据结构
}
}
原理:ES6中引入了Iterator的概念,目的就是在于为各种数据结构,提供统一的访问接口。这里有两个概念,可迭代器对象Iterable和迭代器Iterator。使用Typescript表示他们的关系如下:
// 可迭代对象一定部署了[Symbol.iterator]方法
interface Iterable {
[Symbol.iterator]() : Iterator;
}
// 执行 [Symbol.iterator]方法得到迭代器
interface Iterator {
next() : IteratorResult;
}
// 迭代器具有next方法,返回迭代结果
interface IteratorResult {
value: any;
done: boolean;
}
概念放到示例中,我们希望myBook是可迭代对象Iterable,它有个唯一的方法能够返回每次的迭代器Iterator(即迭代结果) ,该方法名为 Symbol.iterator ,执行得到迭代器
代码:
部署 [Symbol.iterator] 方法之前,myBook是不可迭代的
let myBook = {
AUTHOR_A: ['A1', 'A2', 'A3', 'A4', 'A5'],
AUTHOR_B: ['B1', 'B2', 'B3', 'B4'],
AUTHOR_C: ['C1', 'C2', 'C3', 'C4']
}
for(let author of myBook){
console.log(author);
}
部署 [Symbol.iterator] 方法之后,myBook是可迭代的
let myBook = {
AUTHOR_A: ['A1', 'A2', 'A3', 'A4', 'A5'],
AUTHOR_B: ['B1', 'B2', 'B3', 'B4'],
AUTHOR_C: ['C1', 'C2', 'C3', 'C4'],
[Symbol.iterator]: function () {
let index = 0;
let keys = Object.keys(myBook);
let author = keys[index];
return {
next: function () {
return {
value: {
name: author,
book: myBook[author]
},
done: keys[index++] ? false : true
}
}
}
}
}
for (let author of myBook) {
console.log(author);
}