Объекты не наследуют прототипированные функции

У меня есть одна функция-конструктор, которая действует как суперкласс:

Bla = function(a){this.a = a;}

Я проецирую его на простой метод:

Bla.prototype.f = function(){console.log("f");

И теперь новый Bla(1).f(); будет записывать "f" в консоли. Но, скажем, мне нужен подкласс, который наследует от Bla:

Bla2 = function(a)
{
    this.base = Bla;
    this.base();
}

x = new Bla2(5);

Теперь, как и ожидалось, x.a дает мне 5. Но x.f есть undefined! Кажется, что Bla2 не наследовал его из класса Bla! Почему это происходит и как его исправить?

Ответ 1

Кажется, что Bla2 не наследовал его из класса Bla!

Right. Вы ничего не сделали, чтобы подключить наследование там, вы только что создали член Bla2, называемый base, который является экземпляром Bla. base не является специальным идентификатором в JavaScript.

Типичный способ настройки наследования в JavaScript выглядит следующим образом:

// The base constructor function
function Base(x) {
    // Base per-instance init
    this.x = x;
}

// An example Base function
Base.prototype.foo = function() {
    console.log("I'm Base#foo, x = " + this.x);
};

// The Derived constructor function
function Derived(x, y) {
    // Normally you need to call `Base` as it may have per-instance
    // initialization it needs to do. You need to do it such that
    // within the call, `this` refers to the current `this`, so you
    // use `Function#call` or `Function#apply` as appropriate.
    Base.call(this, x);

    // Derived per-instance init
    this.y = y;
}

// Make the Derived.prototype be a new object backed up by the
// Base.prototype.    
Derived.prototype = Object.create(Base.prototype);

// Fix up the 'constructor' property
Derived.prototype.constructor = Derived;

// Add any Derived functions
Derived.prototype.bar = function() {
    console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
};

... где Object.create - от ES5, но это одна из вещей, которые можно легко выполнить в основном. (Или вы можете использовать функцию, которая имеет только минимальный минимум, не пытаясь сделать все Object.create, см. Ниже.) И затем вы ее используете:

var d = new Derived(4, 2);
d.foo(); // "I'm Base#foo, x = 4"
d.bar(); // "I'm Derived#bar, x = 4, y = 2"

Живой пример | источник

В более старом коде вы иногда видите Derived.prototype следующим образом:

Derived.prototype = new Base();

... но есть проблема с этим: base может выполнять инициализацию для каждого экземпляра, которая не подходит для полноты Derived для наследования. Это может даже потребовать аргументы (как это делает наш base, что мы будем передавать за x?). Вместо того, чтобы сделать Derived.prototype просто новым объектом, поддерживаемым Base.prototype, мы получаем правильный материал. Затем мы вызываем base из Derived, чтобы получить init для каждого экземпляра.

Вышеприведенное является очень простым и, как вы можете видеть, включает в себя несколько шагов. Он также мало или совсем не делает, чтобы "суперклассы" были легкими и удобными для обслуживания. Вот почему вы видите так много сценариев "наследования", как Prototype Class, Dean Edwards Base2 или (кашель), мой собственный Lineage.


Если вы не можете полагаться на функции ES5 в своей среде и не хотите включать в себя прокладку, которая делает основы Object.create, вы можете просто использовать эту функцию на своем месте:

function simpleCreate(proto) {
    function Ctor() {
    }
    ctor.prototype = proto;
    return new Ctor();
}

Тогда вместо

Derived.prototype = Object.create(Base.prototype);

вы бы сделали:

Derived.prototype = simpleCreate(Base.prototype);

Конечно, вы можете сделать больше, чтобы автоматизировать процесс подключения вверх — который все Lineage в основном делает.


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

Base.prototype.foo = function() {
    // ...
};

... но я не делаю этого в своем реальном коде, потому что Мне нравится помогать моим инструментам мне помочь. Поэтому я склонен использовать шаблон модуля вокруг каждого "класса" (функции конструктора и связанного с ним прототипа) и использовать объявления функций (поскольку я действительно работаю в Интернете, а IE7 и IE8 все еще имеют проблемы с именованными выражениями функций. Поэтому, если я не использовал Lineage, я бы сделал следующее:

// Base
(function(target) {
    // Base constructor
    target.Base = Base;
    function Base(x) {
        // Base per-instance init
        this.x = x;
    }

    // An example Base function
    Base.prototype.foo = Base$foo;
    function Base$foo() {
        console.log("I'm Base#foo, x = " + this.x);
    }
})(window);

// Derived
(function(target, base) {
    // The Derived constructor function
    target.Derived = Derived;
    function Derived(x, y) {
        // Init base
        base.call(this, x);

        // Derived per-instance init
        this.y = y;
    }

    // Make the Derived.prototype be a new object backed up by the
    // Base.prototype.    
    Derived.prototype = Object.create(base.prototype);

    // Fix up the 'constructor' property
    Derived.prototype.constructor = Derived;

    // Add any Derived functions
    Derived.prototype.bar = Derived$bar;
    function Derived$bar() {
        console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
    }
})(window, Base);

... или что-то в этом роде. Живая копия | источник