总结
- for...in
for(let k in A) { ... }
- 数组遍历只能获得键名(0,1,2,...)
- 能够遍历普通对象的键名
- 只能用来遍历对象、数组、字符串
- 字符串遍历和数组相同只能获得键名
- 会遍历手动添加的属性和原型继承来的属性
- 能够退出循环,for循环中continue退出当次循环,break退出之后所有循环。注释:return是不能直接和for配合使用的,只能在函数中打断函数内for循环继续执行
- for...of
- 数组遍历只能获得键值
- 不能够遍历普通对象,但是ES6提供了entries、keys、values、Array.from()返回遍历器对象
- 能遍历以下数据结构
- 字符串遍历中能够正确识别 32 位 UTF-16 字符,即把
'a\uD83D\uDC0A'
这样的字符串识别为a
普通字符串和\uD83D\uDC0A
32 位 UTF-16 字符
- 只会根据 Iterator 接口返回遍历,数组手动添加属性或在原型上添加属性都不会被遍历
- 一样能够退出循环
- forEach等其他遍历方法
- 原生具备 Iterator 接口的数据结构如下
- Array
- Map(注释:二维数组,可以视为有顺序的对象使用)
- Set
- String
- TypedArray(注释:指一类数据结构,类型化数组)
- 函数的 arguments 对象
- NodeList 对象(注释:一个节点的集合,常通过document.querySelectorAll获得)
- 调用 Iterator 接口的场合
- 解构赋值
let [x,y] = set;
- 扩展运算符
['a', ...arr, 'd']
- yield*
yield* [2,3,4];
- for...of
- Array.from() 注释:从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
- Map(), Set() (比如new Map([['a',1],['b',2]]))注释:Map二维数组,一个数组存键(可以为对象),一个数组存值。先查找键然后通过键的索引查找值。
- WeakMap(), WeakSet() 注释:相对Map和Set而言,弱引用不会影响垃圾回收
- Promise.all()
- Promise.race() 注释:一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
- iterator 遍历器实现
- 对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口
- 如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。return方法的使用场合是,一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
- next方法是必须部署的,return方法和throw方法是否部署是可选的
class LikeArr {
constructor() {
this.arr = arguments;
}
[Symbol.iterator]() {
let index = 0;
const arr = this.arr;
return {
next() {
return {
value: arr[index++],
done: index > arr.length
};
}
};
}
}
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
————————————————————————————————————————————————————————————————————————————————
Iterator(遍历器)的概念
- Iterator 接口主要供for...of消费
- Iterator 的遍历过程是:
- 创建一个指针对象,指向当前数据结构的起始位置。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
- 疑问:指针对象是指创建一个包含内存地址的对象吗?应该是一个普通对象包含value和done两个属性
- 每一次调用next方法,都会返回包含value和done两个属性的对象
- value属性是当前成员的值,最后一个可以省略
- done属性是一个布尔值,表示遍历是否结束。非最后一个时可以省略
- 注释:最后返回的指针对象为
{value:undefined,done:true}
- 疑问:任何语法结构实际都是函数的一种表现方式?
- 使用 TypeScript 的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下(略)
默认 Iterator 接口
- 当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
- 默认的 Iterator 接口部署在数据结构的Symbol.iterator属性
- 注释:done不一定需要返回布尔值,只需要判定为true时遍历便会停止
- 注释:最后一个指针对象是不会被遍历的,应该是先判断的done的真假
- 注释:使用class创建构造函数时,constructor中this定义的属性为当前实例的属性,其他地方定义的为其原型上的属性
// 方法1
class LikeArr {
constructor() {
const arr = arguments;
this[Symbol.iterator] = function() {
let index = 0;
return {
next() {
return {
value: arr[index++],
done: index > arr.length
};
}
};
};
}
}
// 方法2
class LikeArr {
constructor() {
this.arr = arguments;
}
[Symbol.iterator]() {
let index = 0;
const arr = this.arr;
return {
next() {
return {
value: arr[index++],
done: index > arr.length
};
}
};
}
}
// 方法3
function LikeArr() {
this.arr = arguments;
}
LikeArr.prototype[Symbol.iterator] = function() {
let index = 0;
const arr = this.arr;
return {
next() {
return {
value: arr[index++],
done: index > arr.length
};
}
};
};
const likeArr = new LikeArr(1, 3, 5, 6, 7);
for (const iterator of likeArr) {
console.log(iterator);
}
function forFun(arr) {
const iteratorObj = arr[Symbol.iterator]();
const toNext = function(iteratorObj) {
const iterator = iteratorObj.next();
if (iterator.done) return;
console.log(iterator.value);
toNext(iteratorObj);
};
toNext(iteratorObj);
}
forFun(likeArr);
- 原生具备 Iterator 接口的数据结构如下
- Array
- Map(注释:二维数组,可以视为有顺序的对象使用)
- Set
- String
- TypedArray(注释:指一类数据结构,类型化数组)
- 函数的 arguments 对象
- NodeList 对象(注释:一个节点的集合,常通过document.querySelectorAll获得)
- 对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
- 注释:创建一个具有Iterator的类,注意this的用法
- 注释:这是一个生成器?适用于需要遍历每一项的大数据,用以节省空间。
- 注释:该class只能遍历一次,源于它特别的Symbol.iterator返回,而且在遍历时更省内存
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
- 通过遍历器实现指针结构的例子
- 注释:
current = current.next;
实现当前指针向下一个对象移动
- 注释:可以把遍历过程理解为对当前实例next方法的调用结果,而这个当前对象是可以变动的
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
} else {
return { done: true };
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
- 对于类似数组的对象(存在数值键名和length属性),,部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口
- 注释:这里的
3:'d'
是不会遍历到的,受制于length。同理其他属性也不会被遍历
let iterable = {
0: 'a',
1: 'b',
2: 'c',
3: "d",
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
调用 Iterator 接口的场合
(1)解构赋值
- 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
(2)扩展运算符
- 提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
(3)yield*
- yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
- 注释:这里讲的是
yield* [2,3,4]
在for...of遍历时作为已扩展对象,即遍历了1后,依次遍历2,3,4,而不是把[2,3,4]作为一个遍历对象
- 注释:generator可以作为一个Symbol.iterator使用
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4)其他场合
- 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。
- for...of
- Array.from() 注释:从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
- Map(), Set() (比如new Map([['a',1],['b',2]]))注释:Map二维数组,一个数组存键(可以为对象),一个数组存值。先查找键然后通过键的索引查找值。
- WeakMap(), WeakSet() 注释:相对Map和Set而言,弱引用不会影响垃圾回收
- Promise.all()
- Promise.race() 注释:一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
字符串的 Iterator 接口
- 可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的
Iterator 接口与 Generator 函数
- Symbol.iterator方法的最简单实现,还是使用下一章要介绍的 Generator 函数
- 注释:Generator函数返回一个含有next方法的对象
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
// 或者采用下面的简洁写法
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
遍历器对象的 return(),throw()
- next方法是必须部署的,return方法和throw方法是否部署是可选的
- 如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。return方法的使用场合是,一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
- return方法必须返回一个对象,这是 Generator 规格决定的
- 注释:chrome 80.0.3987.116 return方法并不一定需要返回
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
// 会在执行return方法关闭文件之后,再抛出错误
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
for...of 循环
数组
- JavaScript 原有的for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值。
- for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
Set 和 Map 结构
- Map进行for...of循环时,遍历的value是一个长度为2的数组,第一位为key第二位为val
var es6 = new Map();
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
计算生成的数据结构
- entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
- keys() 返回一个遍历器对象,用来遍历所有的键名。注释:set返回键值组成的数组
- values() 返回一个遍历器对象,用来遍历所有的键值。
类似数组的对象
- for...of循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。注释:这里
\uD83D\uDC0A
是一个32的 UTF-16 字符
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
- Array.from() 可以通过以下方式来创建数组对象
- 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
- 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
对象
- 对于普通的对象,for...in循环依然可以用来遍历键名。
与其他遍历语法的比较
- 数组无法中途跳出forEach循环,break命令或return命令都不能奏效。
- for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
- 某些情况下,for...in循环会以任意顺序遍历键名。
- for...in循环主要是为遍历对象而设计的,不适用于遍历数组
- for循环中continue退出当次循环,break退出之后所有循环。注释:return是不能直接和for配合使用的,只能在函数中打断函数内for循环继续执行