Наследование JavaScript: когда конструктор имеет аргументы - PullRequest
46 голосов
/ 23 сентября 2011

Используя чистый JavaScript для наследования, я обычно так и делаю:

function A() {}
A.prototype.run = function () {};

function B() {}
B.prototype = new A;
B.prototype.constructor = B;

Поскольку в конструкторе нет аргументов для передачи, новой A не на что жаловаться.Сейчас я не нашел хорошего способа наследования, если у конструктора есть аргументы для передачи.Например,

function A(x, y) {}
A.prototype.run = function () {};

function B(x, y) {}
B.prototype = new A;
B.prototype.constructor = B;

Я мог бы передать некоторые произвольные значения, такие как:

B.prototype = new A(null, null);

В некоторых случаях мне может потребоваться проверить x и y в конструкторе A. В некоторых крайних случаяхВ случаях, когда я проверяю x или y, мне нужно генерировать ошибки.Тогда у B нет возможности наследовать от A, используя новый A.

Есть предложения?

Спасибо!

Ответы [ 4 ]

22 голосов
/ 23 сентября 2011

Ну, если вы хотите сделать B.prototype объектом, который наследуется от A.prototype, без выполнения конструктора A, чтобы избежать всех возможных побочных эффектов, вы можете использовать фиктивный конструктор, например :

function tmp() {}
tmp.prototype = A.prototype;
B.prototype = new tmp();
B.prototype.constructor = B;

Вы можете создать функцию для инкапсуляции логики создания этого нового объекта, например ::

function inherit(o) {
  function F() {}; // Dummy constructor
  F.prototype = o; 
  return new F(); 
}

//...
B.prototype = inherit(A.prototype);
B.prototype.constructor = B;

Если вы ориентируетесь на современные браузеры, вы можете использовать метод ECMAScript 5 Object.create для той же цели, например ::

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
//..
6 голосов
/ 23 сентября 2011

Проблема в том, что вы не можете легко создать объект-прототип для B, так как вызвать конструктор A невозможно. Это связано с тем, что параметры конструктора неизвестны до выполнения new B. Вам нужна фиктивная функция конструктора для создания прототипа для B, который ссылается на прототип A.

B.prototype = (function(parent){
    function protoCreator(){};
    protoCreator.prototype = parent.prototype;
    // Construct an object linking to A.prototype without calling constructor of A
    return new protoCreator();
})(A);

После того как вы настроили объект-прототип для B, вы должны убедиться, что вызываете конструктор A в конструкторе B.

function B(x, y) {
    // Replace arguments by an array with A's arguments in case A and B differ in parameters
    A.apply(this, arguments);
}

Теперь вы должны иметь возможность создать экземпляр B, позвонив по номеру new B(x, y).

Полный список параметров, включая проверку параметров в A, см. a jsFiddle .

В исходном коде вы устанавливаете B.prototype.constructor = B. Я не понимаю, почему вы это делаете. Свойство constructor не влияет на иерархию наследования, за которую отвечает свойство prototype. Если вы хотите, чтобы именованный конструктор содержался в свойстве constructor, вам нужно немного расширить код сверху:

// Create child's prototype – Without calling A
B.prototype = (function(parent, child){
    function protoCreator(){
        this.constructor = child.prototype.constructor
    };
    protoCreator.prototype = parent.prototype;
    return new protoCreator();
})(A, B);

Используя первое определение B.prototype, вы получите следующие результаты:

var b = new B(4, 6);
b.constructor // A
console.info(b instanceof A); // true
console.info(b instanceof B); // true

С расширенной версией вы получите:

var b = new B(4, 6);
b.constructor // B
console.info(b instanceof A); // true
console.info(b instanceof B); // true

Причина различного вывода в том, что instanceof следует за всей цепочкой прототипов b и пытается найти соответствующий объект-прототип для A.prototype или B.prototype (в другом вызове). Прототип b.constructor относится к функции, которая использовалась для определения прототипа экземпляров. Если вам интересно, почему он не указывает на protoCreator, это потому, что его прототип был перезаписан с A.prototype во время создания B.prototype. Расширенное определение, как показано в обновленном примере , исправляет это свойство constructor, чтобы указывать на более подходящую (потому что, вероятно, более ожидаемую) функцию.

Для повседневного использования я бы рекомендовал отказаться от идеи полного использования свойства constructor экземпляров. Вместо этого используйте instanceof, поскольку его результаты более предсказуемы / ожидаемы.

5 голосов
/ 24 сентября 2011

Учтите это:

function B( x, y ) {
    var b = Object.create( new A( x, y ) );

    // augment b with properties or methods if you want to

    return b;
}

А потом

var b = new B( 12, 13 );

Теперь b наследуется от экземпляра A, который, в свою очередь, наследуется от A.prototype.

Демонстрационная версия: http://jsfiddle.net/BfFkU/


Object.create не реализован в IE8, но его легко реализовать вручную:

if ( !Object.create ) {
    Object.create = function ( o ) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Это можно поместить в файл ie8.js, который загружается только для IE8 и ниже с помощью условных комментариев.

4 голосов
/ 19 февраля 2016

Хотя это старая тема, я все равно решил ответить.Два способа сделать это:

Хотя способ Pseudo Classical является наиболее популярным, у него есть свои недостатки, так как он должен вызвать родительский конструктор один раз в дочернем конструкторе и один раз при наследованииПрототип.Кроме того, прототип дочернего элемента будет содержать все свойства родительского конструктора, которые в любом случае будут перезаписаны при вызове дочернего конструктора.Мой личный выбор - Прототип наследования .

1.Псевдо-классическое наследование:

function A(x, y) {} 
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = new A();
B.prototype.constructor = B;

2.Наследование прототипа:

function A(x, y) {}
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
...