博客 / 詳情

返回

你一定要知道的iterator和generator

generator是一種特殊的iterator,generator可以替代iterator實現,使代碼更為簡潔

什麼是iterator

iterator叫做迭代器,是用來幫助某個數據結構進行遍歷的對象,這個對象需要符合迭代器協議(iterator protocol)。

迭代器協議要求實現next方法,next方法有如下要求

  • 0或者1個函數入參
  • 返回值需要包括兩個屬性,done 和 value。
    當遍歷完成時, done 為 true,value 值為 undefined。

迭代器實現原理

  • 創建一個指針對象,指向當前數據結構的起始位置
  • 第一次調用對象的next方法,指針自動指向數據結構的第一個成員
  • 接下來不斷調用next方法,指針一直往後移動,直到指向最後一個成員
  • 每調用next方法返回一個包含value 和 done 屬性的對象

以下對象就實現了迭代器

const names = ["kiki", "alice", "macus"];
let index = 0;
const namesIterator = {
  next: () => {
    if (index == names.length) {
      return { value: undefined, done: true };
    } else {
      return { value: names[index++], done: false };
    }
  },
};

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

當第四次調用next方法時,此時數據已經迭代完成,所以迭代器返回 done 為 true

可迭代對象

可迭代對象與迭代器對象不同。

  • 迭代器對象需要符合迭代器協議(iterator protocol),並且返回next方法。
  • 可迭代對象需要實現iterable protocol協議,即 @@iterator 方法,在代碼中通過 Symbol.iterator 實現,這樣當數據進行for...of遍歷時,就用調用@@iterator方法。

我們知道,對象這種數據類型是不可以通過for...of對其遍歷的

但如果我們對它實現了@@iterator方法之後,它就變成了可迭代對象

const obj = {
  name: "alice",
  age: 20,
  hobby: "singing",
  [Symbol.iterator]: function () {
    let index = 0;
    const keys = Object.keys(this);
    return {
      next: () => {
        if (index == keys.length) {
          return { value: undefined, done: true };
        } else {
          const key = keys[index];
          index++;
          return { value: this[key], done: false };
        }
      },
    };
  },
};
for (let item of obj) {
  console.log(item);
}
const iterator = obj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

可迭代對象的實現中包括了迭代器對象。

原生可迭代對象

簡單來説,可以通過for...of遍歷的就是可迭代對象,原生可迭代對象包括:數組、字符串、arguments、set、map

const arr = ["kiki", "alice", "macus"];
for (let item of arr) {
  console.log("數組", item);
}

const str = "hello";
for (let s of str) {
  console.log("字符串", s);
}

function foo() {
  for (let arg of arguments) {
    console.log("arguments", arg);
  }
}
foo(1, 2, 3, 4);

const mapEntries = [
  ["name", "alice"],
  ["age", "20"],
];
const map = new Map(mapEntries);
for (let m of map) {
  console.log("map", m);
}

const set = new Set(arr);
for (let s of set) {
  console.log("set", s);
}

以上都是原生可迭代對象

可迭代對象的用途

可迭代對象有以下用途

  • for...of遍歷
  • 展開語法
  • 解構賦值
  • 創建其他類型的對象,如array和set
  • Promise.all也可以執行可迭代對象

對象可以使用展開語法和解構賦值,但它並不是可迭代對象,而是es9中單獨實現的屬性

const iteratorObj = {
  names: ["kiki", "alice", "macus"],
  [Symbol.iterator]: function () {
    let index = 0;
    return {
      next: () => {
        if (index == this.names.length) {
          return { value: undefined, done: true };
        } else {
          return { value: this.names[index++], done: false };
        }
      },
    };
  },
};
for (let item of iteratorObj) {
  console.log('for..of遍歷可迭代對象:',item);
}

const newArr = [...iteratorObj];
console.log('展開語法:',newArr);

const [name1, name2, name3] = iteratorObj;
console.log('解構賦值:',name1, name2, name3);

const set = new Set(iteratorObj);
const arr = Array.from(iteratorObj);
console.log('set:',set);
console.log('array:',arr)

Promise.all(iteratorObj).then((value) => {
  console.log("promise.all:", value);
});

以上方法都是獲取next方法中的value值

自定義類的迭代

想要類變成可迭代對象,在類方法中添加 Symbol.iterator 方法並實現就可以了

class Student {
  constructor(name, age, hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }
  push(hobby) {
    this.hobbies.push(hobby);
  }
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index === this.hobbies.length) {
          return { done: true, value: undefined }
        } else {
          return { value: this.hobbies[index++], done: false };
        }
      },
    };
  }
}

const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
  console.log(item)
}

此時可以通過for..of方法遍歷類中的hobbies屬性

什麼是generator

generator叫做生成器,可以用來控制函數什麼時候執行和暫停。

生成器函數也是函數,但是和普通函數之間存在如下區別

  • 生成器函數之間需要添加一個*
  • 執行需要使用一個變量來接收, 每使用一次 next()方法執行一段代碼
  • 通過 yield 關鍵字暫停函數,yield既可以傳參, 又有返回值
  • 返回值是一個生成器(generator),生成器是一種特殊的迭代器

以上代碼實現了生成器函數

function* foo(){
  console.log('開始執行')
  yield
  console.log('world')
  yield
  console.log('結束執行')
}
const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

調用next方法時,返回值與迭代器一致,為包含 value 和 done 的對象,此時value為undefine,因為yield後沒有加上返回值

yield傳參和返回值

通過next方法可以將參數傳遞到生成器函數中,通過yield可以返回數據

function* foo(value1){
  console.log('開始執行')
  const result1 = yield value1
  const result2 = yield result1
  const result3 = yield result2
  console.log('結束執行')
}

const generator = foo('hello')
console.log(generator.next('世界'))
console.log(generator.next('merry'))
console.log(generator.next('christmas'))
console.log(generator.next('done'))

可以看到第一個next獲取的value值是通過生成器函數傳遞的,而不是第一個next方法執行時的參數,所以value值為"hello"而不是"世界"

generator的其他方法

  • throw方法用於拋出異常(需要在生成器函數中捕獲)
  • return方法用於中斷生成器函數的執行
function* createGenerator() {
  console.log("開始執行");
  try {
    yield "hello";
    console.log("hello");
  } catch (error) {
    yield error;
  }
  yield "world";
  console.log("結束執行");
}

const generator = createGenerator();
console.log(generator.next());
console.log(generator.throw("throw"));
console.log(generator.return("return"));

使用return方法後,done變為true,value就變成了return函數中傳遞的值

生成器替代迭代器

生成器是一種特殊的迭代器,通過生成器可以在某些場景做一些替換,使代碼更為簡潔

// 迭代器實現next方法
function createArrayIterator(arr) {
  let index = 0;
  return {
    next: function () {
      if (index == arr.length) {
        return { value: undefined, done: true };
      } else {
        return { value: arr[index++], done: false };
      }
    },
  };
}

// generator遍歷暫停函數
function* createArrayGenerator(arr) {
  for(let item of arr){
    yield item
  }
}

// yiled 語法糖
function* createArraYield(arr) {
  yield* arr
}

const arr = ['alice', 'kiki', 'macus']
const iterator = createArrayIterator(arr)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

const generator = createArrayGenerator(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

const yiledGen = createArraYield(arr)
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())

以上三種方式所實現的功能是一致的

類中使用生成器

類中遍歷的方式由迭代器改為生成器

class Student {
  constructor(name, age, hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }
  push(hobby) {
    this.hobbies.push(hobby);
  }
  *[Symbol.iterator]() {
    yield* this.hobbies
  }
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
  console.log(item)
}

以上就是iterator和generator的用法和聯繫,關於js高級,還有很多需要開發者掌握的地方,可以看看我寫的其他博文,持續更新中~

user avatar tnfe 頭像 kanshouji 頭像 ihengshuai 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.