Что такое конструктор в js. JavaScript Конструкторы объектов. Добавление свойства к конструктору объекта
Гибкость Javascript позволяет создавать объекты множеством способов. Но как это нередко случается, разнообразие таит в себе множество подводных камней. Из этой статьи Вы узнаете о том, как разглядеть и обогнуть эти опасные рифы.
Основы основ
Нелишним будет напомнить, что из себя представляют объекты в Javascript и как их можно создавать. Объект в Javascript - это всего лишь хэш-таблица ключей и значений. Если значения представляют собой базовые типы или другие объекты, их называют свойствами , если же это функции, их называют методами объекта.Объекты, созданные пользователем, можно изменить в любой точке выполнения скрипта. Многие свойства встроенных в язык объектов также изменяемы. То есть можно просто создать пустой объект и добавлять к нему свойства и методы по мере необходимости. Проще всего это сделать с помощью литеральной нотации
:
//создаем пустой объект
var cat = {};
//добавляем свойство:
cat.name = "Garfield";
//или метод:
cat.getName = function() {
return cat.name;
};
Другим способом создания объекта является использование функций-конструкторов:
// литеральная нотация:
var cat = {name: "Garfield"};
// конструктор-функция:
var cat = new Object();
cat.name = "Garfield";
Очевидно, что литеральная нотация короче конструктора. Есть и философская причина предпочитать литеральную нотацию конструкторам: она подчеркивает, что объект - это всего лишь изменяемый хэш, а не нечто, создаваемое по шаблону, заданному классом.
Кроме того, использование конструктора Object вынуждает интерпретатор проверять, не переопределена ли эта функция в локальном контексте.
Подводный камень конструктора Object
Причин использовать конструктор Object нет. Но все мы знаем, что иногда приходится использовать какой-то старый код, и в этом случае полезно знать об одной особенности этого конструктора. Он принимает аргумент, и в зависимости от его типа может поручить создание объекта другому встроенному в язык конструктору; в итоге мы получим не тот объект, что ожидали://пустой объект var o = new Object(); o.constructor === Object; // true var o = new Object(1); o.constructor === Number; //true var o = new Object("string"); o.constructor === String; //true //мы не предполагали, что созданный объект будет иметь этот метод: typeof o.substring; // "function"
Такое поведение конструктора Object может привести к неожиданным результатам, если мы передаем в него значение, неизвестное на этапе выполнения.
Мораль очевидна: не используйте конструктор Object .
Собственные конструкторы
Мы можем определять собственные конструкторы. Использование их выглядит так:var cat = new Cat("Garfield"); cat.say(); // "I am Garfield"
Синтаксис похож на конструктор Java, но в Javascript конструктор является обычной функцией и поэтому определяется так:
var Cat = function(name) { this.name = name; this.say = function() { return "I am" + this.name; } }
При вызове этого конструктора с оператором new внутри функции происходит следующее:
- создается пустой объект, на который указывает переменная this ; этот объект наследует прототип функции;
- к объекту, хранимому в this , добавляются свойства и методы;
- объект, хранимый в this , неявно возвращается в конце функции (т.к. мы ничего не возвращали явно).
Cat.prototype.say = function() { return "I am " + this.name; };
Кроме того, не совсем корректно утверждать, что объект this , неявно создаваемый в конструкторе, пуст: он наследуется от прототипа Cat , однако рассмотрение прототипов выходит за рамки этой статьи.
Что возвращает конструктор
При использовании оператора new , конструктор всегда возвращает объект. По умолчанию, это объект, на который ссылается this . Конструктор возвращает this неявно, однако мы можем явно вернуть любой другой объект, например:var Cat = function() { this.name = "I am Garfield"; var that = {}; that.name = "I am Cat-woman"; return that; }; var cat = new Cat(); cat.name // "I am Cat-woman"
Таким образом, мы можем вернуть из конструктора любое значение, но лишь при условии, что это объект. Если мы попытаемся вернуть, скажем, строку или false , это не приведет к ошибке, но оператор возврата будет проигнорирован, и конструктор вернет this .
Коварный new
Конструкторы - это всего лишь функции, вызываемые с оператором new . Что случится, если забыть этот оператор? Интерпретатор не выдаст предупреждений, но это приведет к логическим ошибкам. Переменная this будет указывать не на объект, унаследованный от прототипа конструктора, а на глобальный объект (window в случае браузера):function Cat() { this.name = "Garfield"; } // новый объект var cat = new Cat(); typeof cat; // "object" cat.name; // "Garfield" //забываем new: var cat = Cat(); typeof cat; // "undefined" window.name; // "Garfield"
В строгом режиме стандарта ECMAScript 5 this в этом случае не будет указывать на глобальный объект. Посмотрим, как можно избежать этой ошибки, если ECMAScript 5 недоступен.
Соглашения об именовании функций
Самым простым способом является неукоснительное соблюдение соглашений об именовании функций: начинаем обычные функции со строчной буквы (myFunction()), а функции-конструкторы - с заглавной (MyConstruction()). К сожалению, такой способ почти ни от чего не спасает.Явный возврат объекта
Конструкторы могут возвращать любые объекты. Программисты могут воспользоваться этим:function Cat() { var that = {}; that.name = "Garfield"; return that; }
Имя переменной that выбрано произвольно, это не часть спецификации. С тем же успехом мы можем назвать возвращаемый объект me или self или как Вам заблагорассудится.
Для простых объектов, вроде создаваемого в примере, мы можем вообще обойтись без дополнительных переменных, используя литеральную нотацию:
function Cat() {
return {
name: "Garfield"
};
}
Такой конструктор будет всегда возвращать объект, независимо от того, как его вызывать:
var first = new Cat(),
second = Cat();
first.name; // "Garfield"
second.name; // "Garfield"
У этого способа есть серьезный недостаток: объект не наследует прототип конструктора, то есть методы и свойства, добавленные непосредственно к Cat , будут недоступны создаваемым с его помощью объектам.
Самовызывающий конструктор
Для решения этой проблемы достаточно проверить, является ли this в теле конструктора экземляром этого самого конструктора, и если нет, вызывать себя снова, но на этот раз с оператором new . Звучит страшно, но на деле просто:function Cat() { if (!(this instanceof Cat)) { return new Cat(); } this.name = "Garfield"; } Cat.prototype.meow = "Meow!"; var first = new Cat(), second = Cat(); first.name; // "Garfield" second.name; // "Garfield" first.meow; // "Meow!" second.meow; // "Meow!"
Если наш конструктор будет в дальнейшем переименован, придется править его тело. Избежать этого можно проверкой arguments.callee вместо имени конструктора:
if (!(this instanceof arguments.callee)) { return new arguments.callee(); }
Здесь мы воспользовались тем, что внутри каждой функции создается объект arguments , содержащий все параметры, передаваемые функции в момент вызова. Свойство callee этого объекта указывает на вызываемую функцию. Но и здесь нужно проявить осторожность: строгий режим ECMAScript 5 вызывает исключение TypeError при обращении к этому свойству, поэтому стоит заранее сделать выбор между удобством рефакторинга и светлым завтра.
Вместо заключения
Javascript - потрясающий язык. Его достаточно легко освоить, а существующие фреймворки позволят без труда использовать его в своих проектах. Но за простотой синтаксиса скрываются целые рифы подводных камней - и очень мощных инструментов. Иногда полезно смотреть, что же там на дне.Пример
function Person(first, last, age, eye) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eye;
}
Считается хорошей практикой называть функции конструктора с первой буквой верхнего регистра.
Типы объектов (чертежи) (классы)
Примеры из предыдущих глав ограничены. Они только создают одиночные объекты.
Иногда мы нуждаемся в "план " для создания многих объектов одного и того же "типа".
Способ создания "типа объекта", заключается в использовании функции конструктора объектов .
В приведенном выше примере функция Person () является функцией конструктора объектов.
Объекты одного типа создаются путем вызова функции конструктора с помощью ключевого слова New :
var myFather = new Person("John", "Doe", 50, "blue");
var myMother = new Person("Sally", "Rally", 48, "green");
Ключевое слово this
В JavaScript, вещь называется это объект, который "владеет" код.
Значение this , при использовании в объекте, является сам объект.
В функции конструктора это значение не имеет. Он заменяет новый объект. Значение этого параметра станет новым объектом при создании нового объекта.
Добавление свойства к объекту
Добавить новое свойство к существующему объекту очень просто:
Пример
myFather.nationality = "English";
Свойство будет добавлено в моего отца. Не моя мать. (не для других объектов Person).
Добавление метода к объекту
Добавить новый метод к существующему объекту очень просто:
Пример
myFather.name = function () {
return this.firstName + " " + this.lastName;
};
Метод будет добавлен в моего отца. Не моя мать. (не для других объектов Person).
Добавление свойства в конструктор
Нельзя добавить новое свойство в конструктор объектов так же, как добавляется новое свойство к существующему объекту:
Пример
Person.nationality = "English";
Чтобы добавить новое свойство в конструктор, необходимо добавить его в функцию конструктора:
Пример
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
this.nationality = "English";
}
Таким образом, свойства объекта могут иметь значения по умолчанию.
Добавление метода в конструктор
Функция конструктора также может определять методы:
Пример
function Person(first, last, age, eyecolor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
this.name = function() {return this.firstName + " " + this.lastName;};
}
Нельзя добавить новый метод в конструктор объектов так же, как добавить новый метод к существующему объекту.
Добавление методов к объекту должно выполняться внутри функции конструктора:
Пример
function Person(firstName, lastName, age, eyeColor) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.eyeColor = eyeColor;
this.changeName = function (name) {
this.lastName = name;
};
}
Функция чанженаме () присваивает значение Name свойству "Фамилия" пользователя.
Объект Math () отсутствует в списке. Математика является глобальным объектом. Ключевое слово New не может использоваться в математике.
Ты знала?
Как вы можете видеть выше, JavaScript имеет версии объектов примитивных типов данных String, number и Boolean. Но нет причин создавать сложные объекты. Примитивные значения намного быстрее.
Используйте литералы объекта {} вместо нового объекта ().
Используйте строковые литералы "" вместо новой строки ().
Используйте числовые литералы 12345 вместо нового числа ().
Используйте логические литералы true/false вместо New Boolean ().
Используйте литералы массива вместо нового массива ().
Используйте шаблонные литералы /()/ вместо нового регулярного выражения ().
Используйте выражения функции () {} вместо новой функции ().
Пример
var x1 = {}; // new object
var x2 = ""; // new primitive string
var x3 = 0; // new primitive number
var x4 = false; // new primitive boolean
var x5 = ; // new array object
var x6 = /()/ // new regexp object
var x7 = function(){}; // new function object
JavaScript предоставляет разработчикам возможность создавать объекты и работать с ними. Для этого существуют следующие приёмы:
- Оператор new
- Литеральная нотация
- Конструкторы объектов
- Ассоциативные массивы
Используем оператор new
Это, наверное, самый легкий способ создания объекта. Вы просто создаете имя объекта и приравниваете его к новому объекту Javascript.
//Создаем наш объект var MyObject = new Object(); //Переменные MyObject.id = 5; //Число MyObject.name = "Sample"; //Строка //Функции MyObject.getName = function() { return this.name; }
Минус данного способа заключается в том, что вы можете работать только с одним вновь созданным объектом.
//Используем наш объект alert(MyObject.getName());
Литеральная нотация
Литеральная нотация является несколько непривычным способом определения новых объектов, но достаточно легким для понимания. Литеральная нотация работает с версии Javascript 1.3.
//Создаем наш объект с использованием литеральной нотации MyObject = { id: 1, name: "Sample", boolval: true, getName: function() { return this.name; } }
Как видите, это довольно просто.
Объект = { идентификатор: значение, ... }
И пример использования:
Alert(MyObject.getName());
Конструкторы объектов
Конструкторы объектов - это мощное средство для создания объектов, которые можно использовать неоднократно. Конструктор объекта - это, по сути, обычная функция Javascript, которой так же можно передавать различные параметры.
Function MyObject(id, name) { }
Только что мы написали конструтор. С помощью него мы и будем создавать наш объект.
Var MyFirstObjectInstance = new MyObject(5,"Sample"); var MySecondObjectInstace = new MyObject(12,"Othe Sample");
Таким образом мы создали различные экземпляры объекта. Теперь мы можем работать отдельно с каждым экземпляром объекта MyObject, не боясь того, что, изменяя свойства одного экземпляра, мы затронем свойства другого экземпляра.
Как и в ООП, у MyObject могут быть методы и различные свойства. Свойствам можно присвоить значения по умолчанию, либо значения, переданные пользователем в конструкторе объекта.
Function MyObject(id, name) { //Значения переданные пользователем this._id = id; this._name = name; //Значение по умолчанию this.defaultvalue = "MyDefaultValue"; }
Аналогичным образом мы можем создавать и функции.
Function MyObject(id,name) { this._id = id; this._name = name; this.defaultvalue = "MyDefaultValue"; //Получение текущего значения this.getDefaultValue = function() { return this.defaultvalue; } //Установка нового значения this.setDefaultValue = function(newvalue) { this.defaultvalue = newvalue; } //Произвольная функция this.sum = function(a, b) { return (a+b); } }
Ассоциативные массивы
Подобный метод будет полезен упорядочивания большого числа однотипных объектов.
Var MyObject = new Number(); MyObject["id"] = 5; MyObject["name"] = "SampleName";
Для обхода таких объектов можно использовать такой цикл:
For (MyElement in MyObject) { //Код обхода //В MyElement - идентификатор записи //В MyObject - содержание записи }
По материалу подготовлена небольшая схема.
Вы можете её посмотреть в форматах.
function User(name, age) { this.name = name; this.age = age; this.sayHi = function () { console.log("Hello!"); } } let user = new User("Homer", 33); console.log(user.name);У каждого объекта, созданного с помощью конструктора, есть неявно добавленное свойство constructor , содержащее ссылку на конструктор, с помощью которого был создан объект:
Console.log(user.constructor === User); // true
Свойство constructor предназначено для идентификации типа объекта. Тоже самое позволяет сделать оператор instanceof :
Console.log(user instanceof User); // true
При использовании конструктора для создания объекта, для каждого созданного объекта создаётся своя копия свойств и методов.
Шаблон "Прототип"
Когда создаётся функция, в неё по умолчанию добавляется свойство prototype . Значением свойства prototype является объект, содержащий общие свойства и методы, которые доступны всем объектам, созданным с помощью этого конструктора.
Вместо того, чтобы указывать свойства и методы в конструкторе, их можно указать непосредственно прототипу:
Function User() {} User.prototype.name = "Homer"; User.prototype.age = "33"; User.prototype.sayHi = function () { console.log("Hello!"); }; let user = new User(); console.log(user.name);
По умолчанию все прототипы имеют только свойство constructor , содержащее ссылку на функцию, к которой оно относится:
Function foo() {} console.log(foo.prototype.constructor === foo); // true
Все остальные методы и свойства наследуются от типа Object . Когда с помощью конструктора создаётся новый объект, в нём определяется внутренний указатель (ссылка) на прототип конструктора. Доступ к этому указателю можно получить с помощью метода Object.getPrototypeOf() :
Function foo() {} let obj = new foo(); console.log(Object.getPrototypeOf(obj) === foo.prototype); // true
При чтении свойства объекта начинается его поиск. Сначала свойство с указанным именем ищется в самом объекте. Если оно обнаруживается у объекта, возвращается значение свойства, если свойства с таким именем нет у объекта, поиск продолжается в прототипе. В случае обнаружения свойства у прототипа возвращается его значение. Так прототипы обеспечивают совместное использование свойств и методов у объектов.
Если в объект добавить свойство с именем, как у свойства прототипа, то у объекта будет создано собственное свойство, в этом случае при следующем чтении свойства будет использоваться свойство объекта, а не прототипа.
Чтобы писать меньше кода, можно перезаписать прототип литералом объекта, содержащим все свойства и методы:
Function User() {} User.prototype = { name: "Homer", age: 33, sayHi: function () { console.log("Hello!"); } }; // Восстановление свойства constructor Object.defineProperty(User.prototype, "constructor", { enumerable: false, value: User });
В этом примере свойству User.prototype присваивается новый объект, созданный с помощью литерала. Он полностью заменяет собою объект, предлагаемый по умолчанию. Результат получается таким же, как и в предыдущем примере, за одним исключением: свойство constructor больше не указывает на функцию User . Явное добавление свойства constructor со значением User в литерал объекта решает эту проблему. Свойство constructor у встроенных объектов по умолчанию неперечислимо, поэтому для его добавления использовался метод Object.defineProperty() .
Объединение шаблонов "конструктор" и "прототип"
С помощью конструктора определяют собственные свойства, а с помощью прототипа – общие методы и свойства:
Function User(name, age) { this.name = name; this.age = age; } User.prototype.sayHi = function () { console.log("Hello!"); }
Function Person(first, last, age, eye) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eye; }
Считается хорошей практикой программирования, чтобы название функции конструкторов начиналось с большой буквы.
Объектные типы (Шаблоны) (Классы)
Примеры из предыдущих глав значительно ограничены. В них создается только одиночные объекты.
Тем не менее, иногда требуется иметь некий "шаблон", по которому можно было бы создавать множество объектов одного и того же "типа".
Для создания "объектного типа" и используется функция конструктора объекта .
В приведенном в начале этой главы примере функция Person() является функцией конструктора объекта.
Объекты одного и того же типа создаются при помощи вызова функции конструктора с ключевым словом new :
Var myFather = new Person("John", "Doe", 50, "blue"); var myMother = new Person("Sally", "Rally", 48, "green");
Ключевое слово this
В JavaScript ключевое слово this обозначает объект, которому "принадлежит" данный код.
Значением ключевого слова this , когда оно используется в объекте, является сам объект.
В функции конструктора у ключевого слова this нет значения. Это "подстановка" для нового объекта. Когда будет создан новый объект, тогда значением ключевого слова this и станет этот новый объект.
Обратите внимание, что this это не переменная, а ключевое слово. Вы не можете изменять его значение.
Добавление свойства к объекту
Добавить новое свойство к существующему объекту очень просто:
MyFather.nationality = "English";
Свойство будет добавлено к объекту myFather, но не к объекту myMother. (Или какому-либо другому объекту типа person).
Добавление метода к объекту
Добавить новый метод к существующему объекту очень просто:
MyFather.name = function () { return this.firstName + " " + this.lastName; };
Метод будет добавлен к объекту myFather, но не к объекту myMother. (Или какому-либо другому объекту типа person).
Добавление свойства к конструктору объекта
Нельзя добавлять новое свойство к конструктору объекта тем же способом, как это делается в случае с существующим объектом.
Чтобы добавить новое свойство к конструктору, вы должны добавить его в функцию конструктора:
Function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; this.nationality = "English"; }
При этом свойствам объекта можно устанавливать значения по умолчанию.
Добавление метода к конструктору объекта
Функция конструктора также может определять методы:
Function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; this.name = function() {return this.firstName + " " + this.lastName;}; }
Нельзя добавлять новые методы к конструктору объекта тем же способом, как это делается в случае с существующим объектом. Добавление методов к объекту должно происходить внутри функции конструктора:
Function Person(firstName, lastName, age, eyeColor) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.eyeColor = eyeColor; this.changeName = function (name) { this.lastName = name; }; }
Функция changeName() присваивает значение параметра name свойству lastName объекта person:
MyMother.changeName("Doe");
JavaScript знает, о каком объекте идет речь, "подставляя" в ключевое слово this объект myMother .
Встроенные конструкторы JavaScript
В JavaScript есть встроенные конструкторы для собственных объектов:
Var x1 = new Object(); // Новый объект Object var x2 = new String(); // Новый объект String var x3 = new Number(); // Новый объект Number var x4 = new Boolean(); // Новый объект Boolean var x5 = new Array(); // Новый объект Array var x6 = new RegExp(); // Новый объект RegExp var x7 = new Function(); // Новый объект Function var x8 = new Date(); // Новый объект Date
В этом списке нет объекта Math(), так как это глобальный объект. Ключевое слово new нельзя использовать с объектом Math.
А вы знали?
Как видно из приведенного выше кода, в JavaScript есть объектные версии примитивных типов данных String, Number и Boolean. Однако нет никаких причин создавать для этих типов комплексные объекты. Примитивные значения работают быстрее.
Таким образом:
- используйте объектные литералы { } вместо new Object().
- используйте строковые литералы "" вместо new String().
- используйте числовые литералы 12345 вместо new Number().
- используйте логические литералы true / false вместо new Boolean().
- используйте литералы-массивы вместо new Array().
- используйте литералы-шаблоны /()/ вместо new RexExp().
- используйте выражения функций () {} вместо new Function().