Для создания объекта используются конструкторы и деструкторы js

Обновлено: 05.05.2024

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

Объект - это составной тип данных, он объединяет множество значений в единый модуль и позволяет сохранять и извлекать значения по их именам. Самым простым способом создания объекта является использование конструктора Object().

  • new - ключевое слово, которое создает новый пустой объект
  • Object() - функция, в которую передается новый пустой объект; она конфигурирует объект и добавляет в него стандартные свойства и методы ; настроенный готовый объект возвращается в переменную, которой он присвоен

В JS мы можем создать строковое значение 2мя способами:

  • используя литерал
  • используя конструктор

var simpleStr = 'My String'; // переменная со строковым значением (литерал)

var objectStr = new String('some String object'); // объект типа String (конструктор)

Добавлять новые свойства и методы можно только к объектам, созданным через оператор new

Добавить свойство к переменной невозможно

Т.о. переменную, в отличие от объекта, невозможено расширять. Каждый раз, когда мы обращаемся к примитиву через переменную, под капотом JS временно превращает его значение в объект для того, чтобы можно было взаимодействовать с определенными нативными методами, характерными для этого примитива (например, toString() для строк). Но этот объект не сохраняется в памяти, поэтому и невозможно обратиться ни к одному его кастомному свойству/методу, если такое было добавлено.

Конструктор Function() позволяет динамически создавать и компилировать анонимные функции. Он принимает неограниченное кол-во параметров, последний параметр всегда является телом создаваемой функции. Параметры, которые передаются в начале списка аргументов Function(), являются входными параметрами для генерируемой функции.

Использовать этот конструктор для создания функций не рекомендуется, т.к. он снижает производительность, а также, являясь экивалентом функции eval(), он открывает потенциальную брешь в безопасности: код, который попадет в тело функции, обязательно выполнится и если этот код функция получает извне от пользователя, то передав вредоносный код, можно получить нежелательные последствия.

Создание пользовательских конструкторов, ключевое слово this

В других языках программирования можно создавать свои типы данных (классы), однако в JS такой возможности нет, можно оперировать лишь теми типами данных, которые заложены в язык: примитивами (Строка, Число, Булев тип), тривиальными типами null и undefined, сложными типами (Объект, Массив) и специальным типом Функция.

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

Как мы отличаем, что объекты относятся к какому-то типу данных (классу)? Если 2 объекта имеют одинаковый набор свойств и методов, значит эти объекты относятся к одному типу (классу).

Функция-конструктор для создания объектов Point

Создание 3х экземпляров класса Point В большинстве ЯП мы бы сказали, что эти переменные типа Point. Но в случае JS эти переменные типа Object, в которых есть по 2 свойства. Поскольку набор свойств этих объектов одинаков, можно условно говорить о том, что они принадлежат к одному типу (классу).

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

Свойство функции-конструктора (аналог статического свойства в других ЯП) Point.maxPointCount = 100;

Метод функции-конструктора (аналог статического метода в других ЯП)

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

Создание экземпляров и работы с их свойствами и методами

Работа со свойствами и методами конструктора

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

Как только мы определяем функцию, вместе с этой функцией появляется связанный с ней объект Prototype. Вместе с созданием функции и ее прототипа, у них появляются ссылки: каждая функция содержит в себе скрытое системное свойство prototype, а каждый прототп содежрит в себе скрытое системное свойство constructor. Свойство prototype связывает функцию-конструктор с прототипом, а свойство constructor наоборот связывает прототип с функцией-конструктором.

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

Метод прототипа Rectangle будет доступен каждому экземпляру, но хранится будет в прототипе, а не в экземпляре. Это сэкономит нам ресурсы при последующем создании новых объектов с помощью конструктора Rectangle. Что происходит в коде ниже: мы берем функцию конструктор Rectangle() и с помощью ее свойства prototype мы получаем доступ к прототипу функции, т.е. к пустому объекту. Далее мы говорим этому пустому объекту, что в нем должен появиться метод getArea().

Штампуем объекты с помощью конструктора

Когда мы обращаемся к объекту rect и вызываем на нем метод getArea() , интерпретатор идет в объект rect и пытается найти в нем метод getArea() . Но в объекте rect этого метода нет. Тогда интерпретатор автоматически берет ссылку __proto__ этого объекта, по этой ссылке поднимается к прототипу и ищет метод getArea() уже в этом прототипе. Если метод getArea() найден, метод запустится. Если нет, то в случае наличия прототипов у прототипа, поиск будет продолжаться вверх по иерархии прототитпов.

