Или как классы реализованы в терминах прототипов

Это часть серии статей о ключевом слове new в различных языках программирования.



Ключевое слово« новое на слишком большом количестве языков
Новое - это отличное место, на которое можно обратить внимание при изучении нового языка, чтобы лучше понять, как будет действовать ваш код. и… medium.com »



JavaScript - замечательный язык общего программирования. Он предоставляет множество функций, которые позволяют писать императивный, функциональный и объектно-ориентированный код. Однако объектно-ориентированные функции JS реализованы совершенно иначе, чем другие языки, такие как Java и C ++, что с годами привело к путанице. Эта путаница также привела к появлению множества ошибок и недоразумений относительно легитимности JS как языка.

Ключевой момент, с которого следует начать, заключается в том, что объектно-ориентированный не означает «классы и наследование» - это просто обычный способ достижения этого. Таким образом, Javascript не имеет классов, независимо от того, как выглядит ES6. В Javascript есть прототипы и синтаксис прототипа, подобный классу.

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

Если вы переходите с C ++, Java, C # или любого другого языка, в котором есть ключевое слово new, имейте в виду, что каждый язык обрабатывает new по-своему - и предварительное знание другого языка может фактически запутать то, что происходит в JavaScript!

С первого взгляда 👓

  • Если вам нужен объектно-ориентированный код, используйте ключевое слово Class
  • Синтаксис класса Javascript фактически скрывает прототипное наследование.
  • Функции - это секретные объекты
  • Прототипы экономят нам память
  • Ключевое слово new заставляет вызов функции действовать как конструктор.

Следуйте коду

Следуйте по ссылке https://codesandbox.io/s/javascript-new-keyword-animated-bouncing-balls-nu43i.

Настройка сцены 💺

Наш начальник местного специализированного магазина бытовой техники «Shovels and Chairs» попросил нас создать веб-симуляцию прыгающего мяча. Это для рекламной кампании, и нам нужно произвести впечатление на потенциальных клиентов. Таким образом, у нас есть требование, чтобы мы могли отображать на экране 1000 прыгающих мячей одновременно, поэтому нам нужно действовать в некоторой степени эффективнее.

Это круто. Мы можем представить это в памяти как таковое:

Однако здесь всего один мяч, а мы хотим сделать их много.

Давайте выделим makeBall строительную функцию, которая даст нам новый объект Ball. Мы также добавим функцию update, чтобы мы могли вызывать ball.update(delta), а не updateBall(ball, delta).

(Это означает, что в хорошем инкапсулированном стиле ООП мы возимся только с участниками ball в частном порядке, а не публично.)

Внутри makeBall мы создаем объект ball и после создания добавляем к нему новую функцию update.

Как это выглядит?

Функции на самом деле являются объектами 🐬🐳

Так же, как все дельфины на самом деле киты, здесь стоит отметить, что все функции на самом деле являются объектами. Это имеет множество последствий, большинство из которых нас сегодня не интересует, но это означает, что каждый раз, когда вы пишете function или () => { }, вы создаете новый объект с кодом функции в нем. Это отличается от C ++ и Java, где код для каждой функции существует только один раз в скомпилированной сборке.

Это означает, что если бы мы создали 3 шара, наша структура памяти выглядела бы так:

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

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

Давайте внесем это и новое

Мы собираемся переписать код, чтобы использовать this и new - обратите внимание, что мы не объявляем явно this нигде. Ключевое слово new предоставит нам это.

По сути, мы заменяем любую ссылку на ball словом this.

В этом новом фрагменте мы переименовали функцию makeBall в Ball, заменили ссылки на ball на this внутри нее, а в строке 24 добавили ключевое слово new перед вызовом функции.

На данный момент мы еще не объявили переменную this, а это значит, что вызов этой функции сейчас вызовет ошибку (если мы не вернемся к строке 4!). Мы увидим, где эта переменная устанавливается позже.

Прямо сейчас в этом нет очевидных преимуществ. Он практически эквивалентен предыдущему коду.

Итак, почему мы должны были внести это изменение? Какую новую силу это дало нам?

