Статьи

Голый Javascript — прототипы и наследование


В части 9 этой серии «Голый Javascript» я собираюсь поговорить о наследовании в Javascript. Чтобы понять концепцию наследования, он определенно должен понять концепцию функций и замыканий в Javascript. Вы можете проверить предыдущую статью о замыканиях и эту статью о функциях, чтобы освежить основы этих понятий.

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

Давайте начнем с корня проблемы.

функции

Как я уже упоминал несколько раз на протяжении всей серии, функции являются объектами первого класса в Javascript. Основная причина проста. В Javascript все является экземпляром класса Object. Это нечто, взятое из Java. Класс Object обеспечивает реализацию по умолчанию нескольких часто используемых функций, например, функции toString. Теперь, чтобы подумать об этом, поскольку все объекты javascript нуждаются в этой функциональности, они должны каким-то образом наследовать эту функцию от класса объекта.

Есть две важные вещи, которые вы должны помнить при работе с наследованием в JavaScript.

1) Поскольку в JavaScript нет классов, наследование в JavaScript достигается

2) Механизм, с помощью которого два объекта соединяются в иерархии наследования, заключается в свойстве объекта «prototype».

Давайте поговорим о «прототипе».

Когда вы создаете любую функцию в javascript, javascript неявно определяет новое свойство для этого объекта под названием «prototype». Свойство прототипа не является обычным свойством.

Давайте посмотрим на простой пример

   

//Here I have defined a simple constructor function
function Employee(name){
 this.name=name;
}
 
//Set the default age
Employee.prototype.age = 16;
 
var emp1 = new Employee('xyz');
console.log(emp1.age);

Это самый простой пример, демонстрирующий использование свойства prototype.

В первых нескольких строках я определил конструктор простой функции, который позволил бы мне создавать объекты Employee. Мы знаем, что функции являются объектами. И, как я уже говорил ранее, каждый объект получает свое собственное свойство прототипа. Поэтому мы смогли определить переменную с именем ‘age’ в свойстве prototype класса employee и установить его значение равным 16.

В последних нескольких строках обратите внимание, что хотя мы не объявляли никакое свойство с именем ‘age’ в экземпляре Employee внутри функции конструктора Employee мы не получили значение undefined при выводе значения свойства age на консоль. Это произошло потому, что мы определили свойство под названием age
на свойство prototype функции Employee.

Здесь происходит то, что при попытке получить доступ к свойству, отсутствующему в объекте «empl», в прототипе функции конструктора был произведен поиск свойства с тем же именем. Так что в этом случае, когда мы обратились к emp1.age, так как движок JavaScript не смог найти свойство для самого объекта, он попытался найти свойство с именем age в объекте Employee.prototype. Это приводит нас к выводу, что прототипы являются хорошим местом для объявления значений по умолчанию.

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

   

function Employee(name){
 this.name=name;
}
 
//Set the default age
Employee.prototype.age = 16;
 
var emp1 = new Employee('xyz');
var emp2 = new Employee('abc');
console.log(emp1.age);
emp1.age++;
console.log(emp2.age);
console.log(emp1.age);

Когда вы проверите консоль, вы заметите, что, как только вы измените свойство объекта, которое было первоначально определено только для прототипа, объекту станет принадлежать локальная копия свойства. В нашем случае, когда мы писали emp1.age ++, мы обычно ожидаем, что программа сделает это:

emp1.age = emp1.age + 1;

Но это то, что двигатель JS выполнил

emp1.age = Employee.prototype.age + 1;

Это потому, что перед добавлением делает сложение, emp1.age не существует. И именно поэтому в RHS оператора equals emp1.age становится Employee.prototype.age

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

 

function Employee(name){
 this.name=name;
}
 
Employee.prototype.getName=function(){
 return this.name;
}

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

Однако что если вы хотите, чтобы функция или свойство были доступны для всех функций?

Ответ довольно прост. Определите свойство / функцию для Function.prototype. Например.

   
Function.prototype.PI=3.14;
 
var dummyFunction = function (){};
 
console.log(dummyFunction.PI);

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

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

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

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

   
//The doom object is the parent object
//You want to inherit its functions and properties in all
//instances of the MyClass class
var doom = {name:'doom',
   baseMethod:function(){
    console.log('defined on doom');}
   };
 
//Your class whose instances you want to create
function MyClass(){
 //Some properties
}
 
//Set the prototype property to the 'doom' object
MyClass.prototype=doom;
 
//The following invocation wont work because the function is defined on
//the prototype of MyClass. Therefore, MyClass itself
//cannot access the variables of doom directly without
//the prototype property
//MyClass.baseMethod();
 
//This works because instances of the class
//can access the variables defined in the
//prototype directly.
var mc = new MyClass();
mc.baseMethod();

Секрет функциональности прототипа интуитивно прост. Каждый объект в javascript имеет скрытое свойство, называемое __proto__. Соблюдайте 2 подчеркивания до и после слова прото. Когда вы создаете объект в javascript, даже если это пустой объект, значение свойства __proto__ устанавливается равным объекту по умолчанию — объекту Object, который содержит ряд полезных методов, таких как toString. Введите следующие строки в консоли браузера, чтобы проверить это

var obj = {};
console.dir(obj.__proto__);

Вы обнаружите, что __proto__ ссылается на объект по умолчанию с набором функций.

Следуя нашему предыдущему примеру, введите в консоли браузера следующее

console.dir(mc.__proto__);

Вы обнаружите, что он указывает на объект Doom. Основной принцип очень прост. Когда вы создали объект типа MyClass, нечто похожее на это было бы выполнено изнутри.

mc.__proto__=Myclass.prototype;

И так как вы уже установили MyClass.prototype для ссылки на объект doom, вы можете наследовать свойства объекта doom.

Итак, всякий раз, когда вы создаете объект, представьте невидимую ссылку из __proto__ объекта в свойство «prototype» его конструктора. Для обычных объектов конструктор является классом Object, а для ваших собственных пользовательских классов конструктор является функцией конструктора ваших классов. Возможно, вы захотите взглянуть на эту статью, чтобы узнать больше об использовании функций в качестве конструкторов.

Еще одним следствием этой функции является то, что, если в любой момент времени вы хотите, чтобы все объекты вашего класса наследовали другой набор свойств, вы можете просто изменить переменную «prototype» в имени функции на другой объект. Это было бы эквивалентно динамической замене родительского класса во время выполнения, тем самым обогащая все существующие объекты новым набором функций, что является чем-то абсолютно крутым, потому что это не то, что вы можете делать в таких языках, как Java. Более того, если вы хотите изменить родительский класс только одного объекта, вы можете изменить свойство __proto__ этого объекта, чтобы ссылаться на другой объект. Просто, мило и сексуально.

Это, вероятно, все, что я должен был сказать в этом посте. Я надеюсь, что вам, ребята, понравилось.