Свойство прототипа будет доступно всем экземплярам

В этом случае мы добавляем свойство name к объекту, а не прототипу. В прототипе не произойдет никаких изменений. При вызове rect1.name интерпретатор найдет свойство name в объекте и не станет подниматься дальше к прототипу. rect1.name = 'first rectangle';

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

Создаем пользовательский конструктор

Создаем объект с помощью пользовательского конструктора var MyCtorObject = new MyCtor(12, 3);

Функция для вывода содержимого свойства constructor аргумента

showCtor(MyArray, 'MyArray'); Конструктор объекта MyArray - это function Array() < [native code] >(это нативная функция и она для нас закрыта)

showCtor(MyDate, 'MyDate'); Конструктор объекта MyDate - это function Date()

showCtor(MyString, 'MyString'); Конструктор объекта MyString - это function String()

showCtor(MyObj, 'MyObj'); Конструктор объекта MyObj - это function Object()

showCtor(MyFunc, 'MyFunc'); Конструктор объекта MyFunc - это function Function()

showCtor(MyCtorObject, 'MyCtorObject'); Конструктор объекта MyCtorObject - это function MyCtor(x, y)

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

Конструктор Object и его методы

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

Если рассмотреть вышеприведенный код в отладчике, то мы увидим, что у объекта rect3 есть свойства width и height, а также системное свойство-ссылка proto. Перейдя по ссылке proto мы увидим свойства и методы конструктора Rectangle3, который является прототипом для rect3, а именно: 2 пользовательских метода getArea() и toString(), системное свойство constructor, а также снова увидим ссылку proto. Перейдя по этой ссылке, мы попадем в Object, который является прототипом конструктора Rectangle3. В Object мы увидим ряд системных методов, таких как hasOwnProperty, valueOf, toString и т.д.

По сути мы не используем наследование, мы используем прототипы. Если в других ЯП наследование подразумевает копирование какой-то функциональности из родительского класса, то в случае с JS мы просто указываем, что один объект связан с другим объектом посредством прототипов, т.е. у одного объекта есть прототип в виде другого объекта. Object является прототипом для всех объектов, которые существуют в JS.

Т.о., если на объекте rect3 мы вызовем свойство width, интерпретатор обратиться к объету rect3 и найдет это свойство. Если мы вызовем метод getArea(), то интерпретатор обратится к объекту rect3, не найдет этот метод, по ссылке proto перейдет к прототипу объекта rect3 и найдет метод в нем. Если мы вызовем метод hasOwnProperty, интерпретатор двинется по цепочке прототипов и, не найдя этот метод в объекте и его прототипе (конструкторе), по ссылке proto обратиться к прототипу прототипа объекта rect3 (т.е. к Object) и, найдя там метод hasOwnProperty, вернет результат (ту функциональность, которая заложена в этом методе).

Итак, методы Object, о которых обязательно нужно знать.

С помощью него мы можем превратить объект в строковое представление. Мы можем заместить системный метод пользовательским, присвоив любую функцию прототипу конструктора Rectangle3 (Rectangle3.prototype.toString = function()<>). Если вызвать rect4.toString() , то интерпретатор начнет поиск этого метода в цепочке прототипов, и найдя первый встретившийся метод toString(), выполнит его. Поскольку первым встретившимся методом toString() будет разработанный нами пользовательский метод в прототипе конструктора, то именно он и выполнится. До системного метода toString() в Object интерпретатор уже не дойдет. Т.о. мы можем замещать любые системные методы.

Если вывести в документ rect3 и rect4.toString(), то результат будет одинаковым: вывод функциональности метода toString() Т.о. с помощью пользовательского метода toString() мы реализовали возможность превращать объект в строковое значение.

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

Основы основ

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

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

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

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

Кроме того, использование конструктора Object вынуждает интерпретатор проверять, не переопределена ли эта функция в локальном контексте.

Подводный камень конструктора Object

Причин использовать конструктор Object нет. Но все мы знаем, что иногда приходится использовать какой-то старый код, и в этом случае полезно знать об одной особенности этого конструктора. Он принимает аргумент, и в зависимости от его типа может поручить создание объекта другому встроенному в язык конструктору; в итоге мы получим не тот объект, что ожидали:

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

Мораль очевидна: не используйте конструктор Object .

Собственные конструкторы

Мы можем определять собственные конструкторы. Использование их выглядит так:

Синтаксис похож на конструктор Java, но в Javascript конструктор является обычной функцией и поэтому определяется так:

  • создается пустой объект, на который указывает переменная this ; этот объект наследует прототип функции;
  • к объекту, хранимому в this , добавляются свойства и методы;
  • объект, хранимый в this , неявно возвращается в конце функции (т.к. мы ничего не возвращали явно).

Кроме того, не совсем корректно утверждать, что объект this , неявно создаваемый в конструкторе, пуст: он наследуется от прототипа Cat , однако рассмотрение прототипов выходит за рамки этой статьи.

Что возвращает конструктор

При использовании оператора new , конструктор всегда возвращает объект. По умолчанию, это объект, на который ссылается this . Конструктор возвращает this неявно, однако мы можем явно вернуть любой другой объект, например:

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

Коварный new

Конструкторы — это всего лишь функции, вызываемые с оператором new . Что случится, если забыть этот оператор? Интерпретатор не выдаст предупреждений, но это приведет к логическим ошибкам. Переменная this будет указывать не на объект, унаследованный от прототипа конструктора, а на глобальный объект ( window в случае браузера):

В строгом режиме стандарта ECMAScript 5 this в этом случае не будет указывать на глобальный объект. Посмотрим, как можно избежать этой ошибки, если ECMAScript 5 недоступен.

Соглашения об именовании функций

Самым простым способом является неукоснительное соблюдение соглашений об именовании функций: начинаем обычные функции со строчной буквы ( myFunction() ), а функции-конструкторы — с заглавной ( MyConstruction() ). К сожалению, такой способ почти ни от чего не спасает.

Явный возврат объекта

Конструкторы могут возвращать любые объекты. Программисты могут воспользоваться этим:

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

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

Такой конструктор будет всегда возвращать объект, независимо от того, как его вызывать:

У этого способа есть серьезный недостаток: объект не наследует прототип конструктора, то есть методы и свойства, добавленные непосредственно к Cat , будут недоступны создаваемым с его помощью объектам.

Самовызывающий конструктор

Для решения этой проблемы достаточно проверить, является ли this в теле конструктора экземляром этого самого конструктора, и если нет, вызывать себя снова, но на этот раз с оператором new . Звучит страшно, но на деле просто:

Здесь мы воспользовались тем, что внутри каждой функции создается объект arguments , содержащий все параметры, передаваемые функции в момент вызова. Свойство callee этого объекта указывает на вызываемую функцию. Но и здесь нужно проявить осторожность: строгий режим ECMAScript 5 вызывает исключение TypeError при обращении к этому свойству, поэтому стоит заранее сделать выбор между удобством рефакторинга и светлым завтра.

Вместо заключения


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

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

Обзор объектов

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

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

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

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

Как и все переменные JavaScript, имя объекта (которое тоже может быть переменной) и имя свойства являются чувствительными к регистру. Вы можете определить свойство указав его значение. Например, давайте создадим объект myCar и определим его свойства make , model , и year следующим образом:

Неопределённые свойства объекта являются undefined (а не null ).

Свойства объектов JavaScript также могут быть доступны или заданы с использованием скобочной записи (более подробно см. property accessors). Объекты иногда называются ассоциативными массивами, поскольку каждое свойство связано со строковым значением, которое можно использовать для доступа к нему. Так, например, вы можете получить доступ к свойствам объекта myCar следующим образом:

Имена свойств объекта могут быть строками JavaScript, или тем, что может быть сконвертировано в строку, включая пустую строку. Как бы то ни было, доступ к любому имени свойства, которое содержит невалидный JavaScript идентификатор (например, имя свойства содержит в себе пробел и тире или начинается с цифры), может быть получен с использованием квадратных скобок. Этот способ записи также полезен, когда имена свойств должны быть динамически определены (когда имя свойства не определено до момента исполнения). Примеры далее:

Обратите внимание, что все ключи с квадратными скобками преобразуются в тип String, поскольку объекты в JavaScript могут иметь в качестве ключа только тип String. Например, в приведённом выше коде, когда ключ obj добавляется в myObj , JavaScript вызывает метод obj.toString () и использует эту результирующую строку в качестве нового ключа.

Вы также можете получить доступ к свойствам, используя значение строки, которое хранится в переменной:

