Чем отличается конструктор от функции

Обновлено: 02.05.2024

Экземпляры функции Cat имеют свойство name и color. Это единственная разница между нормальной и конструкторской функцией?

ОТВЕТЫ

Ответ 1

Функция-конструктор является нормальной функцией.

В чем здесь разница: использование оператора new , который делает контекст ( this ) в функции новым экземпляром, тем самым позволяя ему принимать два свойства и возвращает этот новый экземпляр.

Без оператора new контекст был бы внешним ( window , если ваш код находится в глобальной области в свободном режиме, undefined если в строгом режиме).

То есть, если вы опустите new

функция "работает" (если вы не в строгом режиме), но у вас есть два разных результата:

  • catC undefined , поскольку ваша функция ничего не возвращает
  • name и color теперь являются свойствами внешней области

Вся магия, таким образом, находится в новом операторе:

При выполнении кода new foo (. ) происходит следующее:

Создается новый объект, наследующий от foo.prototype.

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

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

Когда я сказал, что это нормальная функция, я опустил одно: намерение разработчика. Обычно вы определяете функции, которые либо называются конструкторами (т.е. С new ), либо нет. В первом случае вы чаще всего используете аргументы для инициализации полей экземпляра (используя this.name = . ), и вы часто следуете за ним, добавляя функции к прототипу (как и вы), чтобы они стали доступными для всех экземпляров. И чтобы ваше намерение было понятным, обычно можно назвать ваш конструктор, начинающийся с прописной буквы.

Ответ 2

Давайте рассмотрим пример, чтобы понять рождение конструкторов в Javascript. Предположим, вас попросили создать объект сотрудника, и он должен иметь 4 свойства firstName, lastName, пола и назначения. Что ж! Вы сказали, что нет проблем.

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

Кажется, нет проблем вообще. Теперь, если вас спросят, что в общей сложности 100 сотрудников, и вы только что создали 2 из них, обычно вам нужно создать еще 98 объектов сотрудников. Теперь вы не будете создавать объекты, как указано выше, поскольку это кажется утомительным. Попался! Давайте создадим фабричный метод, который будет вызываться любое количество раз, и он будет создавать объекты, а затем возвращать его нам. Да уж! написать один раз и будет использоваться много раз.

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

Итак, что общего в этих видах функций. Это:-

создание пустого объекта

возврат объекта после его заполнения

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

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

Вы должны знать об этом ключевом слове. Оно указывает на текущий объект. Помните, что в функциях конструктора Javascript создает для нас пустой объект, так что это фактически указывает только на этот объект. Функции Javscript Construtor автоматически возвращают объект после его заполнения. Теперь, как сказать Javascript, что функция вызывается в режиме конструктора, это новое ключевое слово, которое сообщает Javascript, что функция рассматривается как функция конструктора. Каждый раз, когда вам нужен объект, используйте ключевое слово new и затем вызывайте функцию, а затем эта функция готовит объект для нас и возвращает его.

Хотя Javascript не основан на классах, вы должны позаботиться об имени функции Constructor. не очень хорошо использовать верблюжий чехол, используйте обычный.

Ответ 3

У Dystroy есть это.

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

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

Ответ 4

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

Итак, var catC = new Cat("Fluffy", "White"); создает новый экземпляр класса конструктора Cat

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

  • 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 нужно обратится к ней так:

Читаю Шилдта и уже 2ой день не могу понять конструкторы. Чем они отличаются от методов? Для чего их необходимо использовать?
Если есть возможность, приведите, пожалуйста, примеры.

DevMan

конструктор – метод, который вызывается автоматически при создании объекта.
Конструктор_(объектно-ориентированное_программирование)

Dywar

Конструктор - специальный метод, который призван создавать экземпляры класса или структуры. У него нет возвращаемого значения (неявный void).
Дефолтный конструктор - конструктор без аргументов.

Метод тоже может создавать новый экземпляр, но его нельзя вызвать через оператор new . ().

Условно:
Конструктор - метод для создания экземпляра.
Метод - это поведение созданных экземпляров.

var a = new Constructor();
a.Method();

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

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

2 ответа 2

Для комментария слишком много, поэтому напишу полноценный ответ.

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

Как пример, если добавить, например, два параметра: мана и магическая атака, то в первом случае придётся написать дополнительно 8 строк кода, а во втором 6. Если же юнитов будет не 2, а например, 200, то в первом случае нужно написать дополнительно 1604 строки, а во втором будет всего 212.

В общем случае, пусть юнитов n, пусть видов комманд k, а характеристик юнита - i. Тогда первый код будет занимать в строках: (2+i)*n+4k, а второй: (2+i)+n+4k. (без учёта самой игры) Очевидно, что второй код будет занимать меньше строк.

всегда ли краткость сестра таланта?! в случае отсутствия конструктора как не забыть инициализировать необходимый параметр?

@Bald56rus Хотя я и пользуюсь краткостью для наглядности картины, но это скорее следствие того, что используется конструктор. Плюсы в конструкторе - большая гибкость кода и возможность автоматически отлавливать какие-то случаи. Для первого случая придётся писать огромные if .

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

Классы, в принципе, используются для того же.

К подразделу 9.1 имеется только одна задача «Перепишите класс», в которой задан класс Clock , написанный с помощью функции-конструктора. Требуется переписать этот класс с помощью синтаксиса, использующего ключевое слово class .

Сама постановка задачи мне нравится. Если в языке есть два способа сделать одно и то же, то неплохо было бы знать, чем эти способы отличаются друг от друга и чем отличаются друг от друга полученные с помощью этих способов результаты. Однако, как оказалось, изложенного в подразделе недостаточно (по крайней мере, мне) для решения этой задачи с налёта. Думаю, подраздел 9.1 учебника требует дальнейшей доработки.

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

0. Исходный код

В этом коде объявляется функция-конструктор Clock , в теле которой объявлены переменная timer и три функции render , this.stop и this.start . Далее с помощью этой функции-конструктора создается объект clock , после чего запускается метод этого объекта clock.start . При запуске этого скрипта в браузерном окружении работы этого скрипта не видно, так как на экран ничего не выводится. Однако, если после запуска скрипта открыть консоль разработчика, то в ней можно увидеть как тикают созданные нами часы: это реализовано бесконечным выводом одной за другой строк с текущим временем. Новые строки в консоль выводятся каждую секунду.

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

Единственное, что меня сначала ввело в ступор, это передача параметра template в функцию-конструктор в первой строке кода:

Итак, начнем переписывать класс. Понятно, что нужно применить ключевое слово class , создать метод constructor , в который нужно передать параметр template , а методы this.stop и this.start легко переделать в методы класса:

Но что делать с переменной timer и вспомогательной функцией render ? Почему бы не оставить их, как есть, ведь класс — это разновидность функции в языке JavaScript (так сказано в подразделе 9.1 учебника), а в теле функции можно объявлять локальные переменные и локальные функции. Однако, как выяснилось, в теле класса такие объявления запрещены. Например, попробуем запустить в браузере следующие куски кода:

Мой браузер даже не может отобразить такой скрипт в консоли разработчика и выдает ошибку «Uncaught SyntaxError: Unexpected identifier».

После этого я решил, что вспомогательную функцию render тоже можно сделать методом класса. А переменную timer можно сделать свойством класса. Тогда получается следующее:

Синтаксически данный вариант кода класса уже не вызовет ошибки и объект clock будет успешно создан. Но при запуске метода clock.start выясняется, что внутри этого метода не видно метода render , что приводит к ошибке.

На самом деле, как я понял, обращение к методам и свойствам класса из других методов этого же класса должно происходить через переменную this . В подразделе 9.1 об этом прямо не сказано, но показано в примерах. В языке C++, который я изучал ранее, в методах класса можно спокойно обращаться к другим методам и свойствам класса напрямую. Ладно, переписываем класс с учетом этого момента (объявление свойства timer класса теперь можно убрать, так как оно должно автоматически добавиться к объекту при первом к нему обращении из метода clock.start ):

Теперь метод this.render класса запустился из первой строки метода clock.start , но внутри метода this.render не видно параметра template . Очевидно, что значение параметра template должно быть доступно всем методам класса, а методы класса, как я понимаю, видят только свойства класса. Следовательно, для хранения значения параметра template нужно создать свойство класса, назовем его this.template . Так же, как и свойство timer класса, объявлять свойство this.template класса отдельно не будем, а создадим его в объекте при первом к нему обращении. Где же сделать это первое обращение? Очевидно, что в методе constructor . Переписываем код:

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

При обращении к this. render происходит потеря this . Об этом подробно рассказывалось в подразделе 6.10 «Привязка контекста к функции». Один из способов избавиться от этой ошибки — использование стрелочной функции в качестве обертки. Переписываем код:

5. Окончательное решение

Теперь всё работает. Задание выполнено.

Что интересно, использование класса, объявленного с помощью функции-конструктора, и использование класса, объявленного с помощью ключевого слова class , в итоге ничем друг от друга не отличаются. Сравните последние две строки исходного кода и последние две строки окончательного решения. Они идентичны:

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