Создать конструктор для создания студентов js

Обновлено: 14.06.2024

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Конструкторы, создание объектов через "new"

Обычный синтаксис <. >позволяет создать только один объект. Но зачастую нам нужно создать множество однотипных объектов, таких как пользователи, элементы меню и т.д.

Это можно сделать при помощи функции-конструктора и оператора "new" .

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  1. Имя функции-конструктора должно начинаться с большой буквы.
  2. Функция-конструктор должна вызываться при помощи оператора "new" .

Когда функция вызывается как new User(. ) , происходит следующее:

  1. Создаётся новый пустой объект, и он присваивается this .
  2. Выполняется код функции. Обычно он модифицирует this , добавляет туда новые свойства.
  3. Возвращается значение this .

Другими словами, вызов new User(. ) делает примерно вот что:

То есть, результат вызова let user = new User("Вася") - это тот же объект, что и:

Теперь, когда нам необходимо будет создать других пользователей, мы можем использовать new User("Маша") , new User("Даша") и т.д. Данная конструкция гораздо удобнее и читабельнее, чем каждый раз создавать литерал объекта. Это и является основной целью конструкторов - удобное повторное создание однотипных объектов.

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

Проверка на вызов в режиме конструктора: new.target

Используя специальное свойство new.target внутри функции, мы можем проверить, вызвана ли функция при помощи оператора new или без него.

В случае, если функция вызвана при помощи new , то в new.target будет сама функция, в противном случае undefined .

Это можно использовать, чтобы отличить обычный вызов от вызова "в режиме конструктора". В частности, вот так можно сделать, чтобы функцию можно было вызывать как с, так и без new :

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

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

Возврат значения из конструктора return

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

Но если return всё же есть, то применяется простое правило:

  • При вызове return с объектом, будет возвращён объект, а не this .
  • При вызове return с примитивным значением, примитивное значение будет отброшено.

Другими словами, return с объектом возвращает объект, в любом другом случае конструктор вернёт this .

В примере ниже return возвращает объект вместо this :

А вот пример с пустым return (или мы могли бы поставить примитив после return , неважно)

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

Создание методов в конструкторе

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

В this мы можем добавлять не только свойства, но и методы.

Например, в примере ниже, new User(name) создаёт объект с данным именем name и методом sayHi :

Для создания сложных объектов есть и более "продвинутый" синтаксис - классы, которые мы разберём позже.

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

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

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

Обычный синтаксис <. >позволяет создать только один объект. Но зачастую нам нужно создать множество однотипных объектов, таких как пользователи, элементы меню и т.д.

Это можно сделать при помощи функции-конструктора и оператора "new" .

Функция-конструктор

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  1. Имя функции-конструктора должно начинаться с большой буквы.
  2. Функция-конструктор должна вызываться при помощи оператора "new" .

Когда функция вызывается как new User(. ) , происходит следующее:

  1. Создаётся новый пустой объект, и он присваивается this .
  2. Выполняется код функции. Обычно он модифицирует this , добавляет туда новые свойства.
  3. Возвращается значение this .

Другими словами, вызов new User(. ) делает примерно вот что:

То есть, результат вызова let user = new User("Вася") – это тот же объект, что и:

Теперь, когда нам необходимо будет создать других пользователей, мы можем использовать new User("Маша") , new User("Даша") и т.д. Данная конструкция гораздо удобнее и читабельнее, чем каждый раз создавать литерал объекта. Это и является основной целью конструкторов – удобное повторное создание однотипных объектов.

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

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

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

Проверка на вызов в режиме конструктора: new.target

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

Используя специальное свойство new.target внутри функции, мы можем проверить, вызвана ли функция при помощи оператора new или без него.

В случае, если функция вызвана при помощи new , то в new.target будет сама функция, в противном случае undefined .

Это можно использовать, чтобы отличить обычный вызов от вызова «в режиме конструктора». В частности, вот так можно сделать, чтобы функцию можно было вызывать как с, так и без new :

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

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

Возврат значения из конструктора return

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

Но если return всё же есть, то применяется простое правило:

  • При вызове return с объектом, будет возвращён объект, а не this .
  • При вызове return с примитивным значением, примитивное значение будет отброшено.

Другими словами, return с объектом возвращает объект, в любом другом случае конструктор вернёт this .

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

  • 1. Не смотря на расхожее мнение «всё в JS является объектами» — это не так, мы выяснили, что из 6 доступных программисту типов данных аж 5 является примитивами и лишь один представляет тип объектов.
  • 2. Про объекты мы узнали, что это такая структура данных, которая содержит в себе пары «ключ-значение». Значением может быть любой из типов данных (и это будет свойство объекта) или функция (и это будет метод объекта).
  • 3. А вот примитивы – это не объекты. Хотя с ними и можно работать как с объектом (и это вызывает заблуждение что примитив – это объект), но…
  • 4. Переменные можно объявить как по простому (литерально) (var a = ‘str’), так и через функцию-конструктор (обёртка)(var a = new String(‘str’)). Во втором случае мы получим уже не примитив, а объект созданный конструктором String(). (что за магический оператор new и что такое функция-конструктор мы узнаем дальше).
  • 5. Узнали, что именно за счёт создания обёртки над примитивом (new String(‘str’)) c ним можно работать как с объектом. Именно эту обёртку создаёт интерпретатор вокруг примитива, когда мы пытаемся работать с ним как с объектом, но после выполнения операции она разрушается (поэтому примитив никогда не сможет запомнить свойство, которое мы ему присвоим a.test = ‘test’- свойство test исчезнет с обёрткой).
  • 6. Узнали, что у объектов есть метод toString() который возвращает строковое представление объекта (для типа number valueOf() – вернёт числовое значение).
  • 7. Поняли, что при выполнении операций конкатенации или математических операциях примитивы могут переопределить свой тип в нужный. Для этого они используют функции-обёртки своих типов, но без оператора new (str = String(str)).(в чём разница и как это работает, поговорим дальше)
  • 8. И наконец, узнали, что typeof берёт значения из жёстко зафиксированной таблицы (вот откуда ещё одно заблуждение, основанное на typeof null //object).
Функции-конструкторы, как объекты, prototype.

Начнём по порядку. Рассмотрим простую на первый взгляд строчку кода.

Самое первое что можно сказать: «Мы объявили функцию с именем A». Совершенно верно. Но здесь есть нюансы.
1. Не забываем что в JS — практически всё есть Объект. Функция, как оказалось не исключение(это даже два объекта связанные ссылкой).
2. Её можно использовать как функцию-конструктор.
В JavaScript нет того, что принято называть классами. Работу классов в JavaScript выполняют функции-конструкторы, которые создают объекты с определенными заданными свойствами.
В общем-то говоря любая объект-функция в JS может быть конструктором( я говорю о пользовательских функциях). Их условно можно поделить на три (DF(Функция декларация), FE(Функция выражение), функции созданные конструктором Function()). У всех этих функций есть свои особенности(по этому они разделены на разные группы), но о них я здесь рассказывать не буду, если кому интересно я отвечу лично или напишу отдельно про них в другой раз. Однако у них есть и одна общая черта, которая позволяет им быть конструкторами — это наличие внутренних свойств [[Construct]] и [[Call]], а также явного свойства prototype(о нем ниже).
Именно внутренний метод [[Construct]] отвечает за выделения памяти под новый объект и его инициализацию. Однако — это не значит что вызов функции приведёт к созданию объекта, конечно нет. Для этого перед вызовом функции нужно поставить оператор new. Именно new запускает метод [[Construct]] и связанные с ним процессы.

3. Так же можно сказать что это функция декларация(DF) и прочее, но остальное пока не важно.

Итак Функция «A» (из первой строчки первого примера) — это функция-конструктор и по совместительству объект. Раз это объект — она может иметь свойства. Так оно и есть. А раз это функций-конструктор, то она имеет свойство prototype. Свойство prototype — это ссылка на объект, который хранит свойства и методы которые перейдут к экземплярам созданным этой функцией-конструктором. Давайте попробуем всё это отобразить графически.


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

мы получим «обычный»( насколько это можно в JS ) объект «а».
В JS уже встроено много функций-конструкторов. Это например Number(), String() и т. д. Давайте отвлечёмся ненадолго от примера и поговорим о встроенных функциях-конструкторах и об Объектах в целом.

Объекты(__proto__).

Из прошлой статьи, мы знаем, что при создании (явно или не явно) объектов одним из встроенных конструкторов Number(), String() или Boolean(), экземпляр получает доступ к некоторым методам характерным данному типу. Например для Number() есть метод toPrecision(). Если посмотреть в консоли на объект созданный конструктором new Number(2), то Вы не обнаружите там этого метода(Вы вообще не обнаружете там методов). Откуда же он берётся? Как раз он и подобные ему методы(к которым должен иметь доступ потомок) и содержатся в prototype-объекте родителя. Но как экземпляр получает к ним доступ? У экземпляра есть свойство __proto__ — это ссылка на prototype-объект родителя. Если при вызове метода, метод не находится в самом экземпляре, происходит переход по ссылке __proto__ в prototype-объект родителя и поиск продолжается там. На самом деле так продолжается и дальше пока не будет встречен null.
Попробуем всё это нарисовать:


Подведя итог можно сказать, что пока всё не сложно: Есть родитель(функция-конструктор), у которой есть ссылка в свойстве prototype на некий объект где хранятся все методы и свойства к которым потомок должен иметь доступ. И есть, собственно, потомок которому при создании через вызов new от родителя передаётся ссылка в свойство __proto__ на тот самый объект с общими свойствами и методами.

Для закрепления попробуем рассмотреть пример:

constructor.

Я всегда брал слово (пустой) в кавычки когда говорил («пустой» prototype). Мол когда мы создаём функцию-конструктор function A()<>, то создаётся свойство prototype с ссылкой на «пустой» prototype-объект. На самом деле нет. В prototype всё же кое-что лежит. Во-первых поскольку как я уже говорил prototype — это «простой» Объект, то там лежит свойство __proto__ с ссылкой на prototype функции-конструктора Object() (именно она создаёт всё «простые», самые элементарные объекты), а во-вторых там лежит свойство constructor. Свойство constructor туда добавляет интерпретатор, когда понимает что создаётся функция-конструктор а не просто объект Для начала давайте дополним наш первый рисунок с учётом этих двух фактов.


Всё что нарисовано серым, нам сейчас особо не нужно — это для более полной картины. Сосредоточимся на свойстве constructor. Как видно из рисунка constructor указывает на саму функцию-конструктор для которой изначально было создано это «хранилище», этот объект. То есть между свойством prototype функции-конструктора и свойством constructor объекта-prototype появляется цикличность — они указывают на объекты друг-друга.
Через свойство constructor (если оно всё ещё указывает на конструктор, а свойство prototype конструктора, в свою очередь, всё ещё указывает на первоначальный прототип) косвенно можно получить ссылку на прототип объекта: a.constructor.prototype.x. А можно полуть ссылку к самой функции-конструктору и её свойствам которые были присвоены не в prototype-объект, а конкретно к ней. Например:

=<> — как функция-конструктор (new Object()).

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

Вроде как всё должно работать. Мы создали функцию-конструктор, задали «общему хранилищу» (prototype(через ссылку)) свойство (x), создали экземпляр, свойство (x) у него есть — всё нормально. Потом мы вообще переопределили свойство родителя prototype, добавив свойства (x) и (y) указали верный constructor. Всё должно работать в «общем хранилище лежит» оба этих свойства, но нет, (y) интерпретатор не находит. WTF.

Что же здесь за магия происходит? Почему мы не видим этих изменений из потомка этого конструктора? Почему потомок не видит y? Ну во-первых мы переопределяем свойство prototype функции-конструктора(B) и оно начинает ссылаться на новый объект (связь с первоначальным объектом prototype разорвана). Во-вторых обычное присвоение переменной объекта, типа: var a = <>, интерпретатором на самом деле выполняется как var a = new Object(). А это значит, что свойство prototype функции-конструктора теперь содержит совершенно новый объект у которого ссылка constructor отсутствует и чтоб не потерять родителя мы самостоятельно дописываем туда свойство constructor и присваиваем ему самого родителя.
А экземпляр сделанный ранее содержит ссылку __proto__ на старый объект prototype где свойства (y) нет. То есть в отличии от Примера1 здесь мы не «добавили в хранилище свойство» и даже не «переписали хранилище заново», мы просто создали новое, разорвав связь с старым, а экземпляр об этом ничего не знает, он всё ещё пользуется старым по своей старой ссылке __proto__. Выглядит это вот так:


Чёрным цветом — это то что не изменилось и после B.prototype = ;
Красным — то что удалилось
Зелёным — то что добавилось

Так же можно добавить немного об instanceof. Как ни странно, но в данном примере b1 будет принадлежать функции-конструктору B, а b — нет. Всё очень просто. Дело в том что instanceof ищет выполнения следующего условия — что бы объект указанный по ссылке __proto__(на любом уровне цепочки)(кружочек с цифрой 1) был равен объекту на который ссылается свойство prototype искомого родителя(кружочек с цифрой 2)(сравните на рисунке чёрный цвет и зелёный). В чёрном цвете это условие уже не выполняется, а в зелёном — выполняется.
В нашем случае у экземпляра (b) эта связь разорвана, так как новое свойство prototype искомого родителя(B) ссылается уже на новый объект, а не как раньше. Зато у экземпляра (b1) с этим как видим всё в порядке.

Вдогонку

По поводу this в теле функции-конструктора и вообще углубляться не буду — об этом в следующей статье. Единственное что скажу, это то, что this при вызове функции как конструктора(через new) будет указывать на создаваемый экземпляр, а при вызове как функции — на глобальный объект.
Давайте разберём на примере:


Как же узнать как вызвали функцию? Через new или нет? Это делается очень просто:

Примерно таким образом реализован механизм приведения типов. При выполнении например 1+'1' интерпретатор воспринимает + как конкатенацию строк и пытается привести число 1 в строку. Это происходит с помощью неявного вызова String(1)(без new). А в конструкторе String написана примерно та же конструкция что у нас выше. То есть если вызов произошел без new просто вернуть строку(неявный вызов метода toString()). Таким образом без создания каких либо объектов происходит преобразование типов.
Так же хочу добавить следующее, что бы добавить свойство к функции(именно к функции а не к prototype) нужно обратится к ней как к объекту. Например

Это свойство будет недоступно потомку, так как оно не лежит в prototype, а потомок имеет доступ только туда. Но как говорится «если сильно хочется то можно». Тут то нам и пригодится свойсто объекта prototype — constructor. Оно как мы помним ссылается на саму функцию(если конечно этого специально не меняли). Тогда чтоб получить переменную val нужно обратится к ней так:

Гибкость 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 — потрясающий язык. Его достаточно легко освоить, а существующие фреймворки позволят без труда использовать его в своих проектах. Но за простотой синтаксиса скрываются целые рифы подводных камней — и очень мощных инструментов. Иногда полезно смотреть, что же там на дне.

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

Объект - это составной тип данных, он объединяет множество значений в единый модуль и позволяет сохранять и извлекать значения по их именам. Самым простым способом создания объекта является использование конструктора 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() мы реализовали возможность превращать объект в строковое значение.

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