Вы можете пользоваться квадратными скобками в конструкции for. in чтобы выполнить итерацию всех свойств объекта, для которых она разрешена. Чтобы показать как это работает, следующая функция показывает все свойства объекта, когда вы передаёте в неё сам объект и его имя как аргументы функции:

Так что если вызвать эту функцию вот так showProps(myCar, "myCar"), то получим результат:

Перечисление всех свойств объекта

Начиная с ECMAScript 5, есть три способа перечислить все свойства объекта (получить их список):

  • циклы for. in (en-US)
    Этот метод перебирает все перечисляемые свойства объекта и его цепочку прототипов
    Этот метод возвращает массив со всеми собственными (те, что в цепочке прототипов, не войдут в массив) именами перечисляемых свойств объекта o .
    Этот метод возвращает массив содержащий все имена своих свойств (перечисляемых и неперечисляемых) объекта o .

До ECMAScript 5 не было встроенного способа перечислить все свойства объекта. Однако это можно сделать с помощью следующей функции:

Это может быть полезно для обнаружения скрытых (hidden) свойств (свойства в цепочке прототипа, которые недоступны через объект, в случае, если другое свойство имеет такое же имя в предыдущем звене из цепочки прототипа). Перечислить доступные свойства можно, если удалить дубликаты из массива.

Создание новых объектов

JavaScript содержит набор встроенных объектов. Также вы можете создавать свои объекты. Начиная с JavaScript 1.2, вы можете создавать объект с помощью инициализатора объекта. Другой способ — создать функцию-конструктор и сделать экземпляр объекта с помощью этой функции и оператора new .

Использование инициализаторов объекта

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

здесь obj — это имя нового объекта, каждое property_i — это идентификатор (имя, число или строковый литерал), и каждый value_i — это значения, назначенные property_i . Имя obj и ссылка объекта на него необязательна; если далее вам не надо будет ссылаться на данный объект, то вам не обязательно назначать объект переменной. (Обратите внимание, что вам потребуется обернуть литерал объекта в скобки, если объект находится в месте, где ожидается инструкция, чтобы интерпретатор не перепутал его с блоком.)

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

Следующая инструкция создаёт объект и назначает его переменной x , когда выражение cond истинно.

Следующий пример создаёт объект myHonda с тремя свойствами. Заметьте, что свойство engine — это также объект со своими собственными свойствами.

Вы также можете использовать инициализатор объекта для создания массивов. Смотрите array literals.

До JavaScript 1.1 не было возможности пользоваться инициализаторами объекта. Единственный способ создавать объекты — это пользоваться функциями-конструкторами или функциями других объектов, предназначенных для этой цели. Смотрите Using a constructor function.

Использование функции конструктора

Другой способ создать объект в два шага описан ниже:

  1. Определите тип объекта, написав функцию-конструктор. Название такой функции, как правило, начинается с заглавной буквы.
  2. Создайте экземпляр объекта с помощью ключевого слова new .

Чтобы определить тип объекта создайте функцию, которая определяет тип объекта, его имя, свойства и методы. Например предположим, что вы хотите создать тип объекта для описания машин. Вы хотите, чтобы объект этого типа назывался car , и вы хотите, чтобы у него были свойства make, model, и year. Чтобы сделать это, напишите следующую функцию:

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

Теперь вы можете создать объект, называемый mycar , следующим образом:

Эта инструкция создаёт объект типа Car со ссылкой mycar и присваивает определённые значения его свойствам. Значением mycar.make станет строка "Eagle", mycar.year — это целое число 1993, и так далее.

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

Объект может иметь свойство, которое будет другим объектом. Например, далее определяется объект типа Person следующим образом:

и затем создать два новых экземпляра объектов Person как показано далее:

Затем, вы можете переписать определение car и включить в него свойство owner , которому назначить объект person следующим образом:

Затем, чтобы создать экземпляры новых объектов, выполните следующие инструкции:

Заметьте, что вместо того, чтобы передавать строку, литерал или целое число при создании новых объектов, в выражениях выше передаются объекты rand и ken как аргумент функции. Теперь, если вам нужно узнать имя владельца car2, это можно сделать следующим образом:

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

добавляет свойство color к car1, и устанавливает его значение равным "black." Как бы там ни было, это не влияет на любые другие объекты. Чтобы добавить новое свойство всем объектам одного типа, вы должны добавить свойство в определение типа объекта car .

Использование метода Object.create

Объекты также можно создавать с помощью метода Object.create . Этот метод очень удобен, так как позволяет вам указывать объект прототип для нового вашего объекта без определения функции конструктора.

Наследование

Все объекты в JavaScript наследуются как минимум от другого объекта. Объект, от которого произошло наследование называется прототипом, и унаследованные свойства могут быть найдены в объекте prototype конструктора.

Индексы свойств объекта

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

Это ограничение налагается когда вы создаёте объект и его свойства с помощью функции конструктора (как мы это делали ранее с типом Car ) и когда вы определяете индивидуальные свойства явно (например, myCar.color = "red" ). Если вы изначально определили свойство объекта через индекс, например myCar[5] = "25 mpg" , то впоследствии сослаться на это свойство можно только так myCar[5] .

Исключение из правил — объекты, отображаемые из HTML, например массив forms . Вы всегда можете сослаться на объекты в этих массивах или используя их индекс (который основывается на порядке появления в HTML документе), или по их именам (если таковые были определены). Например, если второй html-тег в документе имеет значение атрибута NAME равное "myForm", вы можете сослаться на эту форму вот так: document.forms[1] или document.forms["myForm"] или document.myForm .

Определение свойств для типа объекта

Вы можете добавить свойство к ранее определённому типу объекта воспользовавшись специальным свойством prototype . Через prototype создаётся свойство, единое для всех объектов данного типа, а не одного экземпляра этого типа объекта. Следующий код демонстрирует это, добавляя свойство color ко всем объектам типа car , а затем присваивая значение свойству color объекта car1 .

Смотрите свойство prototype (en-US) объекта Function в Справочнике JavaScript для получения деталей.

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

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

где objectName — это существующий объект, methodname — это имя, которое вы присваиваете методу, и function_name — это имя самой функции.

Затем вы можете вызвать метод в контексте объекта следующим образом:

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

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

Вы можете сделать эту функцию методом car, добавив инструкцию

к определению объекта. Таким образом, полное определение car примет следующий вид:

Теперь вы можете вызвать метод displayCar для каждого из объектов как показано ниже:

Использование this для ссылки на объект

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

Вы можете вызвать эту функцию validate в каждом элементе формы, в обработчике события onchange . Используйте this для доступа к этому элементу, как это сделано ниже:

В общем случае, this ссылается на объект, вызвавший метод.

Через this можно обратиться и к родительской форме элемента, воспользовавшись свойством form . В следующем примере форма myForm содержит элемент ввода Text и кнопку button1 . Когда пользователь нажимает кнопку, значению объекта Text назначается имя формы. Обработчик событий кнопки onclick пользуется this.form чтобы сослаться на текущую форму, myForm .

Определение геттеров и сеттеров

Геттер (от англ. get - получить) — это метод, который получает значение определённого свойства. Сеттер (от англ. setприсвоить) — это метод, который присваивает значение определённому свойству объекта. Вы можете определить геттеры и сеттеры для любых из встроенных или определённых вами объектов, которые поддерживают добавление новых свойств. Синтаксис определения геттеров и сеттеров использует литеральный синтаксис объектов.

Ниже проиллюстрировано, как могут работать геттеры и сеттеры в объекте определённом пользователем:

Объект o получит следующие свойства:

  • o.a — число
  • o.b — геттер, который возвращает o.a плюс 1
  • o.c — сеттер, который присваивает значение o.a половине значения которое передано в o.c

Следует особо отметить, что имена функций, указанные в литеральной форме "[gs]et propertyName() < >" не будут в действительности являться именами геттера и сеттера. Чтобы задать в качестве геттера и сеттера функции с явно определёнными именами, используйте метод Object.defineProperty (или его устаревший аналог Object.prototype.__defineGetter__ ).

В коде ниже показано, как с помощью геттера и сеттера можно расширить прототип объекта Date и добавить ему свойство year, которое будет работать у всех экземпляров класса Date . Этот код использует существующие методы класса Date - getFullYear и setFullYear для работы геттера и сеттера.

Определение геттера и сеттера для свойства year :

Использование свойства year заданного геттером и сеттером:

В принципе, геттеры и сеттеры могут быть либо:

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

Когда определение геттера и сеттера использует инициализаторы объекта, всё что вам нужно, это дополнить геттер префиксом get а сеттер префиксом set . При этом, метод геттера не должен ожидать каких либо параметров, в то время как метод сеттера принимает один единственный параметр (новое значение для присвоения свойству). Например:

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

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