Введите прототип 🤸‍♂️

В C ++, Java, C # и т. Д. Все функции существуют один раз в скомпилированном коде - что, если мы попытаемся сделать то же самое в JS?

В JS все объекты имеют внутреннее скрытое значение prototype, которое указывает на другой объект. Да, все объекты. Даже пустой объект вроде {}.

Вы можете увидеть это в некоторых браузерах, таких как Chrome, набрав Object.getPrototypeOf({}) в консоли.

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

Мы извлекли функцию update и переместили ее на прототип.

Но что такое прототип и почему он хоть как-то помогает?

Прежде чем я объясню, что делает ключевое слово new и как оно соотносится с прототипами, давайте посмотрим, какова сейчас структура памяти для нашего массива balls:

Теперь есть только один объект функции для update. Мы могли бы вызвать new Ball(100) миллион раз, и все равно остался бы только один экземпляр объекта функции обновления. Каждый из этих экземпляров будет указывать на одну и ту же Ball.prototype, которая указывает на одну update функцию.

Так что здесь происходит? Как был установлен prototype?

Что на самом деле делает новое ключевое слово 🔮

Из описания MDN ключевое слово new:

  1. Создает пустой объект JavaScript (например, {})
  2. Заменяет прототип этого объекта на прототип новой функции - помните, что функции являются объектами, и все объекты уже имеют прототип.
  3. Передает вновь созданный объект из шага 1 как переменную this. (Это происходит за кадром.)
  4. Возвращает this, если функция не возвращает собственный объект.

Вы можете смоделировать это внутри JavaScript (хотя, пожалуйста, нигде не используйте этот код!)

Странное предупреждение со стрелочными функциями

Вы могли заметить, что я пока использовал только function () { } в этом посте, а не () => {}. В большинстве случаев стрелочные функции аналогичны обычным функциям, однако есть кое-что, что стоит отметить.

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

Это может препятствовать использованию непосредственного создания конструкторов функций и предпочитать использование ключевого слова class.

Так что насчет классов? 🏫

Современный JavaScript (ES6 или такие языки, как Typescript) предоставляет ключевое слово class, чтобы упростить нам этот процесс.

Давайте перепишем Ball, используя синтаксис класса.

Это современный рекомендуемый способ написания класса Ball. Хотя я бы обычно не рекомендовал вам использовать JavaScript в тяжелом классе, но если вы собираетесь это сделать, то можете следовать лучшим практикам.

Если вы используете современный веб-браузер или версию Node (начиная с 4.3!), Ключевое слово class понимается и поддерживается напрямую. Теоретически, следуя намеченной семантике, он может также создавать более оптимизированный код под капотом (хотя я еще не видел никаких доказательств того, что это можно использовать в своих интересах).

Почему ключевое слово class лучше, чем написать его самому?

Точно так же лучше писать на C, чем писать прямой код на ассемблере. Более высокая абстракция ключевого слова class может скрыть некоторые беспорядочные биты и затруднить нам совершение ошибок. Помогает нам попасть в яму успеха.

Обратите внимание: если вы ориентируетесь на ES5 («обычный» JavaScript), вам нужно будет использовать что-то вроде Babel для компиляции нового кода. Если вы запустите этот пример через Babel, вы увидите, как он на самом деле генерирует код, аналогичный (но на первый взгляд совершенно отличный) от наших первых примеров.

Ключевое слово class все еще создает объект на основе прототипа.

Вы можете увидеть это здесь.



В заключение ✔️

Наша маркетинговая кампания увенчалась успехом! Клиенты любят смотреть, как тысячи мячей отскакивают от веб-страницы, не тратя лишнюю оперативную память.

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

Чтобы на самом деле заключить

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

Если вы переходите на JavaScript с другого языка, не забудьте написать идиоматический код JavaScript - не пытайтесь впихнуть в него такие концепции C ++, как цепочки наследования, потому что это то, к чему вы привыкли.

дальнейшее чтение

Прочтите следующие три статьи для более глубокого понимания объектов и прототипов JavaScript.