В предыдущих частях рассматривались структуры execution context, объекты Function и указатель this. В третьей части речь пойдет о прототипном наследовании.
Как реализуются классы
В JavaScript нет классов. Там, где в классическом ООП используются классы и объекты в JavaScript применяются объекты с функциями и объекты с данными.
При поиске свойств используется внутреннее поле [[Prototype]] (оно доступно снаружи как __proto__). Если свойство не найдено в объекте, то оно ищется в прототипе.
var a = {
n: 1
};
var b = {
__proto__: a,
m: 2
};
console.log(b.n); // выведет 1
Для того, чтобы кажды раз при создании объекта не требовалось явно указывать __proto__ в JavaScript можно использовать функции в качестве конструкторов с помощью оператора new. При интерпретации ключевого слова function каждый раз создается не только объект Function, но и связанный с ним объект Prototype.
Если вызвать функцию как new User()
, а не просто User()
, то будет создан новый объект. Свойство __proto__ нового объекта будет указывать на prototype функции.
При вызове функции через new ThisBinding контекста будет указывать на созданный объект. Если в function User написать this.name = "Unknown"
, то в новом объекте появится поле name с указанным значением.
"Методы класса" логично размещать в прототипе, в этом случае все "объекты класса" будут иметь к ним доступ.
function User(name) {
this.name = name;
}
User.prototype.getName = function _getName() {
return this.name;
}
var user = new User("John");
console.log(user.getName());
Так как при вызове "методов" используется обращение через точку: user.getName()
, то ThisBinding будет указывать на новый объект и return this.name
вернет значение соответствующее значение name.
Важно различать _proto_ и prototype. Свойство __proto__ ([[Prototype]]) есть у всех объектов и используется для поиска свойств. Свойство prototype есть только у объектов Function и используется при создании объектов через new.
Как реализуется наследование классов
Наследование в JavaScript реализуется с помощью цепочки прототипов.
Допустим, от класса User необходимо отнаследовать класс Employee и добавить еще один "метод". Для этого нужно:
- реализовать функцию Employee, которая будет использована в качестве конструктора,
- добавить в её прототип необходимые функции,
- указать в __proto__ прототипа Employee ссылку на прототип User.
В этом случае если функция не будет найдена в одном прототипе поиск будет продолжен в "родительском прототипе".
Впрочем, длинные цепочки наследования в JavaScript не приветствуются.
Подробнее про прототипное наследование в стандарте:
- 8.12.2 [[GetProperty]] (P)
- 11.2.2 The new Operator
- 13.2 Creating Function Objects
- 13.2.2 [[Construct]]
Заключение
Читайте стандарт, ничего сложного там нет. Главное разобраться в основных понятиях и связях между ними. Рекомендую начать с чего-нибудь понятного, например, с вызова функции. Затем – разобраться со встречающимися в тексте определениями. Рекурсивно повторить.
Также имеет смысл посмотреть на первые релизы стандартов. Во-первых, интересно наблюдать за эволюцией. А во-вторых, некоторые фичи появились позже, поэтому сами стандарты были проще. Например, нет разделения на LexicalEnvironment и VariableEnvironment, которое несколько сбивает с толку.
Если хотя бы один разработчик после прочтения статьи разберется со стандартом я смогу считать свою задачу выполненной.
Автор: alvin777