Удаление свойств

Вы можете удалить свойство используя оператор delete . Следующий код показывает как удалить свойство.

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

Смотри delete чтобы получить дополнительную информацию.

Сравнение объектов

В JavaScript объекты имеют ссылочный тип. Два отдельных объекта никогда не будут равными, даже если они имеют равный набор свойств. Только сравнение двух ссылок на один и тот же объект вернёт true.

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

Необходимые знания: Элементарная компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Структурные элементы).
Цель: Понимать основу теории перед началом объектно-ориентированного программирования, как это связано с JavaScript ("большинство сущностей являются объектами"), и как начать работу с объектами JavaScript.

Основы объектов

Объект — это совокупность связанных данных и/или функциональных возможностей. Обычно состоят из нескольких переменных и функций, которые называются свойства и методы, если они находятся внутри объектов. Разберём пример, чтобы показать, как они выглядят.

Как и во многих случаях в JavaScript, создание объекта часто начинается с определения и инициализации переменной. Попробуйте ввести следующий код JavaScript в ваш файл, а затем сохраните файл и обновите страницу браузера:

Если вы введёте person в текстовое JS консоль и нажмёте клавишу Enter, должен получиться следующий результат:

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

После сохранения и обновления, попробуйте ввести что-нибудь следующее в консоль JavaScript браузера:

Как видите, наш объект содержит некоторые данные, а также несколько методов. У нас же с помощью простого синтаксиса есть доступ к ним.

Примечание: Если у вас возникли проблемы с применением файла в работе, попробуйте сравнить ваш код с нашей версией — см. oojs-finished.html (также see it running live). Одна из распространённых ошибок, когда вы начинаете с объектами ставить запятую в конце последнего члена — это приводит к ошибке.

Итак что здесь происходит? Объект состоит из нескольких элементов, каждый из которых имеет своё название (пример name и age выше), и значение (пример ['Bob', 'Smith'] и 32 ). Каждая пара название/значение должны быть разделены запятой, а название и значение в каждом случае разделяются двоеточием. Синтаксис всегда следует этому образцу:

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

Такие объекты называются литералами объекта (object literal) — мы буквально вписали все содержимое объекта для его создания. Этот способ сильно отличается от объектов реализованных классами, которые мы рассмотрим позже.

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

Точечная запись (Dot notation)

Выше вы получили доступ к свойствам и методам используя точечную запись (dot notation). Имя объекта (person) действует как пространство имён (namespace) — оно должно быть введено первым, для того чтобы получить доступ ко всему что заключено (encapsulated) внутри объекта. Далее вы пишете точку, затем элемент, к которому хотите получить доступ — это может быть имя простого свойства, элемент массива, или вызов одного из методов объекта, например:

Внутренние пространства имён (Sub-namespaces)

Можно даже сделать значением элемента объекта другой объект. Например, попробуйте изменить значение свойства name с такого

Здесь мы фактически создаём внутреннее пространство имён (sub-namespace). Это звучит сложно, но на самом деле это не так — для доступа к этим элементам вам нужно сделать один дополнительный шаг с ещё одной точкой. Попробуйте в консоли браузера следующее:

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

Иначе ваши методы больше не будут работать.

Скобочная запись (Bracket notation)

Существует другой способ получить свойства объекта — использовать скобочную запись (bracket notation). Вместо написания этого кода:

Вы можете использовать следующий

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

Запись элементов в объект

До сих пор мы рассматривали только возврат (или получение) элементов объекта — вы так же можете установить (обновить) значение элемента объекта просто объявив элемент, который вы хотите установить (используя точечную или скобочную запись), например:

Попробуйте ввести эти строки, а затем снова верните элементы, чтобы увидеть, как они изменились

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

Теперь вы можете проверить ваши новые элементы:

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

Затем мы можем добавить имя и значение этого нового элемента в объект person таким образом:

Чтобы проверить это, попробуйте добавить следующие строки в свой код, после закрывающей скобки объекта person :

Теперь попробуйте сохранить и обновить, затем введите следующее в консоль браузера:

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

Что такое "this"?

Возможно, вы заметили что-то странное в наших методах. Посмотрите на этот пример:

