Что нового будет в Rails 1.1 и немного секретов Prototype.js


Оглавление (нажмите, чтобы открыть):

Информационный портал по безопасности

Что нового в Rails 5.1

Автор: admin от 1-05-2020, 01:50, посмотрело: 256

Rails 5.1: любимый javascript, системные тесты, зашифрованные секреты и многое другое

В рамках празднования 12-го RailsConf в Фениксе, штат Аризона на этой неделе, мы с гордостью сообщаем, что Rails 5.1 готов в его окончательной форме! Мы сделали более 4 100 коммитов с релиза Rails 5.0 делая его все ЛЕГЧЕ, ПРОЩЕ, и, ухх, ВЕСЕЛЕЕ? (Это шутка RailsConf).

Катушка подсветки действительно не изменилась с первой беты, но вот повтор:

Любимый javascript

На протяжении многих лет у нас были бурные, возможно, даже спорные отношения с javascript. Но это время прошло. javascript сильно улучшился за последние несколько лет, особенно с появлением ES6, а также с инструментами для компоновки и компиляции, такими как Yarn и webpack. Rails охватывает оба этих решения с распростертыми объятиями и пропускает любой прошлый поток воды под мостом.

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

Улучшения в Rails 5.1 сосредоточены на трех основных частях:

Управлять зависимостями javascript от NPM с помощью Yarn. Подумайте о Yarn , как о Bundler для javascript (в нее даже вовлечен Yehuda Katz !). Это позволяет легко управлять зависимостями от библиотек, таких как React или что-либо еще от NPM. Все, что вам нужно, через Yarn становится доступным для использования в asset pipeline, как и ресурсов, принадлежащих сторонним субъектам из директории vendor/assets . Просто используйте binstub bin/yarn для добавления зависимостей.

По желанию компилируйте javascript с помощью webpack. Несмотря на то, что для javascript имеется миллион различных модулей/компиляторов модулей, webpack быстро стал превосходным выбор. Мы упростили использование webpack с Rails с помощью нового гема Webpacker , который вы можете настроить автоматически в новых проектах с помощью —webpack (или даже —webpack=react , —webpack=angular , или —webpack=vue для индивидуальной конфигурации). Это полностью совместимо с asset pipeline, который вы можете продолжать использовать для изображений, шрифтов, звуков и т.п. У вас даже может быть некоторый javascript в asset pipeline, и некоторые сделанный через webpack. Это все управляется через Yarn, который включен по умолчанию.

  • Отбросьте jQuery как зависимость по умолчанию. Раньше мы подключали jQuery, чтобы предоставить такие функции, как data-remote, data-confirm и другие части Rails UJS. Эта зависимость больше не нужна, так как мы переписали rails-ujs для использования ванильного javascript . Вы, конечно, по-прежнему можете использовать jQuery, но вам это больше не нужно.
  • Благодаря Liceth Ovalles за ее работу по интеграции Yarn, Dangyi Liu за работу над rails-ujs и Guillermo Iguaran за сопровождение всего этого!

    Системные тесты

    В своем выступлении в RailsConf в 2014 году я подробно рассказал о том, как чрезмерное сосредоточение на модульных тестах (и TDD) привело нас в заблуждение. Хотя модульные тесты являются частью полного тестового решения, они не самые важные. Интеграционные тесты, которые проверяют поведение на всем пути от контроллеров через модели и представления, должны играть гораздо большую роль. Rails уже имеет отличный встроенный ответ на это.

    Но интеграционные тесты не помогают тестировать всю систему, если эта система использует javascript. И большинство основных веб-систем сегодня в какой-то мере полагаются на javascript. Вот тут-то и начинаются системные тесты под управлением реального браузера.

    Ответы на системные тесты, подобные этому, в Ruby называется Capybara . Это просто было своеобразное приключение, чтобы правильно настроить его в Rails. Так что теперь мы встроили его прямо во фреймворк ! Вы получаете прекрасную оболочку Capybara, которая предварительно настроена для Chrome и улучшена, чтобы обеспечить скриншоты сбоев в рамках Action Dispatch. Вам также больше не нужно беспокоиться о дополнительных стратегиях очистки базы данных , потому что встроенные транзакционные тесты теперь откатывают тестовые изменения системы.

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

    Спасибо Eileen M. Uchitelle за ее работу, извлеченную из Basecamp !

    Зашифрованные секреты

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

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

    Вдохновленные гемом sekrets Ara T. Howard , мы создали управление зашифрованными секретами в Rails 5.1 . Вы можете настроить новый файл зашифрованных секретов с помощью секретов bin/rails secrets:setup . Это создаст мастер-ключ, который вы будете хранить вне хранилища, но позволит вам закоммитить ваш актуальный продакшн-секрет в вашу систему контроля версий. Затем они дешифруются в процессе либо через введенный ключевой файл, либо через RAILS_MASTER_KEY в ENV.

    Спасибо Kasper Timm Hansen за работу над этим и Ara за вдохновение!

    Параметризированный mailer

    Action Mailer смоделирован по образцу Action Controller. Он поддерживает основы Abstract Controller, но давно находится в невыгодном положении от своего кузена-контроллера в том, как он может разделять логику между действиями.

    В Action Controller обычно используется before_action и подобные обратные вызовы для извлечения логики, применяемой к нескольким действиям. Это выполнимо, поскольку хеш params доступен до вызова этого действия. Но в Action Mailer мы использовали обычные сигнатуры методов с явными аргументами, поэтому эти аргументы не были доступны для фильтров, которые выполнялись перед действиями.

    С помощью параметризированного mailer’а мы теперь предоставляем вам возможность вызывать mailer с параметрами, которые, как и в контроллерах, доступны до вызова этого действия. Это сочетается с параметрами по умолчанию for/from/reply_to заголовков, что резко сокращает действия mailer’а.

    Он обладает полной обратной совместимостью, и вы можете конвертировать только mailer’ы, которые в выиграют от этого в первую очередь.

    Прямые и разрешенные маршруты

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

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

    С разрешенными маршрутами вы можете перепрограммировать полиморфный поиск моделей, основанных прямо на совместимых методах. Таким образом, это позволит вам превратить link_to @comment в конечный маршрут, такой как
    message_path(@comment.parent, anchor: «comment_#<@comment.id>«) .

    Объединение form_tag/form_for с form_with

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

    Спасибо Kasper Timm Hansen за это тоже!

    Все остальное

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

    Вашим менеджером релиза Rails 5.1 был Rafael Franca .

    Согласно нашей политике обслуживания выпуск Rails 5.1 означает, что исправления ошибок будут применяться только к 5.1.x, обычным проблемам безопасности до 5.1.x и 5.0.x, а также к серьезным проблемам безопасности до 5.1.x, 5.0.x и 4.2.х. Это означает, что 4.x и ниже по существу не будут поддерживаться!

    Спасибо всем в сообществе за их добросовестную работу по тестированию бета-версии и выпуску кандидатов в Rails 5.1! Мы сделали более 600 коммитов после сообщений об ошибках и проблемах, вызванных этим процессом. Thank you! Gracias! Merci! TAK!

    Как заменить jQuery на прототип в Rails 3.1

    У меня есть проект rails 3.1, который был создан с помощью jQuery по умолчанию. Каков наилучший способ конвертировать проект в использование прототипа?

    javascript prototypejs ruby-on-rails ruby-on-rails-3.1

    2 ответа

    • Удалить jquery gem из Gemfile
    • добавить к нему прототипы-рельсы

    Возможно, вы захотите добавить их в свой приложение/активы/JavaScripts/application.js:

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

    в их config/environment/development.rb.

    diodeus прав, я бы переписал его и рассмотрел как отличную возможность улучшить код;) тем не менее, мне любопытно, почему вы хотите переключиться на прототип TO

    Зачем нужен прототип (prototype)?

    02.09.2015, 18:24

    Зачем нужен оператор |
    Не могу понять, зачем и как использовать вот его>> | Вот примеры( объясните как это работает).

    Не понятно зачем нужен CORS
    Я не понимаю, зачем нужен CORS? Я знаю как он работает, знаю про заголовок Origin, и прочие вещи. Я.

    Зачем нужен API-ключ?
    И так все работает. Добавлено через 13 секунд Или нет? Что не работает

    Зачем javascript-y нужен var?
    Зачем для переменных нужен var, если код и без него даже ИЕ кушает? И ещё попутно хочу спросить.

    Какой из методов рациональнее использовать: Array.prototype.indexOf() или String.prototype.indexOf()
    функции передаётся буква алфавита в нижнем регистре и она должна вернуть следующую по очереди букву.

    02.09.2015, 19:08 2

    w3lifer, ну а как ты дашь например HTMLElement метод empty если не через прототип?
    а вообще prototype лучше использовать, так как можно будет вызывать эти методы как «статичные»
    да и наследование с прототипами легче делается, а в некоторых случаях без прототипов наследование не получится
    если, например, материнскому классу при инициалиции нужно передавать параметры

    Добавлено через 32 секунды
    короче prototype нужен для методов класса, а из нутри конечно можно, но не хорошо

    02.09.2015, 20:06 [ТС] 3

    Почему не хорошо?
    Если не затруднит, может Вы прокомментируете ситуацию именно по вышеуказанному уроку Codecademy?

    Добавлено через 50 минут
    Мысли вслух: при использовании «prototype» метод объявляется общим для всех, если же объявить его в конструкторе, то для каждого экземпляра объекта будет создан свой метод — в первом случае реализуется «экономия ресурсов», почему её не реализовать при объявлении через конструктор и не использовать «prototype» в принципе?
    https://jsfiddle.net/w7ouy3d3/
    https://jsfiddle.net/w7ouy3d3/1/
    Ладно, идём далее .

    02.09.2015, 23:06 4
    02.09.2015, 23:06
    02.09.2015, 23:29 [ТС] 5
    03.09.2015, 16:11 6
    07.09.2015, 15:58 7

    w3lifer, что-то я ступил сначала и объяснить не смог

    короче это эпос на тему prototype
    когда мы обращаемся к свойству объекта происходит примерно следующий алгоритм

    1. ищется само совйство объекта, если найдено, то возвращается значение свойства, если нет то идём дальше
    2. если не найденно свойство объекта, то смотрим в свойство constructor (тут хранится конструктор объекта, а точнее ссылка на него), идём к свойству этого родителя prototype , и если у prototype есть искомое свойство то возвращаем его, если нет то идём дальше
    3. раз мы сюда пришли, значит свойства вообще не существует, возвращаем undefined

    тк вот в чём различия твоего this. и prototype
    когда ты задаёшь через this. ты задаёшь свойство самого объекта, и на второй шаг алгоритма не идём
    а если в prototype, то при несуществовании такого свойства у объекта берётся значение из прототипа родителя

    плюсы this.:
    можно для каждого задать полностью свои значения и на другие объекты такого же класса это не повлияет
    минусы:
    как я уже говорил наследование не возможно
    за е шибёшься всё это прописывать в конструкторе

    плюсы prototype:
    инвертируй минусы this
    можно изменять свойства всем объектам разом, пример: мы делаем бегалку, но с читами или пасхалками, при введении определённого когда, надо чтобы все зайцы, которые являются объектами класса Rabbit, стали в 2 раза быстрее, можно сделать глобальную переменную rabbitSpeed и всё время её вставлять при вычислениях, а можно поменять в прототипе конструктора(Rabbit.prototype) дефалтовую скорость всех зайцев
    выводы из примера:
    гемора меньше,
    человеку, который придёт дорабатывать игру интуитивно понятно, что мы меняем скорость вообще всех зайцев
    мы используем ООП, то есть пользуемся её благами

    минусы prototype:
    блин, ну даже не знаю, я не встречал с ними проблем

    prototype.js или jQuery для новых проектов?

    Должен ли я использовать prototype.js или jQuery для нового проекта? Rails поставляется с prototype.js, но jQuery кажется библиотекой выбора для остального мира. Является ли prototype.js еще активным развитием? Кажется немного пыльным .

    9 ответов

    В основном это вопрос предпочтения. Некоторым людям не нравится, как Prototype расширяет объекты, но для основного использования они довольно эквивалентны.

    Прототип все еще находится в активной разработке — они выпустили кандидат на выпуск следующей версии всего пару месяцев назад.

    Ничего против прототипа, но у него нет количества плагинов или почти такого же большого количества сообщества, как jQuery.

    Для jQuery уже есть плагин для почти чего угодно, поэтому я в конечном итоге пишу намного меньше js.

    Причины, по которым я использую jQuery над любой другой библиотекой:

    Быстрый и легкий
    jQuery продолжает увеличивать производительность своей библиотеки даже в версиях с минимальным уровнем обновления. Когда они выпустили 1.4.2, скорость библиотеки значительно увеличилась даже с 1.4.1, как показано в Ajaxian blog post относительно его выпуска. Анализ производительности 1.4 по сравнению с 1.3 был выполнен командой jQuery в объявлении для версии 1.4 , показав, что 1,4 был сильно оптимизирован в областях его наиболее частого использования и что производительность также увеличилась по всем направлениям.

    Популярные
    jQuery обслуживается 48 из 2000 сайтов в каталоге Alexa, по сравнению с 6 Prototype, MooTools 3 и Dojo’s 1.

    Построенный с Javascript Distrobution показывает, что jQuery используется более 40% индексированных сайтов. Построено с Статистика использования jQuery показывает 32% сайтов Top 10 000 23% из 100 000 лучших сайтов и 15% из лучших 000 000 сайтов в своем индексе с помощью jQuery. Сравните с Prototype составляет 5%, 4% и 2% соответственно в этих категориях. Также обратите внимание на то, что в то время как обычаи jQuery постоянно растут вверх, использование прототипа движется вниз.

    Активно (и разумно) Разработано
    В марте jQuery опубликовал в своем блоге Microsoft расширит свое сотрудничество с сообществом jQuery . Из сообщения:

    Проект jQuery рад объявить, что Microsoft расширяет свою поддержку библиотеки JavaScript jQuery с помощью новых инициатив, включая внесение вкладов кода, интеграцию продуктов и выделение дополнительных ресурсов.

    Microsoft также отправит текущую версию jQuery JavaScript Library как в Visual Studio 2010, так и в ASP.NET MVC, а также продолжит размещение текущих версий библиотеки на CDN Microsoft.

    Microsoft теперь включена в сообщество активных участников кодовой базы jQuery. У Microsoft есть несколько очень умных разработчиков. Microsoft также вносит финансовый вклад в jQuery. Microsoft имеет много денег. Они также дают jQuery мгновенное воздействие любому разработчику с Visual Studio. Microsoft также работает над ASP.NET специально улучшает интеграцию jQuery :

    Также узнайте, как ASP.NET AJAX был разработан для бесперебойной работы с jQuery, как вы можете создавать элементы управления ASP.NET AJAX непосредственно из jQuery и как вы можете использовать библиотеку jQuery в коде ASP.NET AJAX.

    Подробнее о том, как использовать jQuery для Microsoft, можно найти в сообщении блога Скотта Гатри из ASP.NET jQuery и Microsoft .

    исходный код jQuery размещен публично на GitHub. Это плюс для меня лично, так как я предпочитаю Git другим системам SCM.

    jQuery Sizzle — отличный механизм выбора. И это здорово, потому что jQuery не работает над этим в одиночку. В блоге, объявившем о выпуске версии v1.3 в начале прошлого года, они в основном сказали: Мы знаем, что мыне умнее всех, и мы больше заинтересованы в продвижении сообщества, работая с умными людьми, чем в том, что мы лучше, чем они есть ».

    Одна вещь, которая стала очевидной при разработке нового движка: мы хотели иметь возможность сотрудничать с ним с другими библиотеками и разработчиками. Мы увидели возможность для прочного сотрудничества с некоторыми из лучших разработчиков JavaScript, результатом чего станет помощь пользователям всех библиотек. По этой причине мы убедились, что Sizzle смог работать полностью автономно (без зависимостей).

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

    Сейчас мы работаем с Prototype, Dojo, Yahoo UI, MochiKit и TinyMCE (и многими другими) на Sizzle, оттачивая его до совершенства.

    Мобильная поддержка
    В подкасте YayQuery Эпизод 18 (mp3) , Джон Сеньор излагает планы Поддержка мобильных телефонов jQuery. Философия заключается в том, что первичная библиотека jQuery должна быть совместима с мобильными устройствами, вместо того, чтобы писать специальную мобильную версию. Одно из многих преимуществ этого заключается в том, что, поскольку ваша основная библиотека используется как на общих, так и на мобильных сайтах, она, скорее всего, станет кэшированной, а сайты, предназначенные для мобильных устройств, могут использовать ту же кешированную версию, что и не мобильные сайты. Джон также объясняет, какие телефоны и операционные системы планируются для поддержки.

    jQuery можно использовать с PhoneGap для создания собственных приложений с использованием веб-технологий для мобильных устройств. Он также имеет плагин под названием JQTouch для создания собственных веб-приложений для iPhone и других мобильных браузеров. Это означает, что все те ярлыки, которые у вас есть с jQuery, переводятся на другие платформы, уменьшая кривую обучения.

    Активен в сообществе разработчиков
    Джон Ресиг является активным динамиком . Он включает не только крупные конференции и мероприятия, но также появился в подкастах, например YayQuery . Он также написал две книги: Javascript Ninja и Pro Javascript , как о чистых навыках Javascript, выделенный jQuery.

    Используйте то, что соответствует вашим требованиям. jQuery — отличная библиотека, которая активно развивается с большим (и большим) сообществом и является моей личной библиотекой выбора, но не позволяйте этому препятствовать использованию Prototype, если это правильный инструмент для работы.

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

    Я бы попробовал оба и посмотрел, какой из них лучше подходит вам.

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

    Я думаю, что вы можете получить оба, размещенные на серверах Google, и jQuery, размещенный на серверах MS, которые могут прийти вам на помощь.

    Я верю, что jQuery, размещенный через Google CDN, открывает оба.

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

    В то время как на поверхности он основан на ваших предпочтениях, рассмотрите возможность просмотра библиотек, плагинов и образцов вещей, которые вы хотите использовать для запуска jQuery или Prototype. Например, с новым проектом Rails все AJAX выполняется с использованием прототипа по умолчанию. Также, если вы хотите использовать jQuery UI или jQTouch (мобильный), я бы порекомендовал jQuery.

    Прототипы в JavaScript

    Когда вы определяете функцию в JavaScript она получает несколько предопределённых свойств. Одним из таких свойств является мнимый прототип. В данном уроке мы рассмотрим, что такое прототип, зачем и когда его нужно использовать.

    Что такое прототип?

    Свойство прототип — изначально пустой объект. Вы можете добавлять к нему члены, как и для любого другого объекта:

    В выше приведенном примере мы создали функцию. Но если вызвать myObject() , то она просто вернет объект window , так как определена в глобальном контексте. Следовательно, this возвращает глобальный объект, так как еще не было инициализации (подробнее далее в тексте урока).

    Секретная ссылка

    Прежде чем продолжить, нужно обсудить «секретную» ссылку, которая заставляет прототип работать так, как он работает.

    Каждый объект в JavaScript имеет «секретное» свойство, которое добавляется при определении или инициализации объекта. Данное свойство имеет имя __proto__ . Именно через него осуществляется доступ к цепочке прототипа. Однако использование __proto__ в своем приложении является плохой идеей, так как не все браузеры обеспечивают доступ к нему.

    Свойство __proto__ не следует путать с прототипом объекта. Это два разных свойства и они действуют взаимосвязано. Важно понимать различия, так как на первых порах очень легко запутаться. Давайте разберемся детально. Когда мы создаем функцию myObject , то определяем объект типа Function .

    Function — предопределённый объект в JavaScript и, следовательно, он имеет свои собственные свойства (например, length и arguments ) и методы (например, call и apply ). А также свой собственный прототип объекта и ссылку __proto__ . Все выше сказанное означает, что где-то внутри механизма JavaScript, есть код, который делает следующую операцию:

    В действительности все не так просто. Здесь мы приводим только иллюстративную часть для демонстрации работы цепочки прототипа.

    Итак, мы определяем myObject как функцию и даем ей один аргумент name , но не устанавливаем никаких других свойств, таких как length, или методов, таких как call . Почему же тогда работает следующий код?

    Потому, что когда мы определяем myObject , то создается свойство __proto__ и ему назначается значение Function.prototype (как было описано выше). Поэтому, когда мы обращаемся к myObject.length , происходит поиск свойства length у объекта myObject , и при отсутствии оного осуществляется переход по цепочке с помощью ссылки __proto__ link , находится свойство и возвращается его значение.

    Можно удивиться. почему length имеет значение 1 , а не 0 (или любое другое число). Потому, что myObject в действительности является реализацией Function .

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

    Дополнительно, когда вы создаете новый объект Function , код конструктора Function подсчитывает количество аргументов и обновляет свойство this.length соответствующим образом. В нашем случае аргумент 1 .

    Но если мы создаем реализацию myObject с помощью ключевого слова new , __proto__ указывает на myObject.prototype, так как myObject является конструктором новой реализации.

    В дополнение к методам в прототипа Function , например, к call и apply , у нас появляется метод myObject getName .

    Таким образом мы можем создавать столько реализаций объекта, сколько нужно.

    Зачем использовать прототип?

    Например, при разработке игры нужно несколько (возможно, сотню) объектов отображать на экране. Каждый объект имеет свои свойства, такие как координаты x и y , width , height , и другие.

    Можно сделать так:

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

    Но можно сохранить эффективность работы приложения. Нужно все лишь создать сотню разных объектов с единственным объектом prototype .

    Как использовать прототип?

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

    Теперь создаем 100 реализаций GameObject .

    У нас есть массив со ста объектами GameObject , которые используют общий прототип и определение метода draw , что существенно экономит память приложения.

    Вызов метода draw осуществляется очень просто:

    Прототип — обычный объект

    Прототип является обычным объектом. Если после создания всех экземпляров GameObject , мы решим выводить круги вместо прямоугольников, то можно просто обновить метод GameObject.prototype.draw .

    И теперь все предыдущие и будущие реализации GameObject будут рисовать круги.

    Изменение предопределенного прототипа.

    Да, такое возможно. Может быть вы знакомы с библиотеками JavaScript, например, Prototype, которые используют данную возможность.

    Теперь можно использовать данный метод для любой строки:

    Но у данного способа есть обратная сторона. Например, вы можете использовать выше описанный метод в своих приложениях, но в новой версии браузера может появиться предопределенный метод trim в прототипе String . Таким образом, ваше определение trim будет изменять предопределнное! Ой! Для преодоления данной проблемы можно использовать проверку.

    Хотя, обычно следует избегать изменения предопределенных прототипов.

    Заключение

    Прототипы помогают создавать более эффективные приложения JavaScript.

    5 последних уроков рубрики «Разное»

    Как разместить свой сайт на хостинге? Правильно выбранный хороший хостинг — это будущее Ваших сайтов

    Проект готов, Все проверено на локальном сервере OpenServer и можно переносить сайт на хостинг. Вот только какую компанию выбрать? Предлагаю рассмотреть хостинг fornex.com. Отличное место для твоего проекта с перспективами бурного роста.

    Разработка веб-сайтов с помощью онлайн платформы Wrike

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


    20 ресурсов для прототипирования

    Подборка из нескольких десятков ресурсов для создания мокапов и прототипов.

    Топ 10 бесплатных хостингов

    Небольшая подборка провайдеров бесплатного хостинга с подробным описанием.

    Быстрая заметка: массовый UPDATE в MySQL

    Ни для кого не секрет как в MySQL реализовать массовый INSERT, а вот с UPDATE-ом могут возникнуть сложности. Чтобы не прибегать к манипуляциям события ON_DUPLICATE можно воспользоваться специальной конструкцией CASE … WHEN … THEN.

    Что такое prototype в javascript?

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

    Задача следующая: прописать событие на заполнение формы внутри моего класса таким образом, чтобы внутри события я мог вызвать метод уже объекта этого класса, то есть метод не должен быть статическим. Сейчас при нажатии на submit я имею undefined при попытке вывести атрибут x.

    В этом же примере в качестве значения X выводится уже что-то более приличное, но одинаковое для двух объектов.

    Собственно почему так и как мне быть в моей ситуации?

    • Вопрос задан более трёх лет назад
    • 6081 просмотр

    Прототип (Конструктор.prototype) — объект, который клонируется по ссылке при создании инстанса конструктора через new. Создается объект со скрытым свойством [[prototype]], в некоторых движках свойство доступно как Инстанс.__proto__. При получении свойств инстанса, если они отсутствуют в новом объекте, движок ищет их в прототипе. Новые же свойства пишутся в инстанс, не изменяя прототип.
    В примере у вас еще и проблемы с пониманием контекста вызова (this) функции. В конструкторе (при вызове через new) это будет новый, созданный из прототипа, объект. В методе это объект, от которого его вызывают (до последней точки). При вызове как функции (без точек) это глобальный объект (window). Контекст можно привязать методом функции .bind или запустить функцию в произвольном контексте — .call, .apply.
    Вы добавляете метод и свойство в прототип при каждом вызове конструктора и запускаете метод в контексте прототипа, а не инстанса.

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

    Сегодня мы поговорим о работе с классами в JS. Думаю, что все, кто интересуется разработкой, слышали про объектно-ориентированное программирование (ООП). Эта парадигма программирования в настоящее время одна из самых популярных. И сейчас мы узнаем, как она реализуется в языке JS. ООП — очень обширная тема, в ней много понятий, поэтому данная статья не будет исчерпывающей. Мы разберём только основные моменты, не сильно вдаваясь в понятия и термины.

    Сперва нужно разобраться с двумя основным идеями ООП — классом и объектом. Класс можно представить как чертёж, то есть проект чего-то, ещё не созданного, а объект — как воплощённый в жизнь чертёж. Если проводить параллель со строительством, то чертёж будущего дома — это класс, а построенный дом — объект.

    Сразу хочу отметить, что в JS нет ООП как такового. Вместо него используется прототипно-ориентированное программирование — один из стилей ООП. У него есть отличия, но сейчас я их опущу.

    ZIP Service, Москва, можно удалённо, от 100 000 ₽

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

    В ES6 был добавлен сахарок для работы с прототипами (для удобства буду называть их «классами»). Появились разнообразные команды для удобной разработки, которые скрывают под капотом все те «старые» команды из ES5.

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

    Создадим «класс» и объявим в нём конструктор ( constructor ). В конструкторе создадим две переменные: numberA и numberB . This — это указатель на текущий «класс». Если говорить проще, то теперь объявленные переменные будут доступны везде внутри «класса».

    В итоге получится следующее:

    Этот калькулятор будет хранить два значения «по умолчанию» и использовать их при работе, если не указано иное.

    Наш маленький калькулятор будет поддерживать 4 основные операции. Поэтому создадим метод add() , который будет суммировать переданные ему аргументы. А если таковых нет, то калькулятор просуммирует те самые значения по «умолчанию».

    Я не буду добавлять различные проверки вроде «числа ли переданы?», «не на ноль ли делим?» и т.д. Остановлюсь только на том, что должны быть переданы два аргумента, либо будут использоваться значения по умолчанию.

    Вот так будет выглядеть метод сложения:

    По аналогии с ним напишем методы для вычитания, умножения и деления. Единственное отличие — знак между операндами.

    После создания всех методов «класс» приобретёт следующий вид:

    Основная часть работы сделана. Осталось «создать экземпляр класса» и протестировать методы на работоспособность. Объявим константу calculate и присвоим ей новый объект. Посмотрите на пример, чтобы всё стало ясно:

    Команда new используется для создания нового «экземпляра класса». Значения по умолчанию в этом примере — 72 и 39. Их мы передаём в «класс» как аргументы в обычную функцию. По сути, «класс» это и есть простая функция с некоторыми отличиями.

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

    Да, всё правильно. Только в случае передачи двух аргументов, метод использует их. В остальных случаях используются значения по умолчанию.

    Сегодня я познакомил вас с прототипно-ориентированным программированием в JavaScript. Да-да, несмотря на то что ключевое слово для создания прототипа — class , это всё те же прототипы. Именно поэтому я брал слова «класс» и «экземпляр» в кавычки.

    JavaScript парадигма объектов и прототипов. Простое объяснение.

    Многим новым разработчикам, особенно тем, кто привык работать с традиционным ООП, работать в мире JavaScript может показаться не удобно и не привычно. Для них код на JavaScript может выглядеть грязным и запутанным. Данная статья это попытка объяснить, максимально простым языком, что такое объекты в JavaScript и рассказать о механизме наследования на основе прототипов.

    Хочу отметить, что я читал, множество статей, пытающихся объяснить что такое объекты в JavaScript, но я, никогда не читал те, которые объясняли бы их полностью или четко. Они часто сосредотачиваются исключительно на наследовании, оставляя без внимания другие важные моменты или не приводят достаточного количества примеров.

    Что такое объекты?

    Объекты на самом базовом уровне можно представить как список пар ключ / значение, причем ключ всегда является строкой, а значение… ну что-нибудь еще. Это похоже на то, что вы можете назвать «картой» или «словарем» на других языках. Все, базовые сущности которые вы обычно создаете в JavaScript, и которое не является примитивами, является объектами. Объекты облегчают упаковку и перемещение данных, а создание новых объектов в JavaScript более тривиально, чем в других объектно-ориентированных, таких как Java / C #.

    Когда говорят об объектах часто упоминают термин “свойство”. Этот термин означает определенную пару ключ/значение. Чтобы дать вам представление о том, как выглядят объекты, мы начнем с простого примера объекта с двумя свойствами: age и weight.

    Это фрагмент кода демонстрирует использования литеральной (инициирующей) нотации объекта. Объектный литерал не является переменной или возвращаемым значением.

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

    Как было замечено раньше, в Javascript все что не примитивы являются объектами, включая функции… Я знаю это может показаться странным. Сложно думать о функции как о группе пар ключ/значение. Так как функции объекты, их часто еще называют как объекты-фукнции. Это специальные группа пар ключ/значение с особыми свойствами для выполнения кода и передачи значений. Мы рассмотрим эти свойства в следующем разделе. В начале давайте поговорим почему функции так важны.

    Можно сказать, что объекты-функции имеют две основные цели. Если мы хотим создать блок логики, который выполняется, мы можем использовать объект-функцию: точно так же, как «методы» в любом другом языке программирования. Другой целью является то, что если мы захотим создать объекты со значениями и методами, и возможно, с некоторой логикой для установки этих значений, мы также будем использовать объекты-функции. Здесь вы можете думать о объектах-функциях как о «классах», которые ведут себя как объектно-ориентированные языки (то есть Java / C #).

    В JavaScript вы часто встречаете термин “метод” . Термин метод относится к объекту-функции которая является свойством другого объекта.

    В стандартном случае функции в JavaScript выглядят как функции на любом другом языке; они выполняют логику для выполнения конкретной задачи.

    Если мы хотим упаковать небольшую группу данных, как, например, два наших свойства в объекте Dog, то достаточно простой список пар ключ / значение. Но что если мы захотим создать несколько объектов Dog? Может быть, некоторые значения должны быть статическими, а другие динамическими. В данном случае нам понадобятся объекты-функции. Когда мы вызываем функцию с помощью new, объект (он же экземпляр-объект) вернется со свойствами установленными с помощью ключевого слова this внутри функции.

    Объекты против Прототипов

    Теперь давайте поговорим о прототипах. Вы часто слышали, что JavaScript – это язык на основе прототипов. Значит ли это, что объекты и прототипы – это одно и то же? Ну, не совсем. Прототипы являются особым типом объекта и существуют как свойство для объектов-функций. Когда мы пытаемся получить доступ к ключу объекта-функции, JavaScript проверяет его свойство prototype, чтобы увидеть, есть ли оно там. Если нет, он пойдет вверх по цепочке прототипов, чтобы попытаться найти его. Чтобы понять цепочку прототипов, нам нужно узнать о функциях и наследовании.

    Функции и наследование

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

    Если мы попытаемся получить доступ к свойству объекта-экземпляра, а его там нет, JavaScript сначала перейдет к __proto__ и проверит, находится ли оно в прототипе родительской функции. Чтобы увидеть это в действии, давайте установим свойство в одном из атрибутов prototype нашего объекта Dog, и когда мы вызовем Spot[‘whatever the key name is’] или Bingo[‘whatever the key name is’], мы получим то же значение. Это будет работать даже после того, как будут созданы оба объекта-экземпляра dog.

    Создание методов таким способом (в отличие от использования this внутри функций) особенно полезна, потому что создание метода будет происходить только один раз, а не каждый раз, когда вызывается new. Это будет экономить память и увеличит производительность.

    Рассмотрим наследование немного глубже

    В основе JavaScript-наследования лежит ключевое слово Object, которое является объектом-функцией. Все экземпляры-объекты наследуются от него. Да же когда мы литерально создаем объект в действительности вызывается new Object(). Свойство нового объекта __proto__ будет указывать на прототип родительского объекта (Object().__proto__) . Таким образом, все объекты, создание из литералов объектов, на самом деле являются объектами экземпляра Object. Это предоставляет нам множество полезных родительских методов. Например hasOwnProperty, которое может сообщить нам, существует ли определенное свойство у объекта. Если мы попытаемся получить доступ к свойству непосредственно у объекта-функции, JavaScript сначала будет смотреть на prototype, а затем перемещаться вверх по цепочке, используя __proto__ у родительского prototype.

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

    Литеральный объект (Object-literal):

    Объект Экземпляр (Instance-Object):

    Объект Функция (Function-object):

    А как насчет __proto__ у объектов-функций?

    Как уже говорилось, __proto__ помогает связать объекты с прототипами, от которых они наследуются. А как насчет вызова __proto__ непосредственно для объектов-функций? JavaScript действительно имеет встроенный объект-функцию под названием Function. Свойство __proto__ каждой функции указывает на Function.prototype, который является функцией, но не имеет свойства prototype и возвращает неопределенное значение. Function.prototype определяет поведение по умолчанию, от которого наследуются все функции. Как и все свойства прототипов функциональных объектов, он по-прежнему имеет __proto__, который указывает на Object.prototype.

    Выше сказанное можно продемонстрировать следующей картинкой. Обратите внимание, что Object.prototype – это то, откуда все происходит.

    Многоуровневое наследование

    Когда мы говорим о наследовании, мы обычно думаем об объектах экземпляра, возвращаемых из функций. С прототипом вы также можете создать несколько уровней наследования и иметь объекты-функции, наследуемые от других объектов-функций. Все, что вам нужно сделать, это установить прототип дочернего объекта-функции в другой экземпляр прототипа родительского объекта-функции. Тогда все свойства родителя будут скопированы. Если родительская функция получает аргументы, такие как age и weight у Dog, используйте .call, чтобы установить свойство this дочернего объекта.

    Labrador наследуется от Dog:

    Классы

    Классы в JavaScript, созданные в ES6, являются просто синтаксическим сахаром над объектами-функциями. Вместо того, чтобы набирать прототип снова и снова, чтобы определить методы для функций, с ключевым словом class мы можем просто определить группу методов внутри класса. С помощью ключевого слова extends классы могут наследоваться от других классов без необходимости использовать Object.create и Object.call. Лично мне больше нравится использовать классы, но имейте в виду, что старые браузеры могут их не поддерживать. Для решения этой проблемы есть такие инструменты, как Babel.

    Использование объектов функций:

    Аналогичный код но с использованием классов:

    Объекты против примитивов

    Код JavaScript по сути сводится к двум основным типам: примитивам и объектам. В JavaScript есть 5 примитивов: boolean, number, string, null и undefined. Примитивы – это просто простые значения без свойств. Три примитива: boolean, number и string имеют дубликаты объектов, которые JavaScript использует как оболочку во время определенных операций. Например, “some string”.length вызовет new String() и вернет объект-экземпляр, обернутый вокруг строкового примитива, чтобы можно было получить доступ к свойству length. Как уже упоминалось, все объекты-экземпляры наследуются от Object. Так что со строкой вы можете использовать методы родительского объекта, например тот же hasOwnProperty.

    Что нового будет в Rails 1.1 и немного секретов Prototype.js

    # Вы не знаете JS: this и прототипы объектов

    Глава 5: Прототипы

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

    Примечание: Любые попытки эмуляции копирования классов, упомянутые в главе 4 как «примеси», полностью обходят механизм цепочки [[Prototype]] , рассматриваемый в этой главе.

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

    Примечание: Чуть ниже мы увидим, что объект может иметь пустую ссылку [[Prototype]] , хотя такой вариант встречается реже.

    Для чего используется ссылка [[Prototype]] ? В главе 3 мы изучили операцию [[Get]] , которая вызывается когда вы ссылаетесь на свойство объекта, например myObject.a . Стандартная операция [[Get]] сначала проверяет, есть ли у объекта собственное свойство a , если да, то оно используется.

    Примечание: Прокси-объекты ES6 выходят за рамки этой книги (мы увидим их в одной из следующих книг серии!), но имейте в виду, что обсуждаемое нами стандартное поведение [[Get]] и [[Put]] неприменимо если используются Proxy .

    Но нас интересует то, что происходит, когда a отсутствует в myObject , т.к. именно здесь вступает в действие ссылка [[Prototype]] объекта.

    Если стандартная операция [[Get]] не может найти запрашиваемое свойство в самом объекте, то она следует по ссылке [[Prototype]] этого объекта.

    Примечание: Чуть позже мы объясним что делает Object.create(..) и как он работает. Пока же считайте, что создается объект со ссылкой [[Prototype]] на указанный объект.

    Итак, у нас есть myObject , который теперь связан с anotherObject через ссылку [[Prototype]] . Очевидно, что myObject.a на самом деле не существует, однако обращение к свойству выполнилось успешно (свойство нашлось в anotherObject ) и действительно вернуло значение 2 .

    Если бы a не нашлось и в объекте anotherObject , то теперь уже его цепочка [[Prototype]] использовалась бы для дальнейшего поиска.

    Этот процесс продолжается до тех пор, пока либо не будет найдено свойство с совпадающим именем, либо не закончится цепочка [[Prototype]] . Если по достижении конца цепочки искомое свойство так и не будет найдено, операция [[Get]] вернет undefined .

    По аналогии с этим процессом поиска по цепочке [[Prototype]] , если вы используете цикл for..in для итерации по объекту, будут перечислены все свойства, достижимые по его цепочке (при условии, что они перечислимые — см. enumerable в главе 3). Если вы используете оператор in для проверки существования свойства в объекте, то in проверит всю цепочку объекта (независимо от перечисляемости).

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

    Но где именно «заканчивается» цепочка [[Prototype]] ?

    В конце каждой типичной цепочки [[Prototype]] находится встроенный объект Object.prototype . Этот объект содержит различные утилиты, используемые в JS повсеместно, поскольку все обычные (встроенные, не связанные с конкретной средой исполнения) объекты в JavaScript «происходят от» объекта Object.prototype (иными словами, имеют его на вершине своей цепочки [[Prototype]] ).

    Некоторые утилиты этого объекта могут быть вам знакомы: .toString() и .valueOf() . В главе 3 мы видели еще одну: .hasOwnProperty(..) . Еще одна функция Object.prototype , о которой вы могли не знать, но узнаете далее в этой главе — это .isPrototypeOf(..) .

    Установка и затенение свойств

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

    Если непосредственно у myObject есть обычное свойство доступа к данным с именем foo , то присваивание сводится к изменению значения существующего свойства.

    Если непосредственно у myObject нет foo , то выполняется обход цепочки [[Prototype]] по аналогии с операцией [[Get]] . Если foo не будет найдено в цепочке, то свойство foo добавляется непосредственно к myObject и получает указанное значение, как мы того и ожидаем.

    Однако если foo находится где-то выше по цепочке, то присваивание myObject.foo = «bar» может повлечь за собой более сложное (и даже неожиданное) поведение. Рассмотрим этот вопрос подробнее.

    Если свойство с именем foo присутствует как у самого myObject , так и где-либо выше в цепочке [[Prototype]] , начинающейся с myObject , то такая ситуация называется затенением. Свойство foo самого myObject затеняет любые свойства foo , расположенные выше по цепочке, потому что поиск myObject.foo всегда находит свойство foo , ближайшее к началу цепочки.

    Как уже отмечалось, затенение foo в myObject происходит не так просто, как может показаться. Мы рассмотрим три сценария присваивания myObject.foo = «bar» , когда foo не содержится непосредственно в myObject , а находится выше по цепочке [[Prototype]] объекта myObject :

    1. Если обычное свойство доступа к данным (см. главу 3) с именем foo находится где-либо выше по цепочке [[Prototype]] , и не отмечено как только для чтения ( writable:false ), то новое свойство foo добавляется непосредственно в объект myObject , и происходит затенение свойства.
    2. Если foo находится выше по цепочке [[Prototype]] , но отмечено как только для чтения ( writable:false ), то установка значения этого существующего свойства, равно как и создание затененного свойства у myObject , запрещены. Если код выполняется в strict mode , то будет выброшена ошибка, если нет, то попытка установить значение свойства будет проигнорирована. В любом случае, затенения не происходит.
    3. Если foo находится выше по цепочке [[Prototype]] и является сеттером (см. главу 3), то всегда будет вызываться сеттер. Свойство foo не будет добавлено в myObject , сеттер foo не будет переопределен.

    Большинство разработчиков предполагает, что присваивание свойства ( [[Put]] ) всегда приводит к затенению, если свойство уже существует выше по цепочке [[Prototype]] , но как видите это является правдой лишь в одной (№1) из трех рассмотренных ситуаций.

    Если вы хотите затенить foo в случаях №2 и №3, то вместо присваивания = нужно использовать Object.defineProperty(..) (см. главу 3) чтобы добавить foo в myObject .

    Примечание: Ситуация №2 может показаться наиболее удивительной из трех. Наличие свойства только для чтения мешает нам неявно создать (затенить) свойство с таким же именем на более низком уровне цепочки [[Prototype]] . Причина такого ограничения по большей части кроется в желании поддержать иллюзию наследования свойств класса. Если представить, что foo с верхнего уровня цепочки наследуется (копируется) в myObject , то имеет смысл запретить изменение этого свойства foo в myObject . Но если перейти от иллюзий к фактам и согласиться с тем, что никакого наследования с копированием на самом деле не происходит (см. главы 4 и 5), то кажется немного странным, что myObject не может иметь свойство foo лишь потому, что у какого-то другого объекта есть неизменяемое свойство foo . Еще более странно то, что это ограничение действует только на присваивание = , но не распространяется на Object.defineProperty(..) .

    Затенение при использовании методов приведет к уродливому явному псевдополиморфизму (см. главу 4) если вам потребуется делегирование между ними. Обычно затенение приносит больше проблем и сложностей, чем пользы, поэтому старайтесь избегать его если это возможно. В главе 6 вы увидите альтернативный шаблон проектирования, который наряду с другими вещами предполагает отказ от затенения в пользу более разумных альтернатив.

    Затенение может даже произойти неявно, поэтому если вы хотите его избежать, будьте бдительны. Например:

    Хотя может показаться, что выражение myObject.a++ должно (через делегирование) найти и просто инкрементировать свойство anotherObject.a , вместо этого операция ++ соответствует выражению myObject.a = myObject.a + 1 . В результате [[Get]] ищет свойство a через [[Prototype]] и получает текущее значение 2 из anotherObject.a , далее это значение увеличивается на 1, после чего [[Put]] присваивает значение 3 новому затененному свойству a в myObject . Ой!

    Будьте очень осторожны при работе с делегированными свойствами, пытаясь изменить их значение. Если вам нужно инкрементировать anotherObject.a , то вот единственно верный способ сделать это: anotherObject.a++ .

    Вы уже могли задаться вопросом: «Зачем одному объекту нужна ссылка ссылка на другой объект?» Какой от этого толк? Это очень хороший вопрос, но сначала нам нужно выяснить, чем [[Prototype]] не является, прежде чем мы сможем понять и оценить то, чем он является, и какая от него польза.

    В главе 4 мы выяснили, что в отличие от класс-ориентированных языков в JavaScript нет абстрактных шаблонов/схем объектов, называемых «классами». В JavaScript просто есть объекты.

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

    В JavaScript классы не могут описывать поведение объекта (учитывая тот факт, что их вообще не существует!). Объект сам определяет собственное поведение. Есть только объект.

    В JavaScript есть специфическое поведение, которым долгие годы цинично злоупотребляли для создания поделок, внешне похожих на «классы». Рассмотрим этот подход более подробно.

    Специфическое поведение «как бы классов» основано на одной странной особенности: у всех функций по умолчанию есть публичное, неперечислимое (см. главу 3) свойство, называемое prototype , которое указывает на некий объект.

    Этот объект часто называют «прототипом Foo», поскольку обращение к нему происходит через свойство с неудачно выбранным названием Foo.prototype . Как мы вскоре увидим, такая терминология обречена приводить людей в замешательство. Вместо этого, я буду называть его «объектом, ранее известным как прототип Foo». Ладно, шучу. Как насчет этого: «объект, условно называемый ‘Foo точка prototype'»?

    Как бы мы не называли его, что же это за объект?

    Проще всего объяснить так: у каждого объекта, создаваемого с помощью вызова new Foo() (см. главу 2), ссылка [[Prototype]] будет указывать на этот объект «Foo точка prototype».

    Проиллюстрируем на примере:

    Когда a создается путем вызова new Foo() , одним из результатов (все четыре шага см. в главе 2) будет создание в a внутренней ссылки [[Prototype]] на объект, на который указывает Foo.prototype .

    Остановитесь на секунду и задумайтесь о смысле этого утверждения.

    В класс-ориентированных языках множественные копии (или «экземпляры») создаются как детали, штампуемые на пресс-форме. Как мы видели в главе 4, так происходит потому что процесс создания экземпляра (или наследования от) класса означает «скопировать поведение из этого класса в физический объект», и это выполняется для каждого нового экземпляра.

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

    Вызов new Foo() создает новый объект (мы назвали его a ), и этот новый объект a связан внутренней ссылкой [[Prototype]] с объектом Foo.prototype .

    В результате получилось два объекта, связанных друг с другом. Вот и все. Мы не создали экземпляр класса. И мы уж точно не копировали никакого поведения из «класса» в реальный объект. Мы просто связали два объекта друг с другом.

    На самом деле секрет, о котором не догадывается большинство JS разработчиков, состоит в том, что вызов функции new Foo() практически никак напрямую не связан с процессом создания ссылки. Это всегда было неким побочным эффектом. new Foo() — это косвенный, окольный путь к желаемому результату: новому объекту, связанному с другим объектом.

    Можем ли мы добиться желаемого более прямым путем? Да! Герой дня — Object.create(..) . Но мы вернемся к нему чуть позже.

    В JavaScript мы не делаем копии из одного объекта («класса») в другой («экземпляр»). Мы создаем ссылки между объектами. В механизме [[Prototype]] визуально стрелки идут справа налево и снизу вверх.

    Этот механизм часто называют «прототипным наследованием» (мы подробнее рассмотрим код чуть ниже), которое обычно считается вариантом «классического наследования» для динамических языков. Это попытка воспользоваться общепринятым пониманием термина «наследование» в класс-ориентированных языках и подогнать* знакомую семантику под динамический язык.

    Термин «наследование» имеет очень четкий смысл (см. главу 4). Добавление перед ним слова «прототипное» чтобы обозначить на самом деле почти противоположное поведение привело к неразберихе в течение двух десятков лет.

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

    Лучше называть вещи своими именами. Это позволяет лучше понять как их сходство, так и многочисленные отличия.

    Из-за всей этой неразберихи с терминами я считаю, что само название «прототипное наследование» (а также некорректное использование связанных с ним терминов, таких как «класс», «конструктор», «экземпляр», «полиморфизм» и т.д.) принесло больше вреда чем пользы в понимании того, как на самом деле работает JavaScript.

    «Наследование» подразумевает операцию копирования, а JavaScript не копирует свойства объекта (по умолчанию). Вместо этого JS создает ссылку между двумя объектами, в результате один объект по сути делегирует доступ к свойствам/функциям другому объекту. «Делегирование» (см. главу 6) — более точный термин для описания механизма связывания объектов в JavaScript.

    Другой термин, который иногда встречается в JavaScript — это «дифференциальное наследование». Суть в том, что мы описываем поведение объекта с точки зрения того, что отличается в нем от более общего описания. Например, вы можете сказать, что автомобиль является видом транспортного средства, у которого ровно 4 колеса, вместо того чтобы заново описывать все свойства транспортного средства (двигатель, и т.д.).

    Если представить, что любой объект в JS является суммой всего поведения, которое доступно через делегирование, и мысленно объединить все это поведение в одну реальную сущность*, то можно предположить, что «дифференциальное наследование» (вроде как) подходящий термин.

    Но как и «прототипное наследование», «дифференциальное наследование» претендует на то, что мысленная модель важнее того, что физически происходит в языке. Здесь упускается из виду факт, что объект B на самом деле не создается дифференциально, а создается с конкретно заданными характеристиками, а также с «дырами», где ничего не задано. Эти дыры (пробелы или отсутствующие определения) могут заменяться механизмом делегирования, который на лету «заполняет их» делегированным поведением.

    Объект по умолчанию не сворачивается в единый дифференциальный объект посредством копирования, как это подразумевается в мысленной модели «дифференциального наследования». Таким образом, «дифференциальное наследование» не слишком подходит для описания реального механизма работы [[Prototype]] в JavaScript.

    Вы можете* придерживаться терминологии и мысленной модели «дифференциального наследования», но нельзя отрицать тот факт, что это лишь упражнение ума в вашей голове, а не реальное поведение движка.

    Вернемся к рассмотренному ранее коду:

    Что именно заставляет нас подумать, что Foo является «классом»?

    С одной стороны, мы видим использование ключевого слова new , совсем как в класс-ориентированных языках, когда создаются экземпляры классов. С другой стороны, кажется, что мы на самом деле выполняем метод конструктора класса, потому что метод Foo() на самом деле вызывается, так же как конструктор реального класса вызывается при создании экземпляра.

    У объекта Foo.prototype есть еще один фокус, который усиливает недоразумение, связанное с семантикой «конструкторов». Посмотрите на этот код:

    По умолчанию объект Foo.prototype (во время объявления в первой строке примера!) получает публичное неперечислимое (см. главу 3) свойство .constructor , и это свойство является обратной ссылкой на функцию (в данном случае Foo ), с которой связан этот объект. Более того, мы видим, что объект a , созданный путем вызова «конструктора» new Foo() , похоже тоже имеет свойство с именем .constructor , также указывающее на «функцию, создавшую его».

    Примечание: На самом деле это неправда. У a нет свойства .constructor , и хотя a.constructor действительно разрешается в функцию Foo , «конструктор» на самом деле не значит «был сконструирован этой функцией». Мы разберемся с этим курьезом чуть позже.

    Ах, да, к тому же. в мире JavaScript принято соглашение об именовании «классов» с заглавной буквы, поэтому тот факт что это Foo , а не foo является четким указанием, что мы хотим определить «класс». Это ведь абсолютно очевидно, не так ли!?

    Примечание: Это соглашение имеет такое влияние, что многие JS линтеры возмущаются когда вы вызываете new с методом, имя которого состоит из строчных букв, или не вызываете new с функцией, начинающейся с заглавной буквы. Удивительно, что мы с таким трудом пытаемся добиться (фальшивой) «класс-ориентированности» в JavaScript, что даже создаем правила для линтеров, чтобы гарантировать использование заглавных букв, хотя заглавные буквы вообще ничего не значат для движка JS.

    Конструктор или вызов?

    В примере выше есть соблазн предположить, что Foo — это «конструктор», потому что мы вызываем её с new и видим, что она «конструирует» объект.

    В действительности, Foo такой же «конструктор», как и любая другая функция в вашей программе. Функции сами по себе не являются конструкторами. Однако когда вы добавляете ключевое слово new перед обычным вызовом функции, это превращает вызов функции в «вызов конструктора». На самом деле new как бы перехватывает любую обычную функцию и вызывает её так, что в результате создается объект, а также выполняется код самой функции.

    NothingSpecial — обычная функция, но когда она вызывается с new , то практически в качестве побочного эффекта создает объект, который мы присваиваем a . Этот вызов был вызовом конструктора, но сама по себе функция NothingSpecial не является конструктором.

    Иначе говоря, в JavaScript «конструктор» — это любая функция, вызванная с ключевым словом new перед ней.

    Функции не являются конструкторами, но вызовы функций являются «вызовами конструктора» тогда и только тогда, когда используется new .

    Являются ли эти особенности единственными причинами многострадальных дискуссий о «классах» в JavaScript?

    Не совсем. JS разработчики постарались симулировать поведение классов настолько, насколько это возможно:

    Этот пример показывает два дополнительных трюка для «класс-ориентированности»:

    this.name = name : свойство .name добавляется в каждый объект ( a и b , соответственно; см. главу 2 о привязке this ), аналогично тому как экземпляры классов инкапсулируют значения данных.


    Foo.prototype.myName = . : возможно более интересный прием, добавляет свойство (функцию) в объект Foo.prototype . Теперь работает a.myName() , но каким образом?

    В примере выше велик соблазн думать, что при создании a и b свойства/функции объекта Foo.prototype копируются в каждый из объектов a и b . Однако этого не происходит.

    В начале этой главы мы изучали ссылку [[Prototype]] — часть стандартного алгоритма [[Get]] , которая предоставляет запасной вариант поиска, если ссылка на свойство отсутствует в самом объекте.

    В силу того, как создаются a и b , оба объекта получают внутреннюю ссылку [[Prototype]] на Foo.prototype . Когда myName не находится в a или b соответственно, она обнаруживается (через делегирование, см. главу 6) в Foo.prototype .

    И снова о «конструкторе»

    Вспомните наше обсуждение свойства .constructor . Кажется, что a.constructor === Foo означает, что в a есть реальное свойство .constructor , указывающее на Foo , верно? Не верно.

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

    Кажется ужасно удобным, что у объекта a , «созданного» Foo , будет доступ к свойству .constructor , которое указывает на Foo . Но это ложное чувство безопасности. Лишь по счастливой случайности a.constructor указывает на Foo через делегирование [[Prototype]] по умолчанию. На самом деле есть несколько способов наломать дров, предполагая что .constructor означает «использовался для создания».

    Начнем с того, что свойство .constructor в Foo.prototype по умолчанию есть лишь у объекта, создаваемого в момент объявления функции Foo . Если создать новый объект и заменить у функции ссылку на стандартный объект .prototype , то новый объект по умолчанию не получит свойства .constructor .

    Object(..) не был «конструктором» a1 , не так ли? Выглядит так, будто «конструктором» объекта должна быть Foo() . Многие разработчики думают, что Foo() создает объект, но эта идея трещит по швам, когда вы думаете что «constructor» значит «был создан при помощи». Ведь в таком случае a1.constructor должен указывать на Foo , но это не так!

    Что же происходит? У a1 нет свойства .constructor , поэтому он делегирует вверх по цепочке [[Prototype]] к Foo.prototype . Но и у этого объекта нет .constructor (в отличие от стандартного объекта Foo.prototype !), поэтому делегирование идет дальше, на этот раз до Object.prototype — вершины цепочки делегирования. У этого объекта действительно есть .constructor , который указывает на встроенную функцию Object(..) .

    Разрушаем заблуждение.

    Конечно, можно вернуть объекту Foo.prototype свойство .constructor , но это придется сделать вручную, особенно если вы хотите, чтобы свойство соответствовало стандартному поведению и было не перечислимым (см. главу 3).

    Как видите, для исправления .constructor необходимо много ручной работы. Больше того, все это мы делаем ради поддержания ошибочного представления о том, что «constructor» означает «используется для создания». Дорого же нам обходится эта иллюзия.

    Факт в том, что .constructor объекта по умолчанию указывает на функцию, которая, в свою очередь, имеет обратную ссылку на объект — ссылку .prototype . Слова «конструктор» и «прототип» лишь наделяются по умолчанию ненадежным смыслом, который позднее может оказаться неверным. Лучше всего постоянно напоминать себе, что «конструктор не значит используется для создания».

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

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

    Видите, насколько произвольным является его значение?

    Что в итоге? Некая произвольная ссылка на свойство объекта, например a1.constructor , не может считаться надежной ссылкой на функцию по умолчанию. Более того, как мы вскоре увидим, в результате небольшого упущения a1.constructor может вообще указывать на весьма странное и бессмысленное значение.

    a1.constructor — слишком ненадежная и небезопасная ссылка, чтобы полагаться на нее в коде. В общем случае таких ссылок по возможности следует избегать.

    Мы увидели некоторые типичные хаки для добавления механики «классов» в программы на JavaScript. Но «классы» JavaScript были бы неполными без попыток смоделировать «наследование».

    На самом деле мы уже видели механизм под названием «прототипное наследование», когда объект a «унаследовал от» Foo.prototype функцию myName() . Но обычно под «наследованием» подразумевается отношение между двумя «классами», а не между «классом» и «экземпляром».

    Мы уже видели эту схему, на которой показано не только делегирование от объекта (или «экземпляра») a1 к объекту Foo.prototype , но и от Bar.prototype к Foo.prototype , что отчасти напоминает концепцию наследования классов родитель-потомок. Вот только направление стрелок здесь другое, поскольку изображены делегирующие ссылки, а не операции копирования.

    Вот типичный пример кода в «прототипном стиле», где создаются такие ссылки:

    Примечание: Чтобы понять, почему this указывает на a , см. главу 2.

    Самая важная строка здесь Bar.prototype = Object.create( Foo.prototype ) . Object.create(..) создает «новый» объект из ничего, и связывает внутреннюю ссылку [[Prototype]] этого объекта с указанным объектом (в данном случае Foo.prototype ).

    Другими словами, эта строка означает: «создать новый объект ‘Bar точка prototype’, связанный с ‘Foo точка prototype'».

    При объявлении function Bar() < .. >функция Bar , как и любая другая, получает ссылку .prototype на объект по умолчанию. Но этот объект не ссылается на Foo.prototype , как мы того хотим. Поэтому мы создаем новый* объект, который имеет нужную ссылку, и отбрасываем исходный, неправильно связанный объект.

    Примечание: Типичная ошибка — пытаться использовать следующие варианты, думая, что они тоже сработают, но это приводит к неожиданным результатам:

    Bar.prototype = Foo.prototype не создает новый объект, на который ссылалось бы Bar.prototype . Вместо этого Bar.prototype становится еще одной ссылкой на Foo.prototype , и в результате Bar напрямую связывается с тем же самым объектом, что и Foo : Foo.prototype . Это значит, что когда вы начнете присваивать значения, например Bar.prototype.myLabel = . , вы будете изменять не отдельный объект, а общий объект Foo.prototype , что повлияет на любые объекты, привязанные к Foo.prototype . Это наверняка не то, чего вы хотите. В противном случае вам вообще не нужен Bar , и стоит использовать только Foo , сделав код проще.

    Bar.prototype = new Foo() действительно создает новый объект, корректно привязанный к Foo.prototype . Но для этого используется «вызов конструктора» Foo(..) . Если эта функция имеет какие-либо побочные эффекты (логирование, изменение состояния, регистрация в других объектах, добавление свойств в this , и т.д.), то эти побочные эффекты сработают во время привязывания (и возможно в отношении неправильного объекта!), а не только при создании конечных «потомков» Bar , как можно было бы ожидать.

    Поэтому, для правильного привязывания нового объекта без побочных эффектов от вызова Foo(..) у нас остается лишь Object.create(..) . Небольшой недостаток состоит в том, что нам приходится создавать новый объект и выбрасывать старый, вместо того чтобы модифицировать существующий стандартный объект.

    Было бы здорово если бы существовал стандартный и надежный способ поменять привязку существующего объекта. До ES6 был нестандартный и не полностью кроссбраузерный способ через свойство .__proto__ , которое можно изменять. В ES6 добавлена вспомогательная утилита Object.setPrototypeOf(..) , которая проделывает нужный трюк стандартным и предсказуемым способом.

    Сравните пред-ES6 и стандартизованный в ES6 способ привязки Bar.prototype к Foo.prototype :

    Если отбросить небольшой проигрыш в производительности (выбрасывание объекта, который позже удаляется сборщиком мусора), то способ с Object.create(..) немного короче и может даже читабельнее, чем подход ES6+. Хотя это всего лишь пустые разговоры о синтаксисе.

    Инспектируем связи между «классами»

    Что если у вас есть объект a и вы хотите выяснить, какому объекту он делегирует? Инспектирование экземпляра (объект в JS) с целью найти его предка (делегирующая связь в JS) в традиционных класс-ориентированных языках часто называют интроспекцией (или рефлексией).

    Как выполнить интроспекцию a , чтобы найти его «предка» (делегирующую связь)? Первый подход использует путаницу с «классами»:

    Оператор instanceof принимает в качестве операнда слева обычный объект, а в качества операнда справа — функцию. instanceof отвечает на следующий вопрос: присутствует ли где-либо в цепочке [[Prototype]] объекта a объект, на который указывает Foo.prototype ?

    К сожалению, это значит, что вы можете получить сведения о «происхождении» некоторого объекта ( a ) только имея некоторую функцию ( Foo c её ссылкой .prototype ). Если у вас есть два произвольных объекта, например a и b , и вы хотите узнать, связаны ли сами эти объекты друг с другом через цепочку [[Prototype]] , одного instanceof будет недостаточно.

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

    Использование функции с жесткой привязкой для «вызова конструктора» крайне маловероятно, но если вы сделаете это, то она будет вести себя так, как если бы вы вызвали целевую функцию. Это значит, что вызов instanceof с жестко привязанной функцией также ведет себя в соответствии с оригинальной функцией.

    Этот фрагмент кода показывает нелепость попыток рассуждать об отношениях между двумя объектами используя семантику «классов» и instanceof :

    Внутри isRelatedTo(..) мы временно используем функцию F , меняя значение её свойства .prototype на объект o2 , а затем спрашиваем, является ли o1 «экземпляром» F . Ясно, что o1 на самом деле не унаследован от и даже не создан с помощью F , поэтому должно быть понятно, что подобные приемы бессмысленны и сбивают с толку. Проблема сводится к несуразности семантики классов, навязываемой JavaScript, что наглядно показано на примере косвенной семантики instanceof .

    Второй подход к рефлексии [[Prototype]] более наглядный:

    Заметьте, что в этом случае нам неинтересна (и даже не нужна) Foo . Нам просто нужен объект (в данном случае произвольный объект с именем Foo.prototype ) для сопоставления его с другим объектом. isPrototypeOf(..) отвечает на вопрос: присутствует ли где-либо в цепочке [[Prototype]] объекта a объект Foo.prototype ?

    Тот же вопрос, и в точности такой же ответ. Но во втором подходе нам не нужна функция ( Foo ) для косвенного обращения к её свойству .prototype .

    Нам просто нужны два объекта для выявления связи между ними. Например:

    Заметьте, что в этом подходе вообще не требуется функция («класс»). Используются прямые ссылки на объекты b и c , чтобы выяснить нет ли между ними связи. Другими словами, наша утилита isRelatedTo(..) уже встроена в язык и называется isPrototypeOf(..) .

    Мы можем напрямую получить [[Prototype]] объекта. В ES5 появился стандартный способ сделать это:

    Здесь видно, что ссылка на объект является тем, что мы и ожидаем:

    В большинстве браузеров (но не во всех!) давно добавлена поддержка нестандартного альтернативного способа доступа к [[Prototype]] :

    Загадочное свойство .__proto__ (стандартизовано лишь в ES6!) «магически» возвращает ссылку на внутреннее свойство [[Prototype]] объекта, что весьма полезно, если вы хотите напрямую проинспектировать (или даже обойти: .__proto__.__proto__. ) цепочку.

    Аналогично рассмотренному ранее .constructor , свойство .__proto__ отсутствует у инспектируемого объекта ( a в нашем примере). На самом деле оно есть (и является неперечисляемым, см. главу 2) у встроенного Object.prototype , наряду с другими известными утилитами ( .toString() , .isPrototypeOf(..) , и т.д.).

    Более того, .__proto__ выглядит как свойство, но правильнее думать о нем как о геттере/сеттере (см. главу 3).

    Грубо говоря, можно представить, что .__proto__ реализовано так (см. главу 3 об определениях свойств объекта):

    Таким образом, когда мы обращаемся (получаем значение) к a.__proto__ , это похоже на вызов a.__proto__() (вызов функции геттера). В этом* вызове функции this указывает на a , несмотря на то что функция геттера находится в объекте Object.prototype (см. главу 2 о правилах привязки this ), так что это равносильно Object.getPrototypeOf( a ) .

    Значение .__proto__ можно также изменять, например с помощью функции Object.setPrototypeOf(..) в ES6, как показано выше. Однако обычно не следует изменять [[Prototype]] существующего объекта.

    Глубоко внутри некоторых фреймворков можно встретить сложные, продвинутые механизмы, позволяющие выполнять трюки наподобие «создания производного класса» от Array , но обычно подобные вещи не поощряются в повседневной практике, поскольку такой код гораздо труднее понимать и сопровождать.

    Примечание: В ES6 добавлено ключевое слово class , с помощью которого можно делать вещи, напоминающие «создание производных классов» от встроенных объектов, таких как Array . Обсуждение синтаксиса class из ES6 см. в Приложении А.

    В остальном же единственным исключением будет установка свойства [[Prototype]] стандартного объекта .prototype функции, чтобы оно ссылалось на какой-то другой объект (помимо Object.prototype ). Это позволит избежать замены этого стандартного объекта новым объектом. В любой другой ситуации лучше всего считать ссылку [[Prototype]] доступной только для чтения, чтобы облегчить чтение вашего кода в будущем.

    Примечание: В сообществе JavaScript неофициально закрепилось название двойного подчеркивания, особенно перед именами свойств (как __proto__ ): «dunder». Поэтому в мире JavaScript «крутые ребята» обычно произносят __proto__ как «dunder proto».

    Как мы уже видели, механизм [[Prototype]] является внутренней ссылкой, существующей у объекта, который ссылается на другой объект.

    Переход по этой ссылке выполняется (в основном) когда происходит обращение к свойству/методу первого объекта, и это свойство/метод отсутствует. В таком случае ссылка [[Prototype]] указывает движку, что свойство/метод нужно искать в связанном объекте. Если и в этом объекте ничего не находится, то происходит переход по его ссылке [[Prototype]] , и так далее. Эта последовательность ссылок между объектами образует то, что называется «цепочкой прототипов».

    Мы подробно объяснили, почему механизм [[Prototype]] в JavaScript не похож на «классы», и увидели, что вместо этого создаются ссылки между подходящими объектами.

    В чем смысл механизма [[Prototype]] ? Почему многие JS разработчики прикладывают так много усилий (эммулируя классы), чтобы настроить эти ссылки?

    Помните, как в начале этой главы мы сказали, что Object.create(..) станет нашим героем? Теперь вы готовы это увидеть.

    Object.create(..) создает новый объект ( bar ), связанный с объектом, который мы указали ( foo ), и это дает нам всю мощь (делегирование) механизма [[Prototype]] , но без ненужных сложностей вроде функции new , выступающей в роли классов и вызовов конструктора, сбивающих с толку ссылок .prototype и .constructor , и прочих лишних вещей.

    Примечание: Object.create(null) создает объект с пустой (или null ) ссылкой [[Prototype]] , поэтому этот объект не сможет ничего делегировать. Поскольку у такого объекта нет цепочки прототипов, оператору instanceof (рассмотренному ранее) нечего проверять, и он всегда вернет false . Эти специальные объекты с пустым [[Prototype]] часто называют «словарями», поскольку они обычно используются исключительно для хранения данных в свойствах, потому что у них не может быть никаких побочных эффектов от делегируемых свойств/функций цепочки [[Prototype]] , и они являются абсолютно плоскими хранилищами данных.

    Для создания продуманных связей между двумя объектами нам не нужны классы. Нам нужно только лишь связать объекты друг с другом для делегирования, и Object.create(..) дает нам эту связь без лишней возни с классами.

    Полифилл для Object.create()

    Object.create(..) была добавлена в ES5. Вам может понадобиться поддержка пред-ES5 окружения (например, старые версии IE), поэтому давайте рассмотрим простенький частичный полифилл для Object.create(..) :

    В этом полифилле используется временно создаваемая функция F , и её свойство .prototype переопределяется так, чтобы указывать на объект, с которым нужно создать связь. Затем мы используем new F() , чтобы создать новый объект, который будет привязан нужным нам образом.

    Такой вариант использования Object.create(..) встречается в подавляющем большинстве случаев, поскольку эту часть можно заменить полифиллом. В ES5 стандартная функция Object.create(..) предоставляет дополнительную функциональность, которую нельзя заменить полифиллом в пред-ES5. Для полноты картины рассмотрим, в чем она заключается:

    Второй аргумент Object.create(..) указывает свойства, которые будут добавлены в создаваемый объект, объявляя дескриптор каждого нового свойства (см. главу 3). Поскольку полифиллинг дескрипторов свойств в пред-ES5 невозможен, эту дополнительную функциональность Object.create(..) также невозможно реализовать в виде полифилла.

    В большинстве случаев используется лишь та часть функциональности Object.create(..) , которую можно заменить полифиллом, поэтому большинство разработчиков устраивает использование частичного полифилла в пред-ES5 окружениях.

    Некоторые разработчики придерживаются более строгого подхода, считая, что можно использовать только полные полифиллы. Поскольку Object.create(..) — одна из тех утилит, что нельзя полностью заменить полифиллом, такой строгий подход предписывает, что если вы хотите использовать Object.create(..) в пред-ES5 окружении, то вместо полифилла следует применить собственную утилиту, и вообще воздержаться от использования имени Object.create . Вместо этого можно определить свою собственную утилиту:

    Я не разделяю такой подход. Меня полностью устраивает показанный выше частичный полифилл Object.create(..) и его использование в коде даже в пред-ES5. Решайте сами, какой подход вам ближе.

    Ссылки в роли запасных свойств?

    Существует соблазн думать, что эти ссылки между объектами в основном предоставляют что-то вроде запасного варианта на случай «отсутствующих» свойств или методов. И хотя такой вывод допустим, я не считаю что это верный способ размышления о [[Prototype]] .

    Этот код работает благодаря [[Prototype]] , но если вы написали его так, что anotherObject играет роль запасного варианта на случай если myObject не сможет обработать обращение к некоторому свойству/методу, то вероятно ваше ПО будет содержать больше «магии» и будет сложнее для понимания и сопровождения.

    Я не хочу сказать, что такой подход является в корне неверным шаблоном проектирования, но он нехарактерен для JS. Если вы используете его, возможно вам стоит сделать шаг назад и подумать, является ли такое решение уместным и разумным.

    Примечание: В ES6 добавлена продвинутая функциональность, называемая Proxy , с помощью которой можно реализовать что-то наподобие поведения «отсутствующих методов». Proxy выходит за рамки этой книги, но будет подробно рассмотрена в одной из следующих книг серии «Вы не знаете JS».

    Не упустите одну важную, но едва уловимую мысль.

    Явно проектируя ПО таким образом, что разработчик может вызвать, к примеру, myObject.cool() , и это будет работать даже при отсутствии метода cool() у myObject , вы добавляете немного «магии» в дизайн вашего API, что может в будущем преподнести сюрприз другим разработчикам, которые будут поддерживать ваш код.

    Но вы можете спроектировать API и без подобной «магии», не отказываясь при этом от преимуществ ссылки [[Prototype]] .

    Здесь мы вызываем myObject.doCool() — метод, который действительно есть у объекта myObject , делая наш API более явным (менее «магическим»). Внутри наша реализация следует шаблону делегирования (см. главу 6), используя делегирование [[Prototype]] к anotherObject.cool() .

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

    При попытке обратиться к несуществующему свойству объекта внутренняя ссылка [[Prototype]] этого объекта задает дальнейшее направление поиска для операции [[Get]] (см. главу 3). Этот каскад ссылок от объекта к объекту образует «цепочку прототипов» (чем то похожую на цепочку вложенных областей видимости) для обхода при разрешении свойства.

    У обычных объектов есть встроенный объект Object.prototype на конце цепочки прототипов (похоже на глобальную область видимости при поиске по цепочке областей видимости), где процесс разрешения свойства остановится, если свойство не будет найдено в предыдущих звеньях цепочки. У этого объекта есть утилиты toString() , valueOf() и несколько других, благодаря чему все объекты в языке имеют доступ к ним.

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

    Этим «другим объектом» является объект, на который указывает свойство .prototype функции, вызванной с new . Функции, вызываемые с new , часто называют «конструкторами», несмотря на то что они не создают экземпляры классов, как это делают конструкторы в традиционных класс-ориентированных языках.

    Хотя эти механизмы JavaScript могут напоминать «создание экземпляров классов» и «наследование классов» из традиционных класс-ориентированных языков, ключевое отличие в том, что в JavaScript не создаются копии. Вместо этого объекты связываются друг с другом через внутреннюю цепочку [[Prototype]] .

    По множеству причин, среди которых не последнюю роль играет терминологический прецедент, «наследование» (и «прототипное наследование») и все остальные ОО-термины не имеют смысла, учитывая то как на самом деле работает JavaScript.

    Более подходящим термином является «делегирование», поскольку эти связи являются не копиями, а делегирующими ссылками.

    Прототипы объектов

    Базовая компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Строительные блоки) и основы OOJS (см. Введение в объекты).

    Понять прототипы объектов JavaScript, как работают прототипные цепочки и как добавить новые методы в prototype свойство.

    Язык основанный на прототипах?

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

    Точнее, свойства и методы определяются в свойстве prototype функции-конструктора объектов, а не в самих объектах.

    В JavaScript создается связь между экземпляром объекта и его прототипом (свойство __proto__ , которое является производным от свойства prototype конструктора), а свойства и методы обнаруживаются при переходе по цепочке прототипов.

    Примечание: Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj) или через устаревшее свойство __proto__ ) и свойством prototype в функциях-конструкторах. Первое свойство является свойством каждого экземпляра, а второе — свойством конструктора. То есть Object.getPrototypeOf(new Foobar()) относится к тому же объекту, что и Foobar.prototype .

    Давайте посмотрим на пример, чтобы стало понятнее.

    Понимание прототипа объектов

    Вернемся к примеру, когда мы закончили писать наш конструктор Person() — загрузите пример в свой браузер. Если у вас еще нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).

    В этом примере мы определили конструкторную функцию, например:

    Затем мы создаём экземпляр объекта следующим образом:

    Если вы наберете « person1. » в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:

    В этом списке вы увидите элементы, определенные в конструкторе person 1 — Person() — name , age , gender , interests , bio , и greeting . Однако вы также увидите некоторые другие элементы — watch , valueOf и т. д. — они определены в объекте прототипа Person (), который является Object .

    Итак, что произойдет, если вы вызываете метод в person1 , который фактически определен в Object ? Например:

    Этот метод — Object.valueOf() наследуется person1, потому что его конструктором является Person(), а прототипом Person () является Object(). valueOf() возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:

    • Сначала браузер проверяет, имеет ли объект person1, доступный в нем метод valueOf(), как определено в его конструкторе, Person().
    • Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта ( Object() ) конструктора Person() доступный в нем метод valueOf() . Так оно и есть, поэтому он вызывается, и все хорошо!

    Примечание: Мы хотим повторить, что методы и свойства не копируются из одного объекта в другой в цепочке прототипов — к ним обращаются, поднимаясь по цепочке, как описано выше.

    Примечание: Официально нет способа получить доступ к объекту прототипа объекта напрямую — «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом [[prototype]] в спецификации для языка JavaScript ( см. ECMAScript). Однако у большинства современных браузеров есть свойство, доступное для них под названием __proto__ (это 2 подчеркивания с обеих сторон), который содержит объект-прототип объекта-конструктора. Например, попробуйте person1.__proto__ и person1.__proto__.__proto__ , чтобы увидеть, как выглядит цепочка в коде!

    С ECMAScript 2015 вы можете косвенно обращаться к объекту прототипа объекта Object.getPrototypeOf (obj) .

    Свойство prototype: Где определены унаследованные экземпляры

    Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу со ссылкой Object , вы увидите в левой части большое количество свойств и методов — это намного больше, чем количество унаследованных членов, доступных для объекта person1 . Некоторые из них унаследованы, а некоторые нет — почему это?

    Как упоминалось выше, что наследуемые — это те, которые определены в свойстве prototype (вы можете назвать это подпространством имен), то есть те, которые начинаются с Object.prototype. , а не те, которые начинаются с простого Object . Значение свойства prototype — это объект, который в основном представляет собой контейнер для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.

    Таким образом Object.prototype.watch() , Object.prototype.valueOf() и т.д. Доступны для любых типов объектов, которые наследуются от Object.prototype , включая новые экземпляры объектов, созданные из конструктора Person() .

    Object.is() , Object.keys() и другие члены, не определенные в контейнере prototype , не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype . Это методы / свойства, доступные только в конструкторе Object() .

    Примечание: Это кажется странным — как у вас есть метод, определенный для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта — см. Ссылку на конструктор Function() , если вы нам не верите.

    1. Вы можете проверить существующие свойства прототипа для себя — вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
    2. Результат не покажет вам очень много — ведь мы ничего не определили в прототипе нашего конструктора! По умолчанию prototype конструктора всегда пуст. Теперь попробуйте следующее:

    Вы увидите большое количество методов, определенных для свойства Object ‘s prototype , которые затем доступны для объектов, которые наследуются от Object , как показано выше.

    Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript — попробуйте найти методы и свойства, определенные на прототипе глобальных объектов String , Date , Number и Array , например. Все они имеют несколько элементов, определенных на их прототипе, поэтому, например, когда вы создаете строку, вот так:

    В myString сразу есть множество полезных методов, таких как split() , indexOf() , replace() и т. д.

    Важно: Свойство prototype является одной из наиболее противоречивых названий частей JavaScript — вы можете подумать, что this указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__ , помните ?). prototype вместо этого — свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.

    Снова create()

    Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.

    1. Например, попробуйте это в консоли JavaScript предыдущего примера:
    2. На самом деле create() создает новый объект из указанного объекта-прототипа. Здесь person2 создается с помощью person1 в качестве объекта-прототипа. Это можно проверить, введя в консоли следующее:

    Это вернет объект person1 .

    Свойство constructor

    Каждая функция-конструктор имеет свойство prototype , значением которого является объект, содержащий свойство constructor . Это свойство constructor указывает на исходную функцию-конструктор. Как вы увидите в следующем разделе, свойства, определенные в свойстве Person.prototype (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор Person() . Следовательно, свойство конструктора также доступно для объектов person1 и person2 .

      Например, попробуйте эти команды в консоли:

    Они должны возвращать конструктор Person() , поскольку он содержит исходное определение этих экземпляров.

    Хитрый трюк заключается в том, что вы можете поместить круглые скобки в конец свойства constructor (содержащие любые требуемые параметры) для создания другого экземпляра объекта из этого конструктора. Конструктор — это функция в конце концов, поэтому ее можно вызвать с помощью круглых скобок; вам просто нужно включить ключевое слово new , чтобы указать, что вы хотите использовать эту функцию в качестве конструктора.

  • Попробуйте это в консоли:
  • Теперь попробуйте получить доступ к функциям вашего нового объекта, например:
  • Это хорошо работает. Вам не нужно будет использовать его часто, но это может быть действительно полезно, если вы хотите создать новый экземпляр и не имеете ссылки на исходный конструктор, который легко доступен по какой-либо причине.

    Свойство constructor имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:

    Например, попробуйте это:

    Примечание: Значение constructor.name может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. д.), Поэтому для более сложных примеров вы захотите использовать оператор instanceof .

    Изменение прототипов

    Давайте рассмотрим пример изменения свойства prototype функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.

    1. Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство prototype конструктора:
    2. Сохраните код и загрузите страницу в браузере и попробуйте ввести следующее в текстовый ввод:

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

    Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаем экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:

    Но метод farewell() по-прежнему доступен в экземпляре объекта person1 — его элементы были автоматически обновлены, чтобы включить недавно определенный метод farewell ().

    Примечание: Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).

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

    Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName из name.first и name.last :

    Однако это не работает, поскольку в этом случае this будет ссылка на глобальную область, а не на область функции. Вызов этого свойства вернет undefined undefined . Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.

    Фактически, довольно распространенный шаблон для большего количества определений объектов — это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:

    Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.

    Резюме

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

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

    Мастер Йода рекомендует:  Обзор FooGallery - гибкий плагин для галереи в WordPress
    Добавить комментарий
    Необходимые знания: