Статьи

Пристальный взгляд на супер-ссылки в JavaScript и ECMAScript.next

В этом посте рассматривается, как супер-ссылки работают в 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 (), выполняются следующие шаги:

  1. Определите супер, прототип объекта, в котором находится текущий метод.
  2. Поиск по описанию: начните с супер, проходите по цепочке прототипов, пока не найдете объект, у которого есть свойство description, верните значение этого свойства.
  3. Вызовите функцию, которую вы нашли, но оставьте ее как раньше. Обоснование: переопределенная версия метода description (), которая должна быть вызвана, должна иметь возможность доступа к свойствам jane.

Принимая это во внимание, мы видим, что super.describe () правильно реализован

    Person.prototype.describe.call(this)

Все шаги выполняются:

  1. Определить супер:
        Person.prototype
    
  2. Ищите описание.
        Person.prototype.describe
    

    Обратите внимание, что мы также находим описание, если его нет в Person.prototype напрямую, но в одном из его прототипов.

  3. Выполните метод, но сохраните текущий 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;
    };

Связанное чтение

  1. Супер-ссылки инициализатора объектов
  2. Прототипы как классы — введение в наследование JavaScript
  3. Что случилось со свойством «конструктор» в JavaScript?
  4. Строгий режим JavaScript: резюме

 

 

Источник: http://www.2ality.com/2011/11/super-references.html