One place for hosting & domains

      Понимание

      Понимание генераторов в JavaScript


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      Введение

      В ECMAScript 2015 были введены генераторы для языка JavaScript. Генератор — это процесс, который может быть остановлен и возобновлен, и может выдать несколько значений. Генаратор в JavaScript состоит из функции генераторов, которая возвращает элемент Generator, поддерживающий итерации.

      Генераторы могут поддерживать состояние и обеспечивать эффективный способ создания итераторов, а также позволяют работать с бесконечным потоком данных, который можно использовать для установки бесконечной прокрутки на внешнем интерфейсе веб-приложений, для работы с данными звуковой волны и т. д. Кроме того, при использовании Promises генераторы могут имитировать функцию async/await, которая позволяет работать с асинхронным кодом более простым и читаемым способом. Хотя async/await является более распространенным способом работы с асинхронными вариантами использования, например извлечения данных из API, генераторы обладают более усовершенствованными функциями, что абсолютно оправдывает изучение методов их использования.

      В этой статье мы расскажем, как создавать функции-генераторы, выполнять итеративный обход объектов Generator, объясним разницу между yield и return внутри генератора, а также коснемся других аспектов работы с генераторами.

      Функции-генераторы

      Функция-генератор — это функция, которая возвращает объект генератора и определяется по ключевому слову функции, за которым следует звездочка (*), как показано ниже:

      // Generator function declaration
      function* generatorFunction() {}
      

      Иногда звездочка отображается рядом с названием функции напротив ключевого слова, например function *generatorFunction(). Это работает так же, но функция со звездочкой function* является более распространенной синтаксической конструкцией.

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

      // Generator function expression
      const generatorFunction = function*() {}
      

      Генераторы могут даже быть методами объекта или класса:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      В примерах, приведенных в данной статье, будет использоваться синтаксическая конструкция объявления функции генератора.

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

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

      Объекты генератора

      Обычно функции в JavaScript выполняются до завершения, и вызов функции вернет значение, когда она дойдет до ключевого слова return. Если пропущено ключевое слово ​​​return, функция вернет значение undefined.

      Например, в следующем коде мы декларируем функцию sum(), которая возвращает значение, состоящее из суммы двух целых аргументов:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

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

      const value = sum(5, 6) // 11
      

      Однако функция генератора не возвращает значение сразу, а вместо этого возвращает элемент Generator, поддерживающий итерации. В следующем примере мы декларируем функцию и придаем ей одно возвращаемое значение, как у стандартной функции:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Активация функции генератора возвращает элемент Generator, который мы можем отнести к переменной:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Если бы это была штатная функция, мы бы могли ожидать, что генератор даст нам строку, переданную в функцию. Однако фактически мы получаем элемент в приостановленном состоянии. Таким образом, вызов генератора даст результат, аналогичный следующему:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      Элемент Generator, возвращаемый функцией — это итератор. Итератор — это объект, имеющий метод ​​​​​​next()​​​, который используется для итерации последовательности значений. Метод next() возвращает элемент со свойствами value и done. value означает возвращаемое значение, а done указывает, прошел ли итератор все свои значения или нет.

      Зная это, давайте вызовем функцию next() нашего генератора и получим текущее значение и состояние итератора:

      // Call the next method on the Generator object
      generator.next()
      

      Результат будет выглядеть следующим образом:

      Output

      {value: "Hello, Generator!", done: true}

      Вызов next() возвращает значение Hello, Generator!, а состояние done имеет значение true, так как это значение произошло из return, что закрыло итератор. Поскольку итератор выполнен, статус функции генератора будет изменен с suspended на closed. Повторный вызов генератора даст следующее:

      Output

      generatorFunction {<closed>}

      На данный момент мы лишь продемонстрировали, как с помощью функции генератора более сложным способом можно получить значение функции return. Однако функции генератора также имеют уникальные свойства, которые отличают их от обычных функций. В следующем разделе мы узнаем об операторе yield и о том, как генератор может приостановить или возобновить выполнение.

      Операторы yield

      Генераторы вводят новое ключевое слово в JavaScript: yield. yield может приостановить функцию генератора и вернуть значение, которое следует за yield, тем самым обеспечивая более простой способ итерации значений.

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

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Сейчас, когда мы вызываем next()​​​​​ в функции генератора, она будет останавливаться каждый раз, когда будет встречать yield. done будет устанавливаться для false​​​ после каждого yield, указывая на то, что генератор не завершен. Когда она встретит return или в функции больше не будет yield, done переключится на true, и генератор будет завершен.

      Используйте метод next() четыре раза в строке:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      В результате будут выведены следующие четыре строки по порядку:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Обратите внимание, что для генератора не требуется return. В случае пропуска последняя итерация вернет {value: undefined, done: true}​​​, по мере наличия последующих вызовов next() после завершения генератора.

      Итерация по генератору

      С помощью метода next() мы вручную выполнили итерацию объекта Generator​​​, получив все свойства value​​​ и done всего объекта. Однако, как и Array,Map и Set, Generator следует протоколу итерации и может быть итерирован с for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      В результате будет получено следующее:

      Output

      Neo Morpheus Trinity

      Оператор расширения также может быть использован для присвоения значений Generator​​​ для массива.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Это даст следующий массив:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Как расширение, так и for...of​​​ не разложит return на значения (в этом случае было бы «The Oracle»).

      Примечание. Хотя оба эти метода эффективны для работы с конечными генераторами, если генератор работает с бесконечным потоком данных, невозможно будет использовать расширение или for...of​​​ напрямую без создания бесконечного цикла.

      Завершение работы генератора

      Как мы увидели, генератор может настроить свое свойство done​​​ на true, а статус на closed путем итерации всех своих значений. Немедленно отменить действие генератора можно еще двумя способами: с помощью метода return() и метода throw().

      С помощью return()​​ генератор можно остановить на любом этапе так, как будто выражение return было в теле функции. Вы можете передать аргумент в return() или оставить его пустым для неопределенного значения.

      Чтобы продемонстрировать return(), мы создадим генератор с несколькими значениями yield, но без return в определении функции:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      Первый next() даст нам «Neo» c done установленным на false​​​. Если мы обратимся к методу return()​​​ на объекте Generator сразу после этого, мы получим переданное значение, и done будет установлено на true. Все дополнительные вызовы next() дадут завершенный ответ генератора по умолчанию с неопределенным значением.

      Чтобы продемонстрировать это, запустите следующие три метода на генераторе:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Будет получено три следующих результата:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      Метод return() заставил объект Generator завершить работу и проигнорировать все другие ключевые слова yield. Это особенно полезно в асинхронном программировании, когда необходимо, чтобы была возможность отмены для функции, например в случае прерывания веб-запроса, когда пользователь хочет выполнить другое действие, так как невозможно напрямую отменить Promise.

      Если тело функции генератора может перехватывать ошибки и работать с ними, можно использовать метод throw() для перебрасывания ошибки в генератор. Это действие запустит генератор, перебросит в него ошибку и прекратит работу генератора.

      Чтобы продемонстрировать это, мы поместим try...catch​​​ в тело функции генератора и зарегистрируем ошибку при ее наличии:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Теперь мы запустим метод next()​​, за которым последует throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Результат будет выглядеть следующим образом:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      С помощью throw(), мы ввели ошибку в генератор, которая была перехвачена try...catch и зарегистрирована в консоли.

      Методы и состояния объекта генератора

      В следующей таблице представлен перечень методов, которые можно использовать на объектах Generator:

      МетодОписание
      next()Возвращает следующее значение генератора
      return()Возвращает значение генератора и прекращает работу генератора
      throw()Выдает ошибку и прекращает работу генератора

      В следующей таблице перечислены возможные состояния объекта Generator:

      СостояниеОписание
      suspendedГенератор остановил выполнение, но не прекратил работу
      closedГенератор прекратил выполнение из-за обнаружения ошибки, возвращения или итерации всех значений

      yield делегирование

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

      Для демонстрации мы можем создать две функции генератора, одна из которых будет yield* оператором для другой:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      Далее, давайте проведем итерацию посредством функции begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Это даст следующие значения в порядке их генерирования:

      Output

      1 2 3 4

      Внешний генератор выдал значения 1 и 2, затем делегировал другому генератору с yield*, который вернул 3 и 4.

      yield* также может делегировать любому итерируемому объекту, например Array или Map. Yield делегирование может быть полезным для организации кода, поскольку любая функция в рамках генератора, использующая yield, также должна быть генератором.

      Бесконечный поток данных

      Один из полезных аспектов генератора — способность работать с бесконечными потоками и коллекциями данных. Это можно увидеть на примере бесконечного цикла внутри функции генератора, который увеличивает число на 1.

      В следующем коде мы определяем функцию генератора и затем запускаем генератор:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Затем проводим итерацию значений с использованием next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Результат будет выглядеть следующим образом:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      Функция возвращает последовательные значения в бесконечном цикле, в то время как свойство done остается false, обеспечивая незавершенность.

      При использовании генераторов вам не нужно беспокоиться о создании бесконечного цикла, так как вы можете останавливать и возобновлять выполнение по своему усмотрению. Однако, вы все-таки должны быть осторожны с тем, как вы активируете генератор. Если вы используете оператор расширения или for...of для бесконечного потока данных, вы одновременно будете проводить итерацию бесконечного цикла, что приведет к отказу среды.

      Для более сложного примера бесконечного потока данных мы можем создать функцию генератора Fibonacci. Последовательность Фибоначчи, которая непрерывно складывает два предыдущих значения вместе, может быть записана с использованием бесконечного цикла в рамках генератора следующим образом:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Для тестирования мы можем создать цикл конечного числа и напечатать последовательность Фибоначчи в консоль.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      В результате вы получите следующий вывод:

      Output

      0 1 1 2 3 5 8 13 21 34

      Способность работать с бесконечными наборами данных — это одно из свойств, благодаря которым генераторы являются таким мощным инструментом. Эта способность может использоваться, например для установки бесконечной прокрутки на внешнем интерфейсе веб-приложений.

      Передача значений в генераторы

      В этой статье мы описывали использование генераторов в качестве итераторов и вырабатывали значения в каждой итерации. Помимо производства значений генераторы могут также потреблять значения от next(). В этом случае yield будет содержать значение.

      Важно отметить, что первый вызванный next() не будет передавать значение, а только запустит генератор. Для демонстрации этого мы можем записать значение yield и вызывать next() несколько раз с некоторыми значениями.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Результат будет выглядеть следующим образом:

      Output

      100 200 {value: "The end", done: true}

      Также возможно создать генератор с первоначальным значением. В следующем примере мы создадим цикл for и передадим каждое значение в метод next(), но также передадим аргумент в первоначальную функцию:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Мы извлечем значение из next() и создадим новое значение в следующей итерации, которое является предыдущим значением,умноженным на десять. В результате вы получите следующий вывод:

      Output

      0 10 20 30 40

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

      async/await в генераторах

      Асинхронная функция — вид функции, имеющийся в ES6+ JavaScript, которая облегчает работу с асинхронными данными, делая их синхронными. Генераторы обладают более широким спектром возможностей, чем асинхронные функции, но способны воспроизводить аналогичное поведение. Реализация асинхронного программирования таким образом может повысить гибкость вашего кода.

      В этом разделе мы продемонстрируем пример воспроизведения async/await с генераторами.

      Давайте создадим асинхронную функцию, которая использует Fetch API для получения данных из JSONPlaceholder API (дает пример данных JSON для тестирования) и регистрирует ответ в консоли.

      Для начала определим асинхронную функцию под названием getUsers, которая получает данные из API и возвращает массив объектов, затем вызовем getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Это даст данные JSON, аналогичные следующим:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      С помощью генераторов мы можем создать нечто почти идентичное, что не использует ключевые слова async/await. Вместо этого будет использоваться новая созданная нами функция и значения yield вместо промисов await.

      В следующем блоке кода мы определим функцию под названием getUsers, которая использует нашу новую функцию asyncAlt (будет описана позже) для имитации async/await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Как мы видим, она выглядит почти идентично реализации async/await, за исключением того, что имеется функция генератора, которая передается в этих значениях функции yield.

      Теперь мы можем создать функцию asyncAlt, которая напоминает асинхронную функцию. asyncAlt​​​ имеет функцию генератора в качестве параметра и является нашей функцией, вырабатывающей промисы, которые получают возвраты. asyncAlt​​​ возвращает непосредственно функцию и решает каждый найденный промис до последнего:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Это даст тот же результат, что и в версии async/await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Обратите внимание, эта реализация предназначена для демонстрации того, как можно использовать генераторы вместо async/await, и не является готовой для эксплуатации конструкцией. В ней отсутствуют настройки обработки ошибок и нет возможности передавать параметры в выработанные значения. Хотя этот метод может сделать ваш код более гибким, async/await зачастую является более оптимальным вариантом, так как способен абстрагировать детали реализации и позволяет сконцентрироваться на написании продуктивного кода.

      Заключение

      Генераторы — это процессы, которые могут останавливать и возобновлять выполнение. Они являются мощной, универсальной, хотя и не слишком распространенной функцией JavaScript. В данном учебном пособии мы узнали о функциях и объектах генератора, методах, доступных для генераторов, операторах yield и yield*, а также генераторах, используемых с конечными и бесконечными массивами данных. Мы также изучили один способ реализации асинхронного кода без вложенных обратных вызовов или длинных цепочек промисов.

      Если вы хотите узнать больше о синтаксисе JavaScript, ознакомьтесь с учебными пособиями Понимание методов This, Bind, Call и Apply в JavaScript​​​ и Понимание объектов Map и Set в JavaScript.



      Source link

      Понимание понятия классов в JavaScript


      Введение

      JavaScript — это язык на базе прототипов, и каждый объект JavaScript имеет скрытое внутреннее свойство [[Prototype]], которое можно использовать для расширения свойств и методов объекта. Вы можете узнать больше о прототипах из нашего обучающего модуля Понимание принципов прототипов и наследования в JavaScript.

      До недавнего времени промышленные разработчики использовали функции конструктора для имитации объектно-ориентированного шаблона в JavaScript. Языковая спецификация ECMAScript 2015 (часто называемая ES6) ввела в язык JavaScript понятие классов. Классы в JavaScript не добавляют дополнительные функции, и представляют собой способ упростить синтаксис при использовании прототипов и наследования и сделать его более элегантным. Поскольку в других языках программирования также используются классы, синтаксис классов в JavaScript упрощает работу для разработчиков, владеющих другими языками.

      Классы — это функции

      Класс JavaScript — это вид функции. Для декларирования классов используется ключевое слово class. Мы используем синтаксис выражения функции для инициализации функции и синтаксис выражения класса для инициализации класса.

      // Initializing a function with a function expression
      const x = function() {}
      
      // Initializing a class with a class expression
      const y = class {}
      

      Мы можем получить доступ к [[Prototype]] объекта с помощью метода Object.getPrototypeOf(). Давайте протестируем созданную нами пустую функцию.

      Object.getPrototypeOf(x);
      

      Output

      ƒ () { [native code] }

      Также мы можем использовать этот метод для только что созданного нами класса.

      Object.getPrototypeOf(y);
      

      Output

      ƒ () { [native code] }

      Программный код, декларированный с помощью function и class, возвращает функцию [[Prototype]]. При использовании прототипов любую функцию можно превратить в экземпляр конструктора с помощью ключевого слова new.

      const x = function() {}
      
      // Initialize a constructor from a function
      const constructorFromFunction = new x();
      
      console.log(constructorFromFunction);
      

      Output

      x {} constructor: ƒ ()

      Это также относится и к классам.

      const y = class {}
      
      // Initialize a constructor from a class
      const constructorFromClass = new y();
      
      console.log(constructorFromClass);
      

      Output

      y {} constructor: class

      Эти примеры конструктора прототипов пустые, но вы видите, как оба метода позволяют добиться одинакового результата вне зависимости от синтаксиса.

      Определение класса

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

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

      constructor.js

      // Initializing a constructor function
      function Hero(name, level) {
          this.name = name;
          this.level = level;
      }
      

      При трансляции в синтаксис класса, как показано ниже, структура будет очень похожей.

      class.js

      // Initializing a class definition
      class Hero {
          constructor(name, level) {
              this.name = name;
              this.level = level;
          }
      }
      

      Мы знаем, что функция конструктора является планом объекта благодаря тому, что первая буква инициализатора (необязательно) заглавная, и потому что мы знакомы с синтаксисом. Ключевое слово class показывает назначение нашей функции более прямо.

      Единственная разница в синтаксисе инициализации заключается в использовании ключевого слова class вместо function, и в том, что свойства назначаются внутри метода constructor().

      Определение методов

      При использовании функций конструктора методы обычно назначаются непосредственно в prototype вместо инициализации, как показано ниже на примере метода greet().

      constructor.js

      function Hero(name, level) {
          this.name = name;
          this.level = level;
      }
      
      // Adding a method to the constructor
      Hero.prototype.greet = function() {
          return `${this.name} says hello.`;
      }
      

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

      class.js

      class Hero {
          constructor(name, level) {
              this.name = name;
              this.level = level;
          }
      
          // Adding a method to the constructor
          greet() {
              return `${this.name} says hello.`;
          }
      }
      

      Давайте посмотрим на эти свойства и методы в действии. Мы создадим новый экземпляр Hero, используя ключевое слово new, и присвоим некоторые значения.

      const hero1 = new Hero('Varg', 1);
      

      Если мы распечатаем дополнительную информацию о нашем новом объекте с помощью команды console.log(hero1), мы более подробно увидим. что происходит при инициализации класса.

      Output

      Hero {name: "Varg", level: 1} __proto__: ▶ constructor: class Hero ▶ greet: ƒ greet()

      В результатах мы видим, что функции constructor() и greet() functions были применены к прототипу __proto__ или [[Prototype]] объекта hero1, а непосредственно к объекту hero1 как к методу. Хотя при создании функций конструктора это очевидно, при создании классов дело обстоит по другому. Классы позволяют использовать более простой и сжатый синтаксис, но при этом немного теряется понятность процесса.

      Расширение класса

      Функции конструктора и классы можно расширять на новые планы объекта на основе родительского экземпляра. Это позволяет не повторять код для похожих объектов, для которых нужно просто добавить дополнительные или более детальные характеристики.

      Новые функции конструктора можно создавать на основе родительского экземпляра с помощью метода call(). В примере ниже мы создадим более конкретный класс персонажа Mage и присвоим ему свойства Hero с помощью метода call(), а также добавим дополнительное свойство.

      constructor.js

      // Creating a new constructor from the parent
      function Mage(name, level, spell) {
          // Chain constructor with call
          Hero.call(this, name, level);
      
          this.spell = spell;
      }
      

      Сейчас мы можем создать новый экземпляр Mage, используя те же свойства, что и Hero ,а также добавленное свойство.

      const hero2 = new Mage('Lejon', 2, 'Magic Missile');
      

      Отправив на консоль команду hero2, мы увидим, что создали новый экземпляр Mage на базе конструктора.

      Output

      Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: ▶ constructor: ƒ Mage(name, level, spell)

      Для классов ES6 ключевое слово super используется вместо call для доступа к родительским функциям. Мы будем использовать extends для обозначения родительского класса.

      class.js

      // Creating a new class from the parent
      class Mage extends Hero {
          constructor(name, level, spell) {
              // Chain constructor with super
              super(name, level);
      
              // Add a new property
              this.spell = spell;
          }
      }
      

      Теперь мы можем точно так же создать новый экземпляр Mage.

      const hero2 = new Mage('Lejon', 2, 'Magic Missile');
      

      Распечатаем hero2 на консоли и посмотрим результат.

      Output

      Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: Hero ▶ constructor: class Mage

      Результат практически такой же, но в конструкции класса прототип [[Prototype]] связан с родительским объектом, в данном случае Hero.

      Ниже приводится полное сравнение процесса инициализации, добавления методов и наследования между функцией конструктора и классом.

      constructor.js

      function Hero(name, level) {
          this.name = name;
          this.level = level;
      }
      
      // Adding a method to the constructor
      Hero.prototype.greet = function() {
          return `${this.name} says hello.`;
      }
      
      // Creating a new constructor from the parent
      function Mage(name, level, spell) {
          // Chain constructor with call
          Hero.call(this, name, level);
      
          this.spell = spell;
      }
      

      class.js

      // Initializing a class
      class Hero {
          constructor(name, level) {
              this.name = name;
              this.level = level;
          }
      
          // Adding a method to the constructor
          greet() {
              return `${this.name} says hello.`;
          }
      }
      
      // Creating a new class from the parent
      class Mage extends Hero {
          constructor(name, level, spell) {
              // Chain constructor with super
              super(name, level);
      
              // Add a new property
              this.spell = spell;
          }
      }
      

      Хотя синтаксис различается, конечный результат для обоих методов практически одинаковый. Классы дают более краткий способ создания шаблонов объектов, а функции конструктора более точно описывают конкретную механику.

      Заключение

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

      Понимание принципов наследования прототипов очень важно, если вы хотите стать эффективным разработчиком на JavaScript. Знакомство с классами очень полезно, потому что популярные библиотеки JavaScript, такие как React, часто используют синтаксис class.



      Source link