各位观众老爷,晚上好!今天咱们聊聊JavaScript里一个挺有意思的东西:Iterable
协议。这玩意儿说白了,就是让你自己定义的 JavaScript 对象也能像数组一样,用 for...of
循环来遍历。听起来是不是挺酷炫的?
咱们先打个预防针,这玩意儿初学可能会觉得有点绕,但只要你跟着我一步一步走,保证你能掌握。而且,掌握了它,你在 JavaScript 的江湖地位,绝对能提升一个档次。
开场白:for...of
的诱惑
在 JavaScript 的世界里,我们经常需要遍历一些数据结构。比如数组:
const myArray = [1, 2, 3, 4, 5];
for (const element of myArray) {
console.log(element); // 输出 1, 2, 3, 4, 5
}
这个 for...of
循环用起来是不是感觉特别舒服?简洁明了,比传统的 for
循环和 forEach
方便多了。
但是,如果你想让自己的对象也能用 for...of
循环,那就需要用到今天的主角:Iterable
协议了。
什么是 Iterable
协议?
Iterable
协议其实很简单,它规定了一个对象要能够被 for...of
循环遍历,必须满足两个条件:
-
必须有一个
Symbol.iterator
属性。 这个属性的值是一个函数,这个函数返回一个迭代器(Iterator)对象。 -
迭代器对象必须有一个
next()
方法。 这个next()
方法返回一个对象,这个对象包含两个属性:value
和done
。value
:表示当前迭代的值。done
:表示迭代是否结束。如果迭代结束,done
的值为true
,否则为false
。
是不是有点抽象?别怕,咱们用一个表格来总结一下:
协议 | 说明 |
---|---|
Iterable |
定义了对象如何被 for...of 循环遍历。 |
Symbol.iterator |
对象的一个属性,其值是一个函数,返回一个迭代器对象。 |
Iterator |
由 Symbol.iterator 返回的对象,必须包含 next() 方法。 |
next() |
迭代器对象的方法,返回一个对象,包含 value 和 done 属性。 |
value |
next() 方法返回的对象的一个属性,表示当前迭代的值。 |
done |
next() 方法返回的对象的一个属性,表示迭代是否结束。 true 表示迭代结束, false 表示迭代未结束。 |
举个栗子:自定义一个可迭代对象
光说不练假把式,咱们来写一个例子,让大家更直观地理解 Iterable
协议。
假设我们有一个 Range
类,表示一个数字范围。我们希望能够用 for...of
循环来遍历这个范围内的所有数字。
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let currentValue = this.start;
const that = this; // 避免 `this` 指向问题
return {
next() {
if (currentValue <= that.end) {
return { value: currentValue++, done: false };
} else {
return { value: undefined, done: true };
}
},
};
}
}
const myRange = new Range(1, 5);
for (const number of myRange) {
console.log(number); // 输出 1, 2, 3, 4, 5
}
咱们来一步一步分析一下这段代码:
-
[Symbol.iterator]()
方法: 这是实现Iterable
协议的关键。这个方法返回一个迭代器对象。 -
迭代器对象: 这个对象包含一个
next()
方法。 -
next()
方法: 这个方法返回一个对象,包含value
和done
属性。value
:表示当前迭代的值,也就是currentValue
。done
:表示迭代是否结束。当currentValue
大于this.end
时,迭代结束,done
的值为true
。
更简洁的写法:使用生成器函数
上面的代码稍微有点繁琐,我们可以使用生成器函数来简化代码。生成器函数是 ES6 引入的一个新特性,它可以更方便地创建迭代器。
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const myRange = new Range(1, 5);
for (const number of myRange) {
console.log(number); // 输出 1, 2, 3, 4, 5
}
在这个版本中,我们使用了 *
符号来定义一个生成器函数。生成器函数使用 yield
关键字来产生值。
yield i
相当于:
return { value: i, done: false };
当循环结束时,生成器函数会自动返回:
return { value: undefined, done: true };
是不是感觉清爽多了?
Iterable
协议的应用场景
Iterable
协议的应用场景非常广泛,比如:
- 自定义数据结构: 你可以用它来让你的自定义数据结构支持
for...of
循环。 - 异步数据流: 你可以用它来处理异步数据流,比如从服务器获取数据。
- 惰性计算: 你可以用它来实现惰性计算,也就是只有在需要的时候才计算值。
再来几个例子:
- 斐波那契数列:
class Fibonacci {
constructor(max) {
this.max = max;
}
*[Symbol.iterator]() {
let a = 0;
let b = 1;
let count = 0;
while (count < this.max) {
yield a;
[a, b] = [b, a + b];
count++;
}
}
}
const fibonacciSequence = new Fibonacci(10);
for (const number of fibonacciSequence) {
console.log(number); // 输出斐波那契数列的前10项
}
- 无限循环的字母表:
class Alphabet {
*[Symbol.iterator]() {
let charCode = 'A'.charCodeAt(0);
while (true) {
yield String.fromCharCode(charCode);
charCode++;
if (charCode > 'Z'.charCodeAt(0)) {
charCode = 'A'.charCodeAt(0);
}
}
}
}
const alphabet = new Alphabet();
let count = 0;
for (const letter of alphabet) {
console.log(letter);
count++;
if (count > 25) { // 限制循环次数
break;
}
}
Iterable
协议与 Iterator
协议
Iterable
协议和 Iterator
协议经常一起出现,它们是密切相关的。
Iterable
协议定义了如何获取一个迭代器。Iterator
协议定义了迭代器应该如何工作。
一个对象要能够被 for...of
循环遍历,必须同时满足这两个协议。
内置的可迭代对象
JavaScript 内置了很多可迭代对象,比如:
Array
String
Map
Set
arguments
对象NodeList
(DOM 节点列表)
这些对象都实现了 Iterable
协议,所以可以直接用 for...of
循环来遍历。
for...in
vs for...of
很多同学容易把 for...in
和 for...of
搞混,它们之间有很大的区别:
for...in
循环遍历的是对象的属性名(key)。for...of
循环遍历的是对象的属性值(value)。
const myObject = { a: 1, b: 2, c: 3 };
for (const key in myObject) {
console.log(key); // 输出 "a", "b", "c"
}
// myObject 不是 Iterable 对象,直接使用 for...of 会报错
// for (const value of myObject) {
// console.log(value);
// }
总结
Iterable
协议是 JavaScript 中一个非常重要的概念,它可以让你自定义的对象支持 for...of
循环。掌握了它,你可以写出更简洁、更优雅的代码。
咱们再用一个表格来总结一下今天的内容:
概念 | 说明 |
---|---|
Iterable 协议 |
定义了对象如何被 for...of 循环遍历。 |
Symbol.iterator |
对象的一个属性,其值是一个函数,返回一个迭代器对象。 |
Iterator 协议 |
定义了迭代器应该如何工作。 |
next() 方法 |
迭代器对象的方法,返回一个对象,包含 value 和 done 属性。 |
value |
next() 方法返回的对象的一个属性,表示当前迭代的值。 |
done |
next() 方法返回的对象的一个属性,表示迭代是否结束。 true 表示迭代结束, false 表示迭代未结束。 |
生成器函数 | 可以更方便地创建迭代器。使用 yield 关键字来产生值。 |
for...in vs for...of |
for...in 循环遍历的是对象的属性名(key),for...of 循环遍历的是对象的属性值(value)。 |
希望今天的讲座对大家有所帮助。 记住,编程之路没有捷径,只有不断学习和实践,才能成为真正的编程高手。
下次再见!