В этом посте рассматривается, как супер-ссылки работают в JavaScript и как они будут упрощены ECMAScript.next. Чтобы понять этот пост, полезно ознакомиться с наследованием JavaScript. Если нет, обратитесь к [2].
Расширение конструкторов в JavaScript
Давайте посмотрим на следующий код JavaScript, где конструктор Employee расширяет конструктор Person. Расширение выполняется с помощью пользовательской функции inherits () (код которой будет показан позже).
// Super-constructor function Person(name) { this.name = name; } Person.prototype.describe = function() { return "Person called "+this.name; }; // Sub-constructor function Employee(name, title) { Person.call(this, name); this.title = title; } Employee.prototype.describe = function me() { return Person.prototype.describe.call(this)+" ("+this.title+")"; }; inherits(Employee, Person);
Сотрудник используется следующим образом:
> var jane = new Employee("Jane", "CTO"); > jane.describe() 'Person called Jane (CTO)'
Супер ссылки
Чтобы понять, как Employee.prototype.describe вызывает свой супер-метод, мы рассмотрим структуру экземпляра jane:
Джейн является первым участником цепочки прототипов. Его прямым прототипом является Employee.prototype, прототипом которого является Person.prototype. Супер-ссылки (в том числе супер-вызовы) — это встроенная функция (предложенная Алленом Уирфс-Броком) в ECMAScript.next, которая позволяет гораздо более кратко написать description ():
Employee.prototype.describe = function () { return super.describe()+" ("+this.title+")"; };
Чтобы сделать супер-вызов super.describe (), выполняются следующие шаги:
- Определите супер, прототип объекта, в котором находится текущий метод.
- Поиск по описанию: начните с супер, проходите по цепочке прототипов, пока не найдете объект, у которого есть свойство description, верните значение этого свойства.
- Вызовите функцию, которую вы нашли, но оставьте ее как раньше. Обоснование: переопределенная версия метода description (), которая должна быть вызвана, должна иметь возможность доступа к свойствам jane.
Принимая это во внимание, мы видим, что super.describe () правильно реализован
Person.prototype.describe.call(this)
Все шаги выполняются:
- Определить супер:
Person.prototype
- Ищите описание.
Person.prototype.describe
Обратите внимание, что мы также находим описание, если его нет в Person.prototype напрямую, но в одном из его прототипов.
- Выполните метод, но сохраните текущий this:
Person.prototype.describe.call(this)
Объект-прототип имеет конструктор свойства, указывающий на конструктор [3]:
Employee.prototype.constructor === Employee
Это позволяет конструктору Employee обращаться к своему супер-конструктору как к супер-методу:
function Employee(name, title) { super.constructor(name); this.title = title; }
Предостережение: вышеописанное работает только со статическими супер-ссылками (подробности см. Ниже). Обратите внимание, что семантика супер-ссылок зависит только от наличия цепочки прототипов и от способности определять объект, который содержит текущий метод. Не имеет значения, как была создана цепочка прототипов: через конструктор, через образец объекта (прототип как класс [2]) или через Object.create (). Суперреференции также не ограничиваются подтипами: можно также переопределить метод в прототипе с помощью метода в экземпляре, и последний вызовет первый.
Определение супер
Существует два способа определения значения super (шаг № 1 выше):
- Динамические супер-ссылки. Когда вы ищите метод, вы сообщаете ему, в каком объекте вы его нашли, подобно тому, как это передается методу. При разрешении супер-ссылки значение super является прототипом этого объекта. Недостаток этого подхода заключается в том, что он требует затрат времени выполнения для всех методов, а не только для тех, которые создают супер-ссылки. Эти затраты не позволяют использовать динамические супер-ссылки для ECMAScript.next.
- Статические супер-ссылки. Каждый метод, создающий супер-ссылку, имеет свойство, указывающее на объект, содержащий метод. Это свойство может быть установлено одним из двух способов. Во-первых, декларативно: если метод находится внутри литерала объекта, тогда ECMAScript.next автоматически устанавливает свойство. Во-вторых, обязательно: метод Object.defineMethod () позволяет вам добавить метод к объекту и установить свойство одновременно. После этого метод должен иметь доступ только к самому себе, чтобы получить соответствующее значение для super. ECMAScript.next обеспечивает такой доступ, но только внутри, когда создается суперссылка.
Имитация статических супер-ссылок
Чтобы смоделировать супер-ссылки ECMAScript.next в ECMAScript 5, нам нужно сохранить ссылку из метода на содержащий его объект и ссылаться на текущий метод изнутри метода. Первое можно сделать с помощью методаоризмы (). Последнее возможно было возможно через
arguments.callee , но это свойство устарело и является недопустимым в строгом режиме [4]. Альтернативой является выражение с
именованной функцией — вы можете присвоить выражению функции имя. Затем он выглядит как объявление функции, но все еще является выражением, а не выражением:
var fac = function me(x) { if (x <= 0) { return 1 } else { return x * me(x-1) } };
Функция в правой части назначения может ссылаться на себя через меня, независимо от переменной, которой она была назначена. Идентификатор me существует только внутри тела функции:
> (function me() { return me }()); [Function: me] > me ReferenceError: me is not defined
Таким образом, в одной и той же области может быть несколько функций, которые все используют имя me.
var Employee = function me(name, title) { me.super.constructor.call(this, name); this.title = title; } Employee.prototype.describe = function me() { return me.super.describe.call(this)+" ("+this.title+")"; };
наследование () работает следующим образом ( полный исходный код см. в
сущности ):
function inherits(subC, superC) { var subProto = Object.create(superC.prototype); // At the very least, we keep the "constructor" property // At most, we preserve additions that have already been made copyOwnFrom(subProto, subC.prototype); addSuperReferencesTo(subProto); subC.prototype = subProto; }; function addSuperReferencesTo(obj) { Object.getOwnPropertyNames(obj).forEach(function(key) { var value = obj[key]; if (typeof value === "function" && value.name === "me") { value.super = Object.getPrototypeOf(obj); } }); } function copyOwnFrom(target, source) { Object.getOwnPropertyNames(source).forEach(function(propName) { Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName)); }); return target; };
Связанное чтение
- Супер-ссылки инициализатора объектов
- Прототипы как классы — введение в наследование JavaScript
- Что случилось со свойством «конструктор» в JavaScript?
- Строгий режим JavaScript: резюме
Источник: http://www.2ality.com/2011/11/super-references.html