Вы, вероятно, задаётесь вопросом, что такое "this"? Ключевое слово this , ссылается на текущий объект, внутри которого пишется код — поэтому в нашем случае this равен объекту person . Но почему просто не написать person ? Как вы увидите в статье Object-oriented JavaScript for beginners (Объектно-ориентированный JavaScript для начинающих), когда мы начинаем создавать конструкторы и т.д., this очень полезен — он всегда будет гарантировать, что используется верное значение, когда контекст элемента изменяется (например, два разных экземпляра объекта person могут иметь разные имена, но захотят использовать своё собственное имя при приветствии.

Давайте проиллюстрируем, что мы имеем в виду, с упрощённой парой объектов person :

В этом случае, person1.greeting() выведет "Hi! I'm Chris.". person2.greeting() , с другой стороны, выведет "Hi! I'm Brian.", хотя код метода одинаковый в обоих случаях. Как мы сказали ранее, this равен объекту, внутри которого находится код — это не очень полезно, когда вы пишите литералы объектов вручную, но оно действительно помогает, когда вы генерируете объекты динамически (например используя конструкторы). Это станет понятнее чуть позже.

Все это время вы использовали объекты

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

Поэтому, когда вы использовали строковые методы, такие как:

Вы использовали метод доступный в экземпляре класса String . Каждый раз создавая строку в вашем коде, эта строка автоматически создаётся как экземпляр String , и поэтому имеет несколько общих методов/свойств, доступных на нем.

Когда вы обращались к объектной модели документа (DOM), используя следующие строки:

Вы использовали методы доступные в экземпляре класса Document . Для каждой загруженной веб-страницы создаётся экземпляр Document , называемый document , который представляет всю структуру страницы, её содержимое и другие функции, такие как URL-адрес. Опять же, это означает, что он имеет несколько общих методов/свойств, доступных на нем.

То же самое относится и к любому другому встроенному объекту/API, который вы использовали — Array , Math , и т. д.

Обратите внимание, что встроенные объекты/API не всегда создают экземпляры объектов автоматически. Как пример, Notifications API — который позволяет новым браузерам запускать системные уведомления, требует, чтобы вы создавали новый экземпляр объекта с помощью конструктора для каждого уведомления, которое вы хотите запустить. Попробуйте ввести следующее в консоль JavaScript:

Опять же, мы рассмотрим конструкторы в следующей статье.

Резюме

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

В следующей статье мы начнём рассматривать теорию объектно-ориентированного программирования (ООП) и как эти техники могут быть использованы в JavaScript

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

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

Языки, основанные на классах против Прототипно-ориентированных языков

Основанные на классах объектно-ориентированные языки программирования, такие как Java и C++, строятся на концепции двух отдельных сущностей: класс и экземпляр.

  • Класс определяет все свойства (учитывая методы и все поля в Java, или свойства в C++), которые характеризуют группу объектов. Класс это абстрактная вещь, а не какой-либо конкретный член множества объектов, которые он описывает. Например, класс Employee может описывать множество всех сотрудников.
  • Экземпляр, это воплощение класса в виде конкретного объекта. Например, Victoria может быть экземпляром класса Employee , представляющий собой конкретного сотрудника. Экземпляр класса имеет ровно столько свойств, сколько и родительский класс (не больше и не меньше).

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

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

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

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

Подклассы и наследование

В языках, основанных на классах, вы создаёте иерархию классов через объявление классов. В объявлении класса вы можете указать, что новый класс является подклассом уже существующего класса. При этом, подкласс унаследует все свойства суперкласса и в дополнение сможет добавить свои свойства или переопределить унаследованные. Например, предположим, что класс Employee включает два свойства: name и dept , а класс Manager является подклассом Employee и добавляет свойство reports . В этом случае, экземпляр класса Manager будет иметь три свойства: name , dept , и reports .

JavaScript реализует наследование, позволяя связать прототипный объект с любой функцией-конструктором. Итак, вы можете создать объект точь-в-точь, как в примере Employee — Manager , но используя несколько иную технику. Для начала нужно определить функцию-конструктор Employee , которая определяет свойства name и dept . Затем, определяем функцию-конструктор Manager , в которой в свою очередь, будет явно вызываться конструктор Employee и определяться новое свойство reports . Наконец, присваиваем новый экземпляр Employee , в качестве prototype для функции-конструктора Manager . Теперь, когда вы создадите нового Manager , он унаследует свойства name и dept из объекта Employee .

Добавление и удаление свойств

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

Подытожим различия

Следующая таблица даёт краткий обзор некоторых из этих различий. А оставшаяся часть этой главы описывает детали использования конструкторов и прототипов JavaScript для создания иерархии объектов и сравнивает это с тем, как вы могли бы сделать это в Java.

Построение иерархии объектов происходит путём присвоения объекта в качестве прототипа функции-конструктора.

Пример Сотрудник

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


Рисунок 8.1: Простая иерархия объектов

Этот пример использует следующие объекты:

  • Employee имеет свойство name (значение которого по умолчанию пустая строка) и dept (значение которого по умолчанию "general").
  • Manager основывается на Employee . Он добавляет свойство reports (значение которого по умолчанию пустой массив, предназначенный для хранения массива объектов Employee ).
  • WorkerBee так же основан на Employee . Он добавляет свойство projects (значение которого по умолчанию пустой массив, предназначенный для хранения строк).
  • SalesPerson основан на WorkerBee . Он добавляет свойство quota (значение которого по умолчанию 100). Он также переопределяет свойство dept , со значением "sales", указывая, что все продавцы находятся в одном отделе.
  • Engineer основан на WorkerBee . Он добавляет свойство machine (значение которого по умолчанию пустая строка), а так же определяет свойство dept значением "engineering".

Создание иерархии

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

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

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

Следующие определения Employee для языков Java и JavaScript довольно похожи. Единственное отличие состоит в том, что вам необходимо указать тип каждого свойства в Java, но не в JavaScript (потому что Java является строго типизированным языком, в то время как JavaScript слабо типизированный).

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

Классы Engineer и SalesPerson создают объекты, которые происходят от WorkerBee и, следовательно, от Employee . Объект этих типов имеет свойства всех объектов, расположенных над ним в иерархии. Также, эти классы переопределяют наследуемое значение свойства dept своими значениями, характерными для этих объектов.

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

экземпляр функции конструктора. Так, в этом примере, вы можете неформально сказать, что jane является экземпляром Engineer . Аналогично, хотя термины parent, child, ancestor и descendant (родитель, ребёнок, предок и потомок) не имеют формальных значений в JavaScript, вы можете использовать их неформально для ссылки на объекты выше или ниже в цепочке прототипов.

figure8.3.jpg


Рисунок 8.3: Создание объектов с простыми определениями

Свойства объекта

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

Наследование свойств

Предположим, вы создаёте объект mark в качестве WorkerBee (как показано на Рисунок 8.3) с помощью следующего выражения:

Когда JavaScript видит оператор new , он создаёт новый обобщённый объект и неявно устанавливает значение внутреннего свойства [[Prototype]] в WorkerkBee.prototype , затем передаёт этот новый объект в качестве значения this в функцию-конструктор WorkerBee . Внутреннее свойство [[Prototype]] определяет цепочку прототипов, используемых для получения значений свойств. После того, как эти свойства установлены, JavaScript возвращает новый объект, а оператор присваивания устанавливает переменную mark для этого объекта.

Этот процесс не задаёт значения свойств (локальных значений), которые унаследованы по цепочке прототипов, объекта mark напрямую. Когда вы запрашиваете значение свойства, JavaScript сначала проверяет, существует ли это значение в данном объекте. Если так и есть, тогда возвращается это значение. Если значение не найдено в самом объекте, JavaScript проверяет цепочку прототипов (используя внутреннее свойство [[Prorotype]]). Если объект в цепочке прототипов имеет значение для искомого свойства, это значение возвращается. Если такое свойство не найдено, JavaScript сообщает, что объект не обладает свойством. Таким образом, объект mark содержит следующие свойства и значения:

Значения для свойств name и dept объекту mark присваиваются из конструктора Employee . Также из конструктора WorkerBee присваивается локальное значение для свойства projects . Это даёт вам наследование свойств и их значений в JavaScript. Некоторые детали этого процесса обсуждаются в Тонкости наследования свойств.

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

Добавление свойств

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

Теперь объект mark имеет свойство bonus , но никакой другой WorkerBee не имеет этого свойства.

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

Как только JavaScript выполняет это выражение, объект mark также получает свойство specialty со значением "none" . Следующий рисунок показывает результат добавления этого свойства в прототип Employee и последующее переопределение его в прототипе Engineer .



Рисунок 8.4: Добавление свойств

Более гибкие конструкторы

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



Рисунок 8.5: Определение свойств в конструкторе, вариант 1

Следующая таблица показывает определения для этих объектов в JavaScript и Java.

Читайте также: