какое ключевое слово позволяет создавать объекты общего вида
JavaScript — тест с ответами
Информатика в настоящее время является стремительно развивающийся наукой. Многие студенты постают в технические университеты, чтобы в будущем связать свою деятельность с IT или приближенными областями. Для проверки знаний по теме JavaScript предлагаем пройти тестирование на этой странице. Обращаем ваше внимание, что в тесте правильные ответы выделены символом [+].
Какое ключевое слово позволяет создавать объекты общего вида:
Какая функция позволяет очищать конечную анимацию:
Что, из ниже перечисленного, относится к событию:
[-] а) начало загрузки web-страницы
[-] б) сохранение кодировки пользователем
[+] в) завершение загрузки web-страницы
Что, из ниже перечисленного, относится к событию:
[+] а) изменение кодировки пользователем
[-] б) начало загрузки web-страницы
[-] в) сохранение кодировки пользователем
Какой оператор служит для создания нового экземпляра из класса однотипных объектов:
В какой строке создастся новый объект Array:
[-] б) var pattern = new Array[a,b,c];
[+] в) var pattern = [a,b,c];
Какой метод позволяет изменять порядок элементов массива на противоположный:
Объекты, отвечающие за то, что содержится на Web-странице в окне браузера, называются:
Какой AJAX-транспорт позволит отправить файл на сервер без перезагрузки страницы:
[+] а) только использование фреймов(IFrame)
[-] б) XmlHttpRequest справится!
[-] в) невозможно ввиду ограничений безопасности javascript
Что нельзя сделать с помощью XmlHttpRequest:
[-] а) передать запрос другого типа кроме GET и POST
[+] б) сделать запрос с http://yandex. ru на http://google. com
[-] в) произвести запрос так, чтобы ответ был готов до следующей строки скрипта
Что такое ECMAScript:
[+] а) спецификация языка Javascript
[-] б) новый язык программирования
[-] в) переработанная реализация Javascript
Можно ли в скрипте перевести посетителя на другую страницу сайта:
[-] а) да, но только в рамках текущего сайта
Можно ли использовать один объект XmlHttpRequest для множества разных запросов:
[-] б) нет, только один запрос на один объект
[-] в) да, но перед каждым новым запросом надо вызывать abort()
Сколько параметров можно передать функции:
[-] а) сколько указано в определении функции или меньше
[+] б) любое количество
[-] в) ровно столько, сколько указано в определении функции
Какая арифметическая операция приводит к ошибке в javascript:
[-] а) корень из отрицательного числа
[+] в) нет верного ответа
Какие конструкции для циклов есть в javascript:
Тест с ответами: “JavaScript”
1. Какое ключевое слово позволяет создавать объекты общего вида:
а) object +
б) this
в) prototype
2. Какая функция позволяет очищать конечную анимацию:
а) setInterval
б) clearTimeout +
в) setTimeout
3. Что, из ниже перечисленного, относится к событию:
а) начало загрузки web-страницы
б) сохранение кодировки пользователем
в) завершение загрузки web-страницы +
4. Что, из ниже перечисленного, относится к событию:
а) изменение кодировки пользователем +
б) начало загрузки web-страницы
в) сохранение кодировки пользователем
6. Какой оператор служит для создания нового экземпляра из класса однотипных объектов:
а) this
б) new +
в) prototype
7. В какой строке создастся новый объект Array:
а) var pattern = /s&/;
б) var pattern = new Array[a,b,c];
в) var pattern = [a,b,c]; +
8. Какой метод позволяет изменять порядок элементов массива на противоположный:
а) reverse() +
б) join()
в) sort()
10. Объекты, отвечающие за то, что содержится на Web-странице в окне браузера, называются:
а) пользовательскими
б) клиентскими +
в) встроенными
11. Какой AJAX-транспорт позволит отправить файл на сервер без перезагрузки страницы:
а) только использование фреймов(IFrame) +
б) XmlHttpRequest справится!
в) невозможно ввиду ограничений безопасности javascript
12. Что нельзя сделать с помощью XmlHttpRequest:
а) передать запрос другого типа кроме GET и POST
б) сделать запрос с http://yandex.ru на http://google.com +
в) произвести запрос так, чтобы ответ был готов до следующей строки скрипта
13. Что такое ECMAScript:
а) спецификация языка Javascript +
б) новый язык программирования
в) переработанная реализация Javascript
14. Можно ли в скрипте перевести посетителя на другую страницу сайта:
а) да, но только в рамках текущего сайта
б) нет, нельзя
в) да, куда угодно +
15. Можно ли использовать один объект XmlHttpRequest для множества разных запросов:
а) можно +
б) нет, только один запрос на один объект
в) да, но перед каждым новым запросом надо вызывать abort()
16. Сколько параметров можно передать функции:
а) сколько указано в определении функции или меньше
б) любое количество +
в) ровно столько, сколько указано в определении функции
17. Какая арифметическая операция приводит к ошибке в javascript:
а) корень из отрицательного числа
б) деление на ноль
в) нет верного ответа +
18. Какие конструкции для циклов есть в javascript:
а) только одна: for
б) три: for, while и do…while. +
в) только две: for и while.
19. Какие из этих тэгов соответствуют стандарту HTML (strict):
а)
Создание объектов и конструкторы
Разбираем основные моменты, связанные с написанием собственных конструкторов классов в Java.
Процесс создания объектов — один из важных аспектов программирования на Java. Под созданием подразумевают как минимум две основные операции: создание нового объекта класса и инициализацию полей объекта.
В этой статье мы рассмотрим:
Программист, преподаватель Skillbox. Пишет про Java.
Что такое конструкторы классов
Следовательно, конструктор класса Cat называется Cat (). В результате работы этого конструктора будет создан новый объект класса Cat. Цель конструктора — правильно инициализировать объект перед его использованием.
Самый простой способ создать объект — это строка вида:
Рассмотрим порядок создания объекта. В этой строке выполняется три действия:
Приведённый выше код можно разбить на две строки:
Выполнять отдельно создание объекта не имеет смысла, так как в таком случае мы не сможем с ним работать — ведь ссылка на него нигде не сохранится.
В первой строке кода переменная murka объявляется как ссылка на объект типа Cat. Здесь важно понять, что объектная переменная фактически не содержит никакого объекта. Значение любой объектной переменной в Java представляет собой ссылку на объект, размещённый в памяти. В данный момент переменная murka пока ещё не ссылается на объект (содержит пустое значение null).
Во второй строке кода создаётся новый объект типа Cat, а ссылка на него присваивается переменной murka. С этого момента переменная murka оказывается ассоциированной с объектом. Чтобы работать с объектами, нужно сначала создать их и задать их исходное состояние. Затем к этим объектам можно применять методы.
Теперь взглянем на код класса Cat (без методов):
Видно, что все объявленные в классе переменные в результате работы конструктора получили значение — объект готов к использованию. Мы можем вызывать различные методы класса, просматривать значение переменных — никаких ошибок не появится.
Важно: Конструкторы имеются у всех классов, независимо от того, определите вы их или нет, поскольку Java автоматически предоставляет конструктор, используемый по умолчанию (без параметров) и инициализирующий все переменные экземпляра их значениями, заданными по умолчанию.
Для справки: Для числовых типов данных значением по умолчанию является нулевое, для типа boolean — логическое значение false, а для ссылочных типов — пустое значение null.
Но как только вы определите свой собственный конструктор, конструктор по умолчанию предоставляться не будет. Следовательно, если мы удалим конструктор из класса Cat и попытаемся создать объект через new Cat (), то объект будет создан, но все переменные в классе получат значения по умолчанию.
Сразу отметим, что полагаться на действия по умолчанию не следует. Если поля инициализируются неявно, программа становится менее понятной.
Как работают параметризованные конструкторы
В предыдущем примере использовался конструктор без параметров, который также называется конструктором по умолчанию. В некоторых случаях этого оказывается достаточно, но зачастую конструктор должен иметь один или несколько параметров. Добавление параметров в конструктор происходит точно так же, как и добавление параметров в метод, — для этого достаточно объявить их в скобках после имени конструктора.
Важно: Класс может иметь несколько различных конструкторов. Они (кстати, как и методы) отличаются между собой количеством, типом и порядком следования параметров. Если в классе несколько конструкторов с разным набором параметров, это называется перегрузкой конструктора.
Например, конструкторы ниже являются разными и вполне могут существовать в одном классе и иметь разную логику:
Заметьте, что конструкторы ниже для компилятора одинаковы и вызовут ошибку при запуске программы (тип, количество и порядок следования параметров идентичны):
Разберём пример применения параметризованного конструктора класса Cat с добавленным полем имени:
Как видно, в этих конструкторах очень много кода повторяется, что в целом не очень удобно и повышает вероятность ошибки: ведь если нам потребуется поменять один конструктор, необходимо будет не забыть внести изменения в другой.
Для этого применяется вызов конструктора из конструктора с использованием ключевого слова this, которое означает ссылку на текущий объект. Обратиться к конструктору из другого конструктора можно через вызов this () — так будет выполнен конструктор без параметров. Если же нужен конструктор с параметрами, их указывают в скобках.
Применять ключевое слово this для вызова другого конструктора очень удобно — нужно лишь один раз написать общий код для конструирования объекта.
Важно: вызов другого конструктора всегда должен стоять первой строкой в конструкторе.
Вот пример оптимизации кода первого из конструкторов:
Обратите внимание: Несколько слов насчёт ключевого слова this. Синтаксис языка Java не запрещает использовать имена параметров или локальных переменных, совпадающие с именами переменных экземпляра (класса). В таком случае говорят, что локальная переменная или параметр скрывает переменную экземпляра. При этом доступ к скрытой переменной экземпляра обеспечивается с помощью ключевого слова this.
Приведённый ниже пример конструктора класса Cat показывает, каким образом лучше выполнять присваивание переданных в конструктор параметров переменным класса:
При выборе имён параметров рекомендую в первую очередь ориентироваться на читаемость кода. Чтобы, взглянув на этот код спустя некоторое время, вы сразу могли понять, что здесь происходит.
Но в целом читаемость кода — это отдельная и довольно обширная тема.
Инициализация полей объекта
Основная задача конструкторов — подготовка объекта к работе с ним и установка значений для полей (переменных) объекта. Но есть и другие варианты установки значения для полей. Это явная инициализация и так называемые блоки инициализации.
Явная инициализация — это возможность присвоить полю соответствующее значение указанным ниже образом:
Здесь присваивание выполняется до вызова конструктора. Такой подход оказывается полезным в тех случаях, когда требуется, чтобы поле имело конкретное значение — независимо от вызова конструктора класса.
Блоки инициализации выглядят так:
Такой блок выполняется каждый раз, когда создаётся объект данного класса. В этом примере начальное значение поля id задаётся в блоке инициализации объекта. Причём неважно, какой именно конструктор используется для создания экземпляра класса. Первым выполняется блок инициализации, а вслед за ним — тело конструктора.
В таком случае блок инициализации будет выполнен при первом обращении к этому классу. Попробуйте выполнить вот такой код:
Что в итоге
При таком многообразии способов инициализации полей класса трудно отследить все возможные пути создания объекта. Поэтому рассмотрим подробнее действия, которые происходят при вызове конструктора:
1. Если в первой строке кода одного конструктора вызывается второй конструктор, то второй конструктор выполняется с предоставляемыми аргументами.
3. Выполняется тело конструктора.
Естественно, код, отвечающий за инициализацию полей, нужно организовать так, чтобы в нём можно было легко разобраться. Например, было бы странным, если бы вызов конструкторов класса зависел от порядка объявления полей. Такой подход чреват ошибками.
Инициализировать статическое поле следует, задавая его начальное значение или используя статический блок инициализации — в случае, если для инициализации статического поля требуется сложный код.
На заметку: важный момент, о котором часто забывают при написании собственных конструкторов, — в конструкторах не должно содержаться никакой бизнес-логики. Их задача — корректное создание объектов и подготовка их к дальнейшему использованию. Вся логика должна находиться в соответствующих методах.
Для изучения — пример, который содержит все элементы, описанные в статье. А именно:
Мы рассмотрели все возможные пути создания объектов и инициализации их полей в Java. Теперь вы сможете создавать свои объекты любым из представленных способов, а также осознанно читать в коде порядок создания объектов других классов.
JavaScript: исследование объектов
Материал, перевод которого мы сегодня публикуем, посвящён исследованию объектов — одной из ключевых сущностей JavaScript. Он рассчитан, преимущественно, на начинающих разработчиков, которые хотят упорядочить свои знания об объектах.
Объекты в JavaScript представляют собой динамические коллекции свойств, которые, кроме того, содержат «скрытое» свойство, представляющее собой прототип объекта. Свойства объектов характеризуются ключами и значениями. Начнём разговор о JS-объектах с ключей.
Ключи свойств объектов
Ключ свойства объекта представляет собой уникальную строку. Для доступа к свойствам можно использовать два способа: обращение к ним через точку и указание ключа объекта в квадратных скобках. При обращении к свойствам через точку ключ должен представлять собой действительный JavaScript-идентификатор. Рассмотрим пример:
При попытке обращения к несуществующему свойству объекта сообщения об ошибке не появится, но возвращено будет значение undefined :
При использовании для доступа к свойствам квадратных скобок можно применять ключи, которые не являются действительными JavaScript-идентификаторами (например, ключ может быть строкой, содержащей пробелы). Они могут иметь любое значение, которое можно привести к строке:
Если в качестве ключей используются нестроковые значения, они автоматически преобразуются к строкам (с использованием, если это возможно, метода toString() ):
Значения свойств объектов
Свойства объекта могут быть примитивными значениями, объектами или функциями.
▍Объект как значение свойства объекта
Объекты можно помещать в другие объекты. Рассмотрим пример:
Подобный подход можно использовать для создания пространств имён:
▍Функция как значение свойства объекта
У этого ключевого слова, однако, могут быть разные значения, что зависит от того, как именно была вызвана функция. Здесь можно почитать о ситуациях, в которых this теряет контекст.
Динамическая природа объектов
Объекты в JavaScript, по своей природе, являются динамическими сущностями. Добавлять в них свойства можно в любое время, то же самое касается и удаления свойств:
Объекты как ассоциативные массивы
Объекты можно рассматривать как ассоциативные массивы. Ключи ассоциативного массива представляют собой имена свойств объекта. Для того чтобы получить доступ к ключу, все свойства просматривать не нужно, то есть операция доступа к ключу ассоциативного массива, основанного на объекте, выполняется за время O(1).
Прототипы объектов
Например, объект, созданный с помощью объектного литерала, имеет ссылку на Object.prototype :
▍Пустые объекты
Благодаря этому будет создан объект без прототипа. Такие объекты обычно используют для создания ассоциативных массивов.
▍Цепочка прототипов
У объектов-прототипов могут быть собственные прототипы. Если попытаться обратиться к свойству объекта, которого в нём нет, JavaScript попытается найти это свойство в прототипе этого объекта, а если и там нужного свойства не окажется, будет сделана попытка найти его в прототипе прототипа. Это будет продолжаться до тех пор, пока нужное свойство не будет найдено, или до тех пор, пока не будет достигнут конец цепочки прототипов.
Значения примитивных типов и объектные обёртки
JavaScript позволяет работать со значениями примитивных типов как с объектами, в том смысле, что язык позволяет обращаться к их свойствам и методам.
При этом, конечно, значения примитивных типов объектами не являются.
Для организации доступа к «свойствам» значений примитивных типов JavaScript, при необходимости, создаёт объекты-обёртки, которые, после того, как они оказываются ненужными, уничтожаются. Процесс создания и уничтожения объектов-обёрток оптимизируется JS-движком.
Встроенные прототипы
Расширение встроенных объектов с помощью полифиллов
JavaScript позволяет легко расширять встроенные объекты новыми функциями с помощью так называемых полифиллов. Полифилл — это фрагмент кода, реализующий возможности, не поддерживаемые какими-либо браузерами.
▍Использование полифиллов
▍Полифиллы и прототипы
С помощью полифиллов новые методы можно добавлять к прототипам объектов. Например, полифилл для String.prototype.trim() позволяет оснастить все строковые объекты методом trim() :
Одиночное наследование
Команда Object.create() позволяет создавать новые объекты с заданным объектом-прототипом. Эта команда используется в JavaScript для реализации механизма одиночного наследования. Рассмотрим пример:
Множественное наследование
Команда Object.assign() копирует свойства из одного или большего количества объектов в целевой объект. Её можно использовать для реализации схемы множественного наследования. Вот пример:
Иммутабельные объекты
Команда Object.freeze() позволяет «заморозить» объект. В такой объект нельзя добавлять новые свойства. Свойства нельзя удалять, нельзя и изменять их значения. Благодаря использованию этой команды объект становится неизменяемым или иммутабельным:
Команда Object.freeze() выполняет так называемое «неглубокое замораживание» объектов. Это означает, что объекты, вложенные в «замороженный» объект, можно изменять. Для того чтобы осуществить «глубокую заморозку» объекта, нужно рекурсивно «заморозить» все его свойства.
Клонирование объектов
Для создания клонов (копий) объектов можно использовать команду Object.assign() :
Эта команда выполняет неглубокое копирование объектов, то есть — копирует только свойства верхнего уровня. Вложенные объекты оказываются, для объектов-оригиналов и их копий, общими.
Объектный литерал
Объектные литералы дают разработчику простой и понятный способ создания объектов:
Однако такой способ создания объектов имеет и недостатки. В частности, при таком подходе все свойства объекта оказываются общедоступными, методы объекта могут быть переопределены, их нельзя использовать для создания новых экземпляров одинаковых объектов:
Метод Object.create()
Если прототип защищён от изменений, объект, являющийся его наследником, не сможет изменять свойства, определённые в прототипе. Теперь методы start() и stop() переопределить нельзя:
Конструкцию Object.create(timerPrototype) можно использовать для создания множества объектов с одним и тем же прототипом.
Функция-конструктор
В JavaScript существуют так называемые функции-конструкторы, представляющие собой «синтаксический сахар» для выполнения вышеописанных действий по созданию новых объектов. Рассмотрим пример:
Тут, для предотвращения изменения прототипа, опять же, можно прототип «заморозить»:
▍Ключевое слово new
Ключевое слово class
В ECMAScript 2015 появился новый способ выполнения вышеописанных действий, представляющий собой очередную порцию «синтаксического сахара». Речь идёт о ключевом слове class и о соответствующих конструкциях, связанных с ним. Рассмотрим пример:
Использование классов не делает прототипы неизменными. Их, если это нужно, придётся «замораживать» так же, как мы это уже делали:
Наследование, основанное на прототипах
В JavaScript объекты наследуют свойства и методы от других объектов. Функции-конструкторы и классы — это «синтаксический сахар» для создания объектов-прототипов, содержащих все необходимые методы. С их использованием создают новые объекты являющиеся наследниками прототипа, свойства которого, специфичные для конкретного экземпляра, устанавливают с помощью функции-конструктора или с помощью механизмов класса.
Хорошо было бы, если бы функции-конструкторы и классы могли бы автоматически делать прототипы неизменными.
Сильной стороной прототипного наследования является экономия памяти. Дело в том, что прототип создаётся лишь один раз, после чего им пользуются все объекты, созданные на его основе.
▍Проблема отсутствия встроенных механизмов инкапсуляции
В шаблоне прототипного наследования не используется разделение свойств объектов на приватные и общедоступные. Все свойства объектов являются общедоступными.
Например, команда Object.keys() возвращает массив, содержащий все ключи свойств объекта. Его можно использовать для перебора всех свойств объекта:
Существует один паттерн, имитирующий приватные свойства, полагающийся на то, что разработчики не будут обращаться к тем свойствам, имена которых начинаются с символа подчёркивания ( _ ):
Фабричные функции
Инкапсулированные объекты в JavaScript можно создавать с использованием фабричных функций. Выглядит это так:
Итоги
Уважаемые читатели! Если вы пришли в JavaScript из других языков, просим рассказать нам о том, что вам нравится или не нравится в JS-объектах, в сравнении с реализацией объектов в уже известных вам языках.
Объектно-ориентированный JavaScript простыми словами
Доброго времени суток, друзья!
В JavaScript существует 4 способа создать объект:
Для того, чтобы ответить на эти вопросы мы не только рассмотрим каждый подход в отдельности, но и сравним между собой классы и фабричные функции по следующим критериям: наследование, инкапсуляция, ключевое слово «this», обработчики событий.
Давайте начнем с того, что такое объектно-ориентированное программирование (ООП).
Что такое ООП?
По сути, ООП — это способ написания кода, позволяющий создавать объекты с помощью одного объекта. В этом также заключается суть шаблона проектирования «Конструктор». Общий объект, обычно, называется планом, проектом или схемой (blueprint), а создаваемые с его помощью объекты — экземплярами (instances).
Каждый экземпляр имеет как свойства, наследуемые от родителя, так и собственные свойства. Например, если у нас имеется проект Human (человек), мы можем создавать на его основе экземпляры с разными именами.
Второй аспект ООП состоит в структурировании кода, когда у нас имеется несколько проектов разного уровня. Это называется наследованием (inheritance) или классификацией (созданием подклассов) (subclassing).
Третий аспект ООП — инкапсуляция, когда мы скрываем детали реализации от посторонних, делая переменные и функции недоступными извне. В этом заключается суть шаблонов проектирования «Модуль» и «Фасад».
Перейдем с способам создания объектов.
Способы создания объекта
Функция-конструктор
Конструкторами являются функции, в которых используется ключевое слово «this».
this позволяет сохранять и получать доступ к уникальным значениям создаваемого экземпляра. Экземпляры создаются с помощью ключевого слова «new».
Класс
Классы являются абстракцией («синтаксическим сахаром») над функциями-конструкторами. Они облегчают задачу создания экземпляров.
Обратите внимание, что constructor содержит тот же код, что и функция-конструктор, приведенная выше. Мы должны это делать, чтобы инициализировать this. Мы может опустить constructor, если нам не требуется присваивать начальные значения.
На первый взгляд, классы кажутся сложнее, чем конструкторы — приходится писать больше кода. Придержите лошадей и не делайте поспешных выводов. Классы — это круто. Чуть позже вы поймете почему.
Экземпляры также создаются с помощью ключевого слова «new».
Связывание объектов
Данный способ создания объектов был предложен Kyle Simpson. В данном подходе мы определяем проект как обычный объект. Затем с помощью метода (который, как правило, называется init, но это не обязательно, в отличие от constructor в классе) мы инициализируем экземпляр.
Для создания экземпляра используется Object.create. После создания экземпляра вызывается init.
Код можно немного улучшить, если вернуть this в init.
Фабричная функция
Фабричная функция — это функция, возвращающая объект. Можно вернуть любой объект. Можно даже вернуть экземпляр класса или связывания объектов.
Вот простой пример фабричной функции.
Для создания экземпляра нам не требуется ключевое слово «new». Мы просто вызываем функцию.
Теперь давайте рассмотрим способы добавления свойств и методов.
Определение свойств и методов
Методы — это функции, объявленные в качестве свойств объекта.
В ООП существует два способа определения свойств и методов:
Определение свойств и методов в конструкторе
Для определения свойства в экземпляре необходимо добавить его в функцию-конструктор. Убедитесь, что добавляете свойство к this.
Методы, обычно, определяются в прототипе, поскольку это позволяет избежать создания функции для каждого экземпляра, т.е. позволяет всем экземплярам использовать одну функцию (такую функцию называют общей или распределенной).
Для добавления свойства в прототип используют prototype.
Создание нескольких методов может быть утомительным.
Можно облегчить себе жизнь с помощью Object.assign.
Определение свойств и методов в классе
Свойства экземпляра можно определить в constructor.
Свойства прототипа определяются после constructor в виде обычной функции.
Создание нескольких методов в классе проще, чем в конструкторе. Для этого нам не нужен Object.assign. Мы просто добавляем другие функции.
Определение свойств и методов при связывании объектов
Для определения свойств экземпляра мы добавляем свойство к this.
Метод прототипа определяется как обычный объект.
Определение свойств и методов в фабричных функциях (ФФ)
Свойства и методы могут быть включены в состав возвращаемого объекта.
При использовании ФФ нельзя определять свойства прототипа. Если вам нужны такие свойства, можно вернуть экземпляр класса, конструктора или связывания объектов (но это не имеет смысла).
Где определять свойства и методы
Где следует определять свойства и методы? В экземпляре или в прототипе?
Многие считают, что для этого лучше использовать прототипы.
Однако на самом деле это не имеет особого значения.
При определении свойств и методов в экземпляре, каждый экземпляр будет расходовать больше памяти. При определении методов в прототипах, память будет расходоваться меньше, но незначительно. Учитывая мощность современных компьютеров, эта разница является несущественной. Поэтому делайте так, как вам удобней, но все же предпочитайте прототипы.
Например, при использовании классов или связывания объектов, лучше использовать прототипы, поскольку в этом случае код легче писать. В случае ФФ прототипы использовать нельзя. Можно определять только свойства экземпляров.
Прим. пер.: позволю себе не согласиться с автором. Вопрос использования прототипов вместо экземпляров при определении свойств и методов — это не только вопрос расходования памяти, но, прежде всего, вопрос назначения определяемого свойства или метода. Если свойство или метод должны быть уникальными для каждого экземпляра, тогда они должны определяться в экземпляре. Если свойство или метод должны быть одинаковыми (общими) для всех экземпляров, тогда они должны определяться в прототипе. В последнем случае при необходимости внесения изменений в свойство или метод достаточно будет внести их в прототип, в отличие от свойств и методов экземпляров, которые корректируются индивидуально.
Предварительный вывод
На основе изученного материала можно сделать несколько выводов. Это мое личное мнение.
Классы против ФФ — Наследование
Прежде чем переходить к сравнению классов и ФФ, необходимо познакомиться с тремя концепциями, лежащими в основе ООП:
Что такое наследование?
В JavaScript наследование означает передачу свойств от родительского объекта к дочернему, т.е. от проекта к экземпляру.
Это происходит двумя способами:
Понимание создания подклассов
Создание подклассов — это когда дочерний проект расширяет родительский.
Рассмотрим это на примере классов.
Создание подклассов с помощью класса
Для расширения родительского класса используется ключевое слово «extends».
Например, давайте создадим класс «Developer», расширяющий класс «Human».
Класс «Developer» будет расширять Human следующим образом:
Ключевое слово «super» вызывает constructor класса «Human». Если вам это не нужно, super можно опустить.
Допустим, Developer умеет писать код (кто бы мог подумать). Добавим ему соответствующий метод.
Вот пример экземпляра класса «Developer».
Создание подклассов с помощью ФФ
Для создания подклассов с помощью ФФ необходимо выполнить 4 действия:
Создадим подкласс «Developer». Вот как выглядит ФФ «Human».
Добавляем ему метод «code».
Создаем экземпляр Developer.
Перезапись родительского метода
Иногда возникает необходимость перезаписать родительский метод внутри подкласса. Это можно сделать следующим образом:
Тот же процесс с использованием ФФ.
Наследование против композиции
Разговор о наследовании редко обходится без упоминания композиции. Эксперты вроде Eric Elliot считают, что всегда, когда это возможно, следует использовать композицию.
Что же такое композиция?
Понимание композиции
По сути, композиция — это объединение нескольких вещей в одну. Наиболее распространенным и самым простым способом объединения объектов является использование Object.assign.
Композицию легче всего объяснить на примере. Допустим, у нас имеется два подкласса, Developer и Designer. Дизайнеры умеют разрабатывать дизайн, а разработчики — писать код. Оба наследуют от класса «Human».
Теперь предположим, что мы хотим создать третий подкласс. Этот подкласс должен быть смесью дизайнера и разработчика — он должен уметь как разрабатывать дизайн, так и писать код. Назовем его DesignerDeveloper (или, если угодно, DeveloperDesigner).
Как нам его создать?
Мы не может одновременно расширить классы «Designer» и «Developer». Это невозможно, поскольку мы не можем решить, какие свойства должны быть первыми. Это называется проблемой ромба (ромбовидным наследованием).
Проблема ромба может быть решена с помощью Object.assign, если мы отдадим одному объекту приоритет над другим. Однако, в JavaScript не поддерживается множественное наследование.
Здесь нам пригодится композиция.
Данный подход утверждает следующее: вместо создания подкласса «DesignerDeveloper», создайте объект, содержащий навыки, которые можно включать в тот или иной подкласс по необходимости.
Реализация этого подхода приводит к следующему.
Нам больше не нужен класс «Human», ведь мы можем создать три разных класса с помощью указанного объекта.
Вот код для DesignerDeveloper.
Мы можем сделать тоже самое для Designer и Developer.
Вы заметили, что мы создаем методы в экземпляре? Это лишь один из возможных вариантов. Мы также можем поместить методы в прототип, но я нахожу это лишним (при таком подходе кажется, что мы вернулись к конструкторам).
Используйте тот подход, который считаете самым подходящим. Результат будет одинаковым.
Композиция с помощью ФФ
Композиция с помощью ФФ заключается в добавлении распределенных методов в возвращаемый объект.
Наследование и композиция
Никто не говорил, что мы не можем использовать наследование и композицию одновременно.
Возвращаясь к примеру с Designer, Developer и DesignerDeveloper, нельзя не отметить, что они также являются людьми. Поэтому они могут расширять класс «Human».
Вот пример наследование и композиции с использованием синтаксиса классов.
А вот тоже самое с использованием ФФ.
Подклассы в реальном мире
Несмотря на то, что многие эксперты утверждают, что композиция по сравнению с подклассами является более гибкой (и поэтому более полезной), подклассы нельзя сбрасывать со счетов. Многие вещи, с которыми мы имеем дело, основаны на этой стратегии.
Например: событие «click» является MouseEvent (событием мыши). MouseEvent — это подкласс UIEvent (событие пользовательского интерфейса), который, в свою очередь, является подклассом Event (событие).
Другой пример: HTML Elements (элементы) являются подклассами Nodes (узлов). Поэтому они могут использовать все свойства и методы узлов.
Предварительный вывод относительно наследования
Наследование и композиция могут использоваться как в классах, так и в ФФ. В ФФ композиция выглядит «чище», но это незначительное преимущество перед классами.
Классы против ФФ — Инкапсуляция
По сути, инкапсуляция — это скрытие одной вещи внутри другой, из-за чего внутренняя сущность становится недоступной снаружи.
В JavaScript скрываемыми сущностями являются переменные и функции, которые доступны только в текущем контексте. В данном случае контекст — это тоже самое, что область видимости.
Простая инкапсуляция
Простейшей формой инкапсуляции является блок кода.
Находясь в блоке, можно получить доступ к переменной, объявленной за его пределами.
Обратите внимание, что переменные, объявленные с помощью ключевого слова «var», имеют глобальную или фукнциональную область видимости. Старайтесь не использовать var для объявления переменных.
Инкапсуляция с помощью функции
Функциональная область видимости похожа на блочную. Переменные, объявленные в функции, доступны только внутри нее. Это относится ко всем переменным, даже объявленным с помощью var.
Когда же мы находимся внутри функции, то имеем доступ к переменным, объявленным за ее пределами.
Функции могут возвращать значения, которые могут быть использованы впоследствии за пределами функции.
Замыкание
Замыкание — это продвинутая форма инкапсуляции. Это просто функция внутри другой функции.
Переменные, объявленные в outsideFunction, могут использоваться в insideFunction.
Инкапсуляция и ООП
При создании объектов мы хотим, чтобы одни свойства были открытыми (публичными), а другие закрытыми (частными или приватными).
Рассмотрим пример. Скажем, у нас имеется проект «Car». При создании нового экземпляра мы добавляем ему свойство «fuel» (топливо) со значением 50.
Пользователи могут использовать это свойство для определения количества оставшегося топлива.
Пользователи также могут самостоятельно устанавливать количество топлива.
Давайте добавим условие, согласно которому бак автомобиля вмещает максимум 100 литров топлива. Мы не хотим, чтобы пользователи имели возможность самостоятельно устанавливать количество топлива, потому что они могут сломать машину.
Существует два способа это сделать:
Частные свойства по соглашению
В JavaScript частные переменные и свойства, обычно, обозначаются с помощью нижнего подчеркивания.
Как правило, мы создаем методы для управления частными свойствами.
Для определения и установки количества топлива пользователи должны использовать методы «getFuel» и «setFuel», соответственно.
Но переменная «_fuel» в действительности не является частной. Она доступна извне.
Для ограничения доступа к переменным следует использовать настоящие частные поля.
По-настоящему частные поля
Поля — это термин, объединяющий переменные, свойства и методы.
Частные поля классов
Классы позволяют создавать частные переменные с помощью префикса «#».
К сожалению, данный префикс нельзя использовать в конструкторе.
Частные переменные должны определяться вне конструктора.
В данной случае мы можем инициализировать переменную при определении.
Теперь переменная «#fuel» доступна только внутри класса. Попытка получить к ней доступ за пределами класса приведет к возникновению ошибки.
Для управления переменной нам нужны соответствующие методы.
Лично я предпочитаю использовать для этого геттеры и сеттеры. Я нахожу такой синтаксис более читаемым.
Частные поля ФФ
ФФ создают частные поля автоматически. Нам нужно лишь объявить переменную. Пользователи не смогут получить доступ к этой переменной извне. Это происходит благодаря тому, что переменные имеют блочную (или функциональную) область видимости, т.е. являются инкапсулированными по умолчанию.
Для управления частной переменной «fuel» также используются геттеры и сеттеры.
Вот так. Легко и просто!
Предварительный вывод относительно инкапсуляции
Инкапсуляция с помощью ФФ проще и легче для восприятия. Она основана на области видимости, которая является важной частью JavaScript.
Инкапсуляция с помощью классов предполагает использование префикса «#», что может быть несколько утомительным.
Классы против ФФ — this
this — главный аргумент против использования классов. Почему? Потому что значение this зависит от того, где и как this используется. Поведение this часто сбивает с толку не только новичков, но и опытных разработчиков.
Однако, на самом деле концепция this не так уж и сложна. Всего существует 6 контекстов, в которых может использоваться this. Если вы разбираетесь в этих контекстах, у вас не должно возникать проблем с this.
Названными контекстами являются:
Использование this в классах
При использовании в классе this указывает на создаваемый экземпляр (контекст свойства/метода). Вот почему экземпляр инициализируется в constructor.
Использование this в функциях-конструкторах
При использовании this внутри функции и new для создания экземпляра, this будет указывать на экземпляр.
В отличии от ФК в ФФ this указывает на window (в контексте модуля this вообще имеет значение «undefined»).
Таким образом, в ФФ не следует использовать this. В этом состоит одно из основных отличий между ФФ и ФК.
Использование this в ФФ
Для того, чтобы иметь возможность использовать this в ФФ, необходимо создать контекст свойства/метода.
Несмотря на то, что мы можем использовать this в ФФ, нам это не нужно. Мы можем создать переменную, указывающую на экземпляр. Такая переменная может использоваться вместо this.
human.firstName является более точным, нежели this.firstName, поскольку human явно указывает на экземпляр.
На самом деле нам даже не нужно писать human.firstName. Мы можем ограничиться firstName, поскольку данная переменная имеет лексическую область видимости (это когда значение переменной берется из внешнего окружения).
Рассмотрим более сложный пример.
Сложный пример
Условия таковы: у нас имеется проект «Human» со свойствами «firstName» и «lastName» и методом «sayHello».
Также у нас имеется проект «Developer», наследующий от Human. Разработчики умеют писать код, поэтому у них должен быть метод «code». Кроме того, они должны заявлять о своей принадлежности к касте разработчиков, поэтому нам необходимо перезаписать метод «sayHello».
Реализуем указанную логику с помощью классов и ФФ.
Классы
Создаем проект «Human».
Создаем проект «Developer» с методом «code».
Перезаписываем метод «sayHello».
ФФ (с использованием this)
Создаем проект «Human».
Создаем проект «Developer» с методом «code».
Перезаписываем метод «sayHello».
ФФ (без this)
Поскольку firstName за счет лексической области видимости доступна напрямую мы можем опустить this.
Предварительный вывод относительно this
Простыми словами, классы требуют использования this, а ФФ нет. В данном случае я предпочитаю использовать ФФ, поскольку:
Классы против ФФ — Обработчики событий
Во многих статьях по ООП упускается из виду тот факт, что как фронденд-разработчики мы постоянно имеем дело с обработчиками событий. Именно они обеспечивают взаимодействие с пользователями.
Поскольку обработчики событий изменяют контекст this, работа с ними в классах может быть проблематичной. В тоже время в ФФ таких проблем не возникает.
Однако изменение контекста this не имеет значения, если мы знаем, как с этим справиться. Рассмотрим простой пример.
Создание счетчика
Для создания счетчика воспользуемся полученными знаниями, включая частные переменные.
Наш счетчик будет содержать две вещи:
Вот как может выглядеть разметка:
Создание счетчика с помощью класса
Для облегчения задачи попросим пользователя найти и передать разметку счетчика классу «Counter»:
В классе необходимо получить 2 элемента:
Далее мы инициализируем переменную «count» текстовым содержимым countElement. Указанная переменная должна быть частной.
При нажатии кнопки значение счетчика должно увеличиваться на 1. Реализуем это с помощью метода «increaseCount».
Теперь нам необходимо обновить DOM. Реализуем это с помощью метода «updateCount», вызываемого внутри increaseCount:
Осталось добавить обработчик событий.
Добавление обработчика событий
Добавим обработчик к this.buttonElement. К сожалению, мы не можем использовать increaseCount в качестве функции обратного вызова. Это приведет к ошибке.
Исключение выбрасывается, потому что this указывает на buttonElement (контекст обработчика событий). В этом можно убедиться, если вывести значение this в консоль.
Значение this необходимо изменить таким образом, чтобы оно указывало на экземпляр. Это можно сделать двумя способами:
Добавление обработчика событий с помощью bind
bind возвращает новую функцию. В качестве первого аргумента ему передается объект, на который будет указывать this (к которому this будет привязан).
Это работает, но выглядит это не очень хорошо. К тому же bind — это продвинутая функция, с которой сложно иметь дело новичкам.
Стрелочные функции
Стрелочные функции, помимо прочего, не имеют собственного this. Они заимствуют его из лексического (внешнего) окружения. Поэтому код счетчика может быть переписан следующим образом:
Есть еще более простой способ. Мы можем создать increaseCount в виде стрелочной функции. В этом случае this будет указывать на экземпляр.
Вот полный код примера:
Создание счетчика с помощью ФФ
Начало аналогичное — мы просим пользователя найти и передать разметку счетчика:
Получаем необходимые элементы, которые по умолчанию будут частными:
Инициализируем переменную «count»:
Значение счетчика будет увеличиваться с помощью метода «increaseCount». Вы можете использовать обычную функцию, но я предпочитаю другой подход:
DOM будет обновляться с помощью метода «updateCount», который вызывается внутри increaseCount:
Обратите внимание, что вместо this.updateCount мы используем counter.updateCount.
Добавление обрабочика событий
Мы можем добавить обработчик событий к buttonElement, используя counter.increaseCount в качестве колбэка.
Это будет работать, поскольку мы не используем this, поэтому для нас не имеет значения то обстоятельство, что обработчик меняет контекст this.
Первая особенность this
Вы можете использовать this в ФФ, но только в контексте метода.
В следующем примере при вызове counter.increaseCount будет вызван counter.updateCount, поскольку this указывает на counter:
Однако обработчик событий работать не будет, потому что значение this изменилось. Данная проблема может быть решена с помощью bind, но не с помощью стрелочных функций.
Вторая особенность this
При использовании синтаксиса ФФ, мы не можем создавать методы в виде стрелочных функций, потому что методы создаются в контексте функции, т.е. this будет указывать на window:
Поэтому при использовании ФФ я настоятельно рекомендую избегать использования this.
Вердикт относительно обработчиков событий
Обработчики событий меняют значение this, поэтому использовать this следует крайне осторожно. При использовании классов советую создавать колбэки обработчиков событий в виде стрелочных функций. Тогда вам не придется прибегать к услугам bind.
При использовании ФФ рекомендую вообще обходиться без this.
Заключение
Итак, в данной статье мы рассмотрели четыре способа создания объектов в JavaScript:
Во-вторых, мы увидели, что подклассы легче создавать с помощью классов. Однако, в случае композиции лучше использовать ФФ.
В-третьих, мы резюмировали, что, когда речь идет об инкапсуляции, ФФ имеют преимущество перед классами, поскольку последние требуют использования специального префикса «#», а ФФ делают переменные частными автоматически.
В-четвертых, ФФ позволяют обойтись без использования this в качестве ссылки на экземпляр. В классах приходится прибегать к некоторым хитростям, дабы вернуть this исходный контекст, измененный обработчиком событий.
На этом у меня все. Надеюсь, статья вам понравилась. Благодарю за внимание.