迭代器对象与生成器函数

215 阅读6分钟

前言

迭代器iterator

当我们遍历数组的时候,用for 循环的时候,如果多个循环嵌套,则需要有多个循环变量,每次需要追踪多个变量,所以,利用迭代器可以消除这种问题。本质上其实迭代器也是一个对象,而且Es6也提出了for...of 循环,来配合遍历迭代器.与for循环相比,for...of不需要追踪值在集合中的位置,也不需要控制循环结束的时机。

迭代器

迭代器是一种对象,是一种专有接口,所有的迭代器对象都有一个next() 方法,调用next() 方法后会返回一个结果对象,这个对象包含两个值,一个是value,值是要返回的当前值,一个是done,done 是一个布尔值,如果还有可返回的value,则done返回false,如果没有,则done返回false, 当done 返回true的时候,value 是函数调用过程中最后一次给调用者传递的参数,如果没有,则返回undefined.

我们来自己创建一个迭代器,其实十分简单,返回一个迭代器对象。

const arr = [1, 2, 3];

const myIterator = (item) => {
  let i = 0;
  return {
    next: () => {
      let done = i >= item.length;
      let value = done ? undefined : item[i++];
      return {
        value,
        done,
      };
    },
  };
};

let iter = myIterator(arr);
console.log(iter.next(), "--->1"); // { value: 1, done: false } --->1
console.log(iter.next(), "---->2"); // { value: 2, done: false } ---->2
console.log(iter.next(), "---->3"); // { value: 3, done: false } ---->3
console.log(iter.next(), "---->3"); // { value: undefined, done: true } ---->3

迭代器对象除了具有next() 方法外,还具有return() 方法和throw()方法,不过return()方法和throw()方法不是必须的

return() 方法

如果for...of 循环出错或者有break语句导致提前退出,就会调用return()方法。return()方法必须返回一个对象。但是return() 的返回值是不会生效的。

const myIterator = (item) => {
  let i = 0;
  return {
    next: () => {
      let done = i >= item.length;
      let value = done ? undefined : item[i++];
      return {
        value,
        done,
      };
    },
    return: () => {
    	console.log('提前退出')
    	return {done: true}
    }
  };
};

let obj = {age: '1', name: 'ss'};
obj[Symbol.iterator] = () => { 
    return myIterator([1,2,3,4])
}

// 返回1, 调用return 方法,打印提前退出
for (let item of obj) {
	console.log(item) // 1
	break;
}

throw() 方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。

Symbol.iterator

Iterator 为所有的数据结构提供了一种统一的访问机制,我们可以利用 for...of 来进行循环遍历。

当我们用for...of 遍历对象的时候,就会提示报错,但是遍历数组或者map, set, 字符串等就可以,是因为数组,map, set,字符串部署了Iterator 接口,简单来讲,就是他们都有一个内置的Symbol.iterator属性

如果对Symbol属性还不了解的,可以点击这里学习~

let arr = {age: '1', name: 'ss'}
for (let item of arr) {
  console.log(i) // TypeError: arr is not iterable
}

let array = [1,2,3];
// 和arr对象不同的是,
//  我们可以在array.__proto__下看到,有一个Symbol(Symbol.iterator): values() 属性

原生默认具备Symbol.iterator属性的数据结构有

  • Array
  • Map
  • Set
  • String
  • 函数的arguments对象
  • NodeList对象

一个数据结构只要有Symbol.iterator属性,就可以认为是可遍历的,就可以使用for...of 来进行遍历。Symbol.iterator 的值就是一个返回迭代器对象的函数

// 我们把上面刚刚写的myIterator函数赋值给obj的Symbol.iterator
let obj = {age: '1', name: 'ss'};
obj[Symbol.iterator] = () => { 
    return myIterator([1,2,3,4])
}

// 然后用for...of 遍历 没有报错
for (let item of obj) {
	console.log(item) // 1,2,3,4
}

我们也可以改变他的Symbol.iterator属性。

let str = '你捉不到的this';
str[Symbol.iterator] = () => {
	return myIterator([1,2,3,4])
}
for (let item of str) {
	console.log(item) // 1,2,3,4
}

模拟for of

实现for...of 也很简单,获取到Symbol.iterator属性的迭代器对象然后遍历

const myForOf = (item, callback) => {
  if (typeof callback !== "function") {
    return new TypeError("callback is not function");
  }

  if (typeof item[Symbol.iterator] !== "function") {
    return new TypeError(`${item} is not iterator`);
  }

  let getIteratorObj;
  let result;
  getIteratorObj = item[Symbol.iterator]();

  result = getIteratorObj.next();
  while (!result.done) {
    callback(result.value);
    result = getIteratorObj.next();
  }
};

myForOf([1, 2, 3, 4], (i) => {
  console.log(i, "--->myForOF");
});

生成器

上面我们自己手动编写了一个简单的迭代器对象,但是实际上的迭代器编写规则是十分复杂的,所以,ES6 还提供了一个生成器函数,用来创建返回迭代器对象。

生成器默认会给Symbol.Iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象

生成器是一个返回迭代器的函数

他有几个特征。一个是 function关键字后与函数名之间有一个* 号,调用方式和普通函数方式调用相同,函数体内部使用yield 表达式,来定义不同的内部状态,相当于是一个状态机

function * createInterator() {
  console.log(1);
  yield 1;
  console.log(2);
  yield 2;
  console.log(3);
  yield 3;
  // return 4  则next() 返回{value: 4, done: true}
}

let r = createInterator();

r.next() // 1, {value: 1, done: false}
r.next() // 2, {value: 2, done: false}
r.next() // 3, {value: 3, done: false}
r.next() // {value: undefined, done: true}

// 注意: 每次调用next()。都会执行到yield 语句,然后不在执行,直到调用下一个next 方法,并且返回一个对象,value值为yield的返回值。如果函数最后return 的话,调用next 返回的value是函数最后return 的值

// 当执行next 的时候,会执行到遇到yield表达式的那行,后面的代码暂停,然后yield 后面表达式的值作为返回对象的value, 下一次调用next,继续往下执行,知道遇到下一个yield。 如果没有新的yield,直到遇到return,将return 值当成value值,并且done 为true, 如果继续next(),则返回{value: undefined, done: true}
let obj = {
	create: function *(){
		console.log('--->生成器')
	}
}
let obj1 = {
	*create(){
		console.log('--->生成器')
	}
}

不可以使用箭头函数来创建生成器函数

yield

Yield 只能在生成器内部使用,在其他地方使用会抛出错误,即使在生成器函数的内部函数中使用也不可以

next()

next()方法可以传入一个参数,参数表示的是上一个yield表达式的值(也就相当于上一个yield表达式返回的值)。如果给第一次调用next()方法传入参数,是无效的,会被忽略。因为第一次调用next() 方法前不会执行任务yield语句

function * create(x){
    yield x + 1;
    yield x + 2;
    let z = yield 3;
    yield z + 5;
}

let c = create(1);
c.next() // {value: 2, done: false}
c.next() // {value: 3, done: false}
c.next() // {value: 3, done: false}
c.next() // 没有传入参数,相当于undefined,所以上一次yield的返回值z = undefined, undefined + 5 = NAN, 所以返回 {value: NaN, done: false}

throw()

生成器函数返回的迭代器对象含有一个throw方法,可以将它抛出的错误传递给生成器函数内部,在内部进行捕获。

调用throw()方法后,会抛出一个错误,后面的代码是否执行,取决于生成器函数内部的代码,如果将错误捕获,就会接着执行,不捕获就不执行

function * create(){
    yield 1;
    yield 2;
    try {
      yield 3;
    } catch(err) {
      yield 4;
    }
    yield 5;
}
let r = create()
r.next() // {value: 1, done: false}
r.throw(new Error('xxx')) // Error
r.next() // {value: undefined, done: true}  如果内部没有捕获错误,下次nextdone直接会变为 true

return

如果生成器函数中return, 则return后面的语句不再执行,如果return了值,则值是value的值

function * create() {
	yield 1;
	yield 2;
  console.log('2')
	return 3;
}

let r = create();
r.next() // {value: 1, done: false}
r.next() // {value: 2, done: false}
r.next() //  2 {value: 3, done: true}

看到这里啦,点个赞吧~~

如有错误,还请指出~~