什么是迭代器
迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
迭代器协议
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)。
迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式。
那么在js中这个标准就是一个特定的next方法。
next方法的要求
一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象。
-
done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
-
value
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
迭代器代码演示
一个简单的迭代器对象
const iterator = {
next(){
return {
done:true,
value:'123'
}
}
}
我们可以创建一个迭代器对象来访问数组。我们可以通过next()方法来访问迭代器。直到数组中所有的元素全部被迭代完了,调用next()方法会返回{ done: true, value: undefined }
const names = ["abc","cba","ccc"]
let index = 0
const namesIterator = {
next(){
if(index < names.length){
return {done:false,value:names[index++]}
}else{
return {done:true, value:undefined}
}
}
}
console.log(namesIterator.next());//{ done: false, value: 'abc' }
console.log(namesIterator.next());//{ done: false, value: 'cba' }
console.log(namesIterator.next());//{ done: false, value: 'ccc' }
console.log(namesIterator.next());//{ done: true, value: undefined }
我们可以使用生成迭代器的函数来优化一下上面的代码。
function createArrayIterator(arr){
let index = 0;
return { //返回一个迭代器对象
next(){
if(index < arr.length){
return {done:false, value : arr[index++]}
}else{
return {done:true, value : undefined}
}
}
}
}
const names = ["abc","cba","ccc"]
let namesiterator = createArrayIterator(names)
console.log(namesiterator.next());
console.log(namesiterator.next());
console.log(namesiterator.next());
console.log(namesiterator.next());
可迭代对象
上面的代码我们整体上来看是十分奇怪的,比如我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象,事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象。
什么是可迭代对象
呢?
它和迭代器是不同的概念,当一个对象实现了iterable protocol协议时,它就是一个可迭代对象,这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性。
当我们要问一个问题,我们转成这样的一个东西有什么好处呢?
当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的 @@iterator 方法。
区分迭代器和迭代器对象
迭代器是一个对象,符合迭代器协议iterable protocol
const iterator = {next(){ return {}}}
可迭代对象,当一个对象实现了iterable protocol协议,它就是一个可迭代对象。要求实现[Symboliterator]方法(函数)
const iterableObj = {[Symbol.iterator]}(){return 迭代器}
那么这个iterableObj
就是一个可迭代对象了
迭代器对象代码演示
const iterableObj = {
names : ["abc","cba","ccc"],
[Symbol.iterator](){
let index = 0;
return {
next:() =>{ //一定要是箭头函数
if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层作用域找
//如果不写箭头函数,调用的时候就是返回的对象.next(),不是iterableObj了
return {done:false,value:this.names[index++]}
}else{
return {done:true,value:undefined}
}
}
}
}
}
console.log(iterableObj[Symbol.iterator]); //[Function: [Symbol.iterator]]
//1.第一次调用
const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// //2.第二次调用(不会有第一次调用的值)
// const iterator1 = iterableObj[Symbol.iterator]()
// console.log(iterator1.next());
// console.log(iterator1.next());
// console.log(iterator1.next());
// console.log(iterator1.next());
迭代器对象有什么用呢?
比如for of 它可以遍历的东西必须是一个可迭代对象。(for of本质就是用了迭代器的next())
//比如普通对象你就无法for of,它不是迭代对象
const obj = {
name : 'harry',
age:21
}
for(const item of obj){
console.log(item); //obj is not iterable
}
如果这个对象是可迭代的对象,那就没问题。
const iterableObj = {
names : ["abc","cba","ccc"],
[Symbol.iterator](){
let index = 0
return {
next:()=> {
if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层作用域找
//如果不写箭头函数,调用的时候就是返回的对象.next(),不是iterableObj了
return {done:false,value:this.names[index++]}
}else{
return {done:true,value:undefined}
}
}
}
}
}
for(const item of iterableObj){ //for of其实是一个语法糖,本质上的来源就是可迭代对象。什么时候done为true,就自动结束了。
console.log(item);
}
内置的可迭代对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的。
比如说:String
、Array
、Map
、Set
、arguments对象
、NodeList集合
拿数组来举例:
const names = ["abc","cba","ccc"] //数组对象本身就是一个可迭代对象
console.log(names[Symbol.iterator]); //[Function: values]
//代码1
const iterator1 = names[Symbol.iterator]()
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
//代码2
for(const item of names){
console.log(item); //代码2就是代码1的语法糖
}
其他的演示
//Map/Set
const set = new Set()
set.add(10)
set.add(20)
for(item of set){
console.log(item);
}
//函数中的arguments
function foo(x,y,z){
for(const arg of arguments){
console.log(arg);
}
}
foo(10,20,30)
可迭代对象的应用场景
展开运算符就是可迭代对象的应用场景。
const iterableObj = {
names : ["abc","cba","ccc"],
[Symbol.iterator](){
let index = 0
return {
next:()=> {
if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层找
return {done:false,value:this.names[index++]}
}else{
return {done:true,value:undefined}
}
}
}
}
}
//1.展开运算符
const names = ["abc","cba","nba"]
const newNames = [...names] //迭代器的用法
console.log(newNames);
//2.关于对象的展开,这是ES9新增的特性,使用的不是迭代器
// const newObj = {...obj}
// console.log(newObj);
//3.解构语法
const [name1,name2] = names
// console.log(name1);
//这也是es9新增的,不是迭代器
const {name,age} = obj
//4.创建一些其他对象的时候
const set1 = new Set(iterableObj)
const set2 = new Set(names)
const arr1 = Array.from(iterableObj)
//5.Promise.all
Promise.all(iterableObj)l.then(res => {
console.log(res);
})
自定义类的可迭代性
class Classroom{
constructor(address,name,students){
this.address = address
this.name = name
this.students = students
}
entry(newStudent){
this.students.push(newStudent)
}
[Symbol.iterator](){
let index = 0
return {
next:() => {
if(index < this.students.length){
return {done:false,value:this.students[index++]}
}else{
return {done:true,value:undefined}
}
},
return: ()=>{
console.log('迭代器终止了');
return {done:true,value:undefined}
}
}
}
}
const classroom = new Classroom("3撞5楼200","计算机教室",["james","curry","lebro"])
classroom.entry("lilei")
for(const item of classroom){
console.log(item); //不加迭代器会报错
}