JavaScript: constructor и prototype
Шаблон "Конструктор"
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!"); }