Расширенный синтаксис JavaScript (итератор, генератор), который нельзя пропустить

внешний интерфейс JavaScript

Как мы все знаем, статус js во фронтенд-разработке. Очень важно хорошо этому научиться.

В следующей статье представлены Iterator, Generator.

Iterator

что такое итератор

Итераторы — это объекты, которые помогают нам перемещаться по структуре данных.

В JavaScript итератор также является конкретным объектом, который должен соответствоватьпротокол итератора(итераторский протокол).

  • Протокол итератора определяет стандартный способ создания последовательности значений, конечной или бесконечной.
  • В js этот стандарт представляет собой конкретный метод next.

Следующий метод имеет следующие требования:

  • Функция без параметров или с одним параметром, возвращающая объект, который должен иметь следующие два свойства:
    • сделано (логическое)
    • value
      • Итератор возвращает любое значение JavaScript. done is true может быть опущен. Но обычно значение устанавливается равным undefined.

реализовать итератор

    function createArrayIterator(arr) {
      let index = 0
      return {
        next: function() {
          if (index < arr.length) {
            return { done: false, value: arr[index++] }
          } else {
            return { done: true, value: undefined } 
          }
        }
      }
    }
    
    // 测试
    const nums = [10, 22, 33, 12]
    const numsIterator = createArrayIterator(nums)
    console.log(numsIterator.next())
    console.log(numsIterator.next())
    console.log(numsIterator.next())
    console.log(numsIterator.next())
    console.log(numsIterator.next())

image.png

повторяемый объект

Но приведенный выше код в целом выглядит немного странно:

Когда мы получаем массив, нам нужно самим создать индексную переменную, а затем создать так называемый объект-итератор. На самом деле мы можем дополнительно инкапсулировать приведенный выше код и сделать его итерируемым объектом.

Что такое итерируемый объект?

  • Это отличается от итератора.
  • когда объект реализуетИтерируемый протокол(итерируемый протокол), это итерируемый объект.
  • Требование к этому объекту состоит в том, что он должен реализовывать метод @@iterator и чтобы этот метод возвращал итератор. В коде мы используем Symbol.iterator для доступа к этому свойству.

Давайте реализуем итерируемый объект

  • При реализации обратите внимание на указатель this.
    // 他需要具有[Symbol.iterator]方法,并且这个方法返回一个迭代器

    const obj = {
      names: ["zh", "llm"],
      [Symbol.iterator]() {
        let index = 0;
        return {
          next: () => {
            if(index < this.names.length) {
              return {
                done: false, value: this.names[index++]
              }
            }else {
              return {
                done: true, value: undefined
              }
            }
          }
        }
      }
    }

Что хорошего в том, что мы поворачиваем таким образом?

Когда объект становится итерируемым объектом, некоторые итерационные операции, такие как for...of, фактически вызывают его.@@iteratorметод.

    for(let item of obj) {
      console.log(item) // zh llm
    }

собственный объект итератора

Фактически, многие из нативных объектов, которые мы обычно создаем, реализуют итеративный протокол и будут генерировать объект итератора:

Строка, массив, карта, набор, объект аргументов, коллекция NodeList.

Итак, каковы приложения этих итерируемых объектов?

  • Грамматика в JavaScript: for ...of, синтаксис распространения, yield*, Destructuring_assignment.

Здесь следует отметить, что когда объекты используют синтаксис расширения и присваивания деструктуризации объекта, итераторы не используются. Поскольку в объекте не развернут метод Symbol.iterator. Это новый синтаксис es9.

  • При создании некоторых объектов: новая карта ([iterable]), новая WeakMap ([iterable]), новый набор ([iterable]), новый WeakSet ([iterable]).
  • Некоторые вызовы методов: Promise.all(iterable), Promise.race(iterable), Array.from(iterable).

Итерации пользовательского класса

Ранее мы видели, что Array, Set, String, Map и другие типы объектов создаются из итерируемого.

В объектно-ориентированной разработке мы можем определить наш собственный класс через класс, и этот класс может создавать множество объектов. Если мы также хотим, чтобы объекты, созданные нашим классом, были итерируемыми по умолчанию, мы можем добавить метод @@iterator при проектировании класса.

Фактически, добавьте к прототипу метод Symbol.iterator.

    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 }
          }
        }
      }
    }

В некоторых случаях итераторы могут сломаться без полной итерации:

  • Например, в процессе обхода операция цикла прерывается прерыванием, продолжением, возвратом и броском.
  • Например, при деструктуризации разрушаются не все значения

Если мы хотим знать, что итератор прерван, мы можем определить метод возврата в объекте итератора. для обозначения прерывания.

    for (const stu of classroom) {
      // 通过break中断
      if (stu === "zh") break
    }

Generator

что такое генератор

Builder - это ES6 с новой функцией управления, используйте программу, она может сделать нас более гибкими функциями управления, а когда продолжить, приостановить выполнение.

Мы обычно пишем много функций, а условия для этих функций для завершения, как правило, являются возвращающими значениями или исключениями.

Генераторная функция тоже является функцией, но есть некоторые отличия от обычных функций:

  • Во-первых, функция генератора должна добавить символ после функции:*.
  • Во-вторых, функции-генераторы могут использовать ключевое слово yield для управления потоком выполнения функции.
  • Наконец, возвращаемое значение функции-генератора — Генератор.

Генератор на самом деле представляет собой особый вид итератора.

    function* foo() {
      console.log("函数开始执行~")

      const value1 = 100
      console.log("第一段代码:", value1)
      yield

      const value2 = 200
      console.log("第二段代码:", value2)
      yield

      const value3 = 300
      console.log("第三段代码:", value3)
      yield

      console.log("函数执行结束~")
    }

    // 调用生成器函数时, 会给我们返回一个生成器对象
    const generator = foo()

Мы обнаружили, что тело выполнения функции генератора foo выше вообще не выполняется, оно просто возвращает объект генератора.

Итак, как мы можем заставить его выполнять то, что находится в функции?

Просто позвони дальше. Когда мы узнали об итераторах раньше, мы знали, что следующий итератор будет иметь возвращаемое значение. Но мы часто не хотим, чтобы next возвращал undefined, в настоящее время мы можем вернуть результат через yield.

Выполнение функции генератора

Мы обнаружили, что тело выполнения функции генератора foo выше вообще не выполняется, оно просто возвращает объект генератора.

Итак, как мы можем заставить его выполнять то, что находится в функции?

Просто позвони дальше.

Когда мы узнали об итераторах раньше, мы знали, что следующий итератор будет иметь возвращаемое значение. Но мы часто не хотим, чтобы next возвращал undefined, в настоящее время мы можем вернуть результат через yield.И значение возврата будет использоваться как значение, сделанное в первый раз, истинно.

    function* foo() {
      console.log("函数开始执行~")

      const value1 = 100
      console.log("第一段代码:", value1)
      yield value1

      const value2 = 200
      console.log("第二段代码:", value2)
      yield value2

      const value3 = 300
      console.log("第三段代码:", value3)
      yield value3

      console.log("函数执行结束~")
      return "123"
    }

Параметры передачи генератора - следующая функция

Поскольку функцию можно приостановить для сегментированного выполнения, функция должна иметь возможность передавать параметры, так как же передавать параметры для каждого сегмента?

Когда мы вызываем следующую функцию, мы сможем передавать ему параметры, то этот параметр будет использоваться в качестве возвращаемого значения предыдущего оператора дохода. Поскольку вначале нет предыдущего дохода, когда в следующий раз параметры не могут быть переданы для первого куска кода в следующем. Но мы можем передавать параметры непосредственно к функции как параметры первого куска кода.

    // 伪代码
    接收参数 = yeild
    next(参数)

Примечание. То есть мы предоставляем значение для этого функционального кода выполнения блока.

Генератор завершается раньше - функция возврата

Другой способ передать аргументы функциям-генераторам — через функцию возврата:

После возврата значения функция генератора завершится, и последующий вызов не продолжит генерацию значений. По сути, он является функцией прямого возврата. И использовать переданный параметр в качестве возвращаемого значения.

function* foo(num) {
  console.log("函数开始执行~")

  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1
  
  // 调用return方法后相当于
  // return n;

  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2

  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3

  console.log("函数执行结束~")
  return "123"
}

const generator = foo(10)

console.log(generator.next())

// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log(generator.return(15))

image.pngСудя по приведенным выше результатам выполнения, после вызова метода возврата функция завершается напрямую. Затем используйте входящее значение в качестве значения объекта итератора и измените его на true.

Генератор выдает исключение - функция броска

В дополнение к параметрам, передаваемым во внутреннюю функцию генератора, могут быть внутренние функции генератора, которые выдают исключение:

  • После генерации исключения мы можем поймать исключение в функции-генераторе.
  • И выброшенное исключение — это фрагмент кода последнего yield.
  • Если исключение перехвачено с помощью try...catch, мы можем передать параметр для выбрасывания в качестве параметра перехвата. И последующий код может продолжать вызывать next для выполнения.
  • Если исключение не перехвачено, последующий код не будет продолжать вызывать следующий для выполнения.
    function* foo() {
      console.log("代码开始执行~")

      const value1 = 100
     //try {
        yield value1
     // } catch (error) {
     //  console.log("捕获到异常情况:", error)
     //}

      console.log("第二段代码继续执行")
      const value2 = 200
      yield value2

      console.log("代码执行结束~")
    }

    const generator = foo()

    const result = generator.next()
    generator.throw("error message")
    generator.next()

image.png

image.png

Генераторы вместо итераторов

Реализация предыдущих итераторов

    function createArrayIterator(arr) {
      let index = 0
      return {
        next: function() {
          if (index < arr.length) {
            return { done: false, value: arr[index++] }
          } else {
            return { done: true, value: undefined } 
          }
        }
      }
    }

Мы знаем, что объект iTerator будет возвращен, когда вызывается функция генератора. Таким образом, мы можем использовать генератор вместо итераторов.

  • Способ 1: извлекать входящие данные напрямую, а затем возвращать их.
  • Способ 2: По сути, это то же самое, что и способ 1.
  • Способ 3: Использованиеyield*для создания итерируемого объекта. В настоящее время он эквивалентен синтаксическому сахару для yield, за исключением того, что итерируемый объект будет повторяться по очереди, по одному значению за раз.
// 1.生成器来替代迭代器
function* createArrayIterator(arr) {

  // 3.第三种写法 yield*
  // yield* arr

  // 2.第二种写法
  // for (const item of arr) {
  //   yield item
  // }
  
  // 1.第一种写法
  for(let i = 0; i < arr.length; i++) {
    yield arr[i];
  }
}

const names = ["zh", "llm", "zhllm"]
const namesIterator = createArrayIterator(names)

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

Пользовательская итерация класса — реализация генератора

По сути, это использование генераторов вместо итераторов. Это знание, упомянутое выше.

    class Classroom {
      constructor(address, name, students) {
        this.address = address
        this.name = name
        this.students = students
      }

      // [Symbol.iterator] = function*() {
      //   yield* this.students
      // }

      *[Symbol.iterator]() {
        yield* this.students
      }
    }

План асинхронной обработки

Давайте рассмотрим этот случай и посмотрим, как с ним бороться.

Требование: когда запрос завершен, получите данные, а затем сделайте запрос. Это может быть запрошено много раз.

    function requestData(url) {
      return new Promise((resolve, reject) => {
        // 模拟网络请求
        setTimeout(() => {
          // 拿到请求的结果
          resolve(url)
        }, 2000);
      })
    }
  • Вариант 1. Вызовите функцию requestData несколько раз.
    // 1.第一种方案: 多次回调
    // 回调地狱
    requestData("zh").then(res => {
      requestData(res + "aaa").then(res => {
        requestData(res + "bbb").then(res => {
          console.log(res) //等待六秒,打印结果 zhaaabbb
        })
      })
    })
  • Вариант 2. Вызовите функцию then несколько раз, потому что состояние then определяется возвращенным промисом.
    // 2.第二种方案: Promise中then的返回值来解决
    requestData("zh").then(res => {
      return requestData(res + "aaa")
    }).then(res => {
      return requestData(res + "bbb")
    }).then(res => {
      console.log(res) //等待六秒,打印结果 zhaaabbb
    })
  • Вариант три: + генератор реализован путем обещания. Вручную вызовут генератор функции из реализации объекта итератора.
    // 3.第三种方案: Promise + generator实现
    function* getData() {
      const res1 = yield requestData("zh")
      const res2 = yield requestData(res1 + "aaa")
      const res3 = yield requestData(res2 + "bbb")
      const res4 = yield requestData(res3 + "ccc")
    }
    
    // 1> 手动执行生成器函数
    const generator = getData()
    generator.next().value.then(res => {
      console.log("res----1",res) // zh
      generator.next(res).value.then(res => {
        console.log("res----2",res) // zhaaa
        generator.next(res).value.then(res => {
          console.log("res----3",res) // zhaaabbb
          generator.next(res).value.then(res => {
          console.log("res----4",res) // zhaaabbbccc
          })
        })
      })
    })
  • Поскольку приведенный выше код вызывается вручную, инкапсулируйте функцию, которая автоматически вызывает объект итератора.
    function* getData() {
      const res1 = yield requestData("zh")
      const res2 = yield requestData(res1 + "aaa")
      const res3 = yield requestData(res2 + "bbb")
      const res4 = yield requestData(res3 + "ccc") // 这里返回的依旧是done: false
      return res4 // 这里最后返回 {done: true, value: res4}
    }
    
    // 封装了一个自动执行的函数
    function execGenerator(genFn) {
      const generator = genFn()
      function exec(res) {
        const result = generator.next(res)
        if (result.done) {
            console.log(result.value) // zhaaabbbccc
          return result.value
        }
        result.value.then(res => {
          exec(res)
        })
      }
      exec()
    }
    
    execGenerator(getData)
  • Сторонние пакеты выполняются автоматически
    function* getData() {
      const res1 = yield requestData("zh")
      const res2 = yield requestData(res1 + "aaa")
      const res3 = yield requestData(res2 + "bbb")
      const res4 = yield requestData(res3 + "ccc")
    }
    const co = require('co')
    co(getData)
  • Используйте асинхронный режим ожидания, представленный в es8.
    async function getData() {
      const res1 = await requestData("zh")
      const res2 = await requestData(res1 + "aaa")
      const res3 = await requestData(res2 + "bbb")
      const res4 = await requestData(res3 + "ccc")
      console.log(res4)
    }

    getData()

В следующей статье будет представлена ​​асинхронность, ожидание.

xdm, если вы найдете это полезным, пожалуйста, лайкните его больше, не всегда нравится.