Я хотел бы реализовать следующее поведение в JS. Обратите внимание, что синтаксис является символическим.

Это мой родительский класс

class = TList {
   FList: array;

   function AddElement(Ele) {
        Flist.Add(Ele)
   };

   function RemoveEle(Ele) {
        FList.Remove(Ele)
   };   
}

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

class = TAlertList(inherit from TList) {

   function AddElement(Ele) {
      Alert('element will be added');
      call.parent.AddElement(Ele)
   };

   function RemoveElement(Ele) {
     call.parent.RemoveElement(Ele);
     Alert('element removed');
   }

}

Обратите внимание, как я наследую родительские методы в местах, которые я хочу.

Теперь я должен иметь возможность создать объект из моего дочернего класса и сделать следующее.

MyAlertList = new TAlertList;
MyAlertList.Add('hello');
console.log(MyAlertList.FList);

Я должен иметь возможность наследовать больше дочерних классов от TAlertList и иметь возможность изменять существующее поведение. Мне нужно сделать это в чистом ES5 без использования каких-либо библиотек. Ожидается стандартная практика ООП.

3
Charlie 18 Дек 2015 в 18:57

4 ответа

Лучший ответ

Обратите внимание, что конструктор TList должен быть применен к экземпляру TAlertList;

ES5, сначала настройте базовый конструктор

function TList() {
    this.Flist = [];
    // ...
}

TList.prototype = {
    constructor: TList,
    AddElement: function AddElement(Ele) {
        this.Flist.push(Ele);
    },
    RemoveEle: function RemoveEle(Ele) {
        var i = this.Flist.lastIndexOf(Ele);
        if (i !== -1)
            this.Flist.splice(i, 1);
    }
};

Затем настройте конструктор, который его расширяет, посмотрите, как это означает вызов базового конструктора в экземпляре, создаваемом расширенным конструктором, и создание объекта-прототипа, который наследует прототип базового конструктора

function TAlertList() {
    // construct from base
    TList.call(this);
    // further construct
    // ...
}
TAlertList.prototype = Object.create(TList.prototype);
TAlertList.prototype.constructor = TAlertList;

// depending on how you want to reference stuff
TAlertList.prototype.AddElement = function AddElement(Ele) {
    alert('element will be added');
    TList.prototype.AddElement.call(this, Ele);
};
TAlertList.prototype.RemoveElement = function RemoveElement(Ele) {
    TList.prototype.RemoveEle.call(this, Ele);
    alert('element removed');
};

Синтаксис ES6 использует ключевое слово super

class TList {
    constructor() {
        this.FList = [];
    }
    AddElement(Ele) {
        this.Flist.push(Ele);
    }
    RemoveEle(Ele) {
        var i = this.Flist.lastIndexOf(Ele);
        if (i !== -1)
            this.Flist.splice(i, 1);
    }
}

class TAlertList extends TList {
    constructor() {
        super();
    }
    AddElement(Ele) {
        alert('element will be added');
        super.AddElement(Ele);
    }
    RemoveElement(Ele) {
        super.RemoveEle(Ele);
        alert('element removed');
    }
}

Возвращаясь к ES5, обобщая как фабрику, вы можете увидеть своего рода алгоритм того, как это сделать

function extend(baseConstructor, extendedConstructor, prototypeLayer) {
    function Constructor() {
        var i = 0, j = 0, args = Array.prototype.slice.call(arguments);

        i = j, j += baseConstructor.length;
        baseConstructor.apply(this, args.slice(i, j));

        i = j, j = args.length;
        extendedConstructor.apply(this, args.slice(i, j));
    }
    Object.defineProperty(Constructor, 'length', { // fix .length
        value: baseConstructor.length + extendedConstructor.length,
        configurable: true
    });
    Constructor.prototype = Object.create(baseConstructor.prototype);
    Constructor.prototype.constructor = Constructor;
    Object.assign(Constructor.prototype, prototypeLayer);
    return Constructor;
}

Итак, тогда

function Foo(x) {this.foo = x;}
Foo.prototype.fizz = 1;
var Bar = extend(Foo, function (x) {this.bar = x;}, {buzz: 1});
// ...
var b = new Bar('foo', 'bar');
b.foo; // "foo"
b.bar; // "bar"
b instanceof Foo; // true
b instanceof Bar; // true
b.fizz; // 1
b.buzz; // 1

Обратите внимание, что это пример алгоритма, которому вы должны следовать при написании каждого расширенного конструктора, а не рабочего кода.

2
Paul S. 18 Дек 2015 в 18:02

С чистой ES5, вы можете сделать это так:

function TList() {
   this.FList = []; //not really ideal.
}

TList.prototype.addElement = function(ele) {
   this.FList.push(ele);
};

TList.prototype.removeElement = function(ele) {
   this.FList.splice(this.FList.indexOf(ele), 1);
}

function TAlertList(){
    this.FList = [];
}

TAlertList.prototype = new TList(); //inherit from TList
TAlertList.prototype.constructor = TAlertList; //reset constructor

TAlertList.prototype.addElement = function(ele) {
  alert('element will be added');
  TList.prototype.addElement.call(this, ele);
};

TAlertList.prototype.removeElement = function(ele) {
  alert('element removed');
  TList.prototype.removeElement.call(this, ele);
};

Пара примечаний:

Свойство FList его родителя фактически будет общим для всех объектов, которые наследуются от родителя, если они не перезаписаны. Это означает, что если вы не перезапишите FList, вы получите следующее:

var a = new TAlertList();
var b = new TAlertList();

a.push(1); //b.Flist === [1]

На мой взгляд, было бы лучше, если бы вы называли свои дочерние функции другими именами, отличными от родительских. Таким образом, вам не нужно делать:

TList.prototype.function.call(this, parameter1, parameter2, ..);

Вы можете просто назвать их так:

this.function(paremeter1, parameter2);

Конечно, это не статический способ вызова родителя, поскольку вы можете перезаписать функцию своей собственной. Опять же TList.prototype.function не является обязательной функцией родительского объекта, которому принадлежит функция. Для этого вам нужно использовать нестандартное свойство ES5 __proto__.

this.__proto__.function.call(this, parameter1, parameter2, ..);

Если вы не планируете манипулировать функцией вокруг различных объектов, вам это не нужно.

-1
MinusFour 18 Дек 2015 в 16:48

Ваш код будет следующим

function TList(){
  this.FList = [];
}

TList.prototype.AddElement = function(Ele){
 this.FList.push(Ele);
}
TList.prototype.RemoveElement = function(Ele){
 this.FList.splice(Ele,1); //Ele is the index to remove;
}

Это приблизительное представление о том, как наследуется в JavaScript.

function TAlertList (){
     TList.call(this);
}

TAlertList.prototype = Object.create(TList.prototype);
TAlertList.prototype.constructor = TAlertList;

TAlertList.prototype.AddElement = function(ele){
   alert('Element will be added');
   TList.prototype.AddElement.call(this,ele);
};
TAlertList.prototype.RemoveElement = function(ele){
   alert('Element will be remove');
   TList.prototype.RemoveElement.call(this,ele);
};

Итак, классический супер-колл

ParentClass.prototype.myMethod.call(this,args);
1
caballerog 18 Дек 2015 в 18:04

Это Q & A

Изменить. Не забудьте также прочитать комментарий @ paul, если вы планируете прочитать полный текст.

Почти все ответы были получены на основе популярного примера Person в документации MDN о JS OOP.

Теория, лежащая в основе этого метода, заключается в том, чтобы поместить поля объекта в функцию конструктора при реализации методов в объекте-прототипе. Дочерний объект может наследовать все поля , вызывая функцию конструктора с придуманным значением this. Также он может наследовать все методы , имея тот же объект-прототип родительского объекта, что и его объект-прототип. Единственное правило заключается в том, что вам нужно вызывать методы, используя call или apply, чтобы указать method на правильный объект this при реализации наследования.

Мне не понравился этот подход по двум причинам.

  1. поля и методы объекта должны быть разделены между двумя объектами ( поля - функция конструктора, методы - прототип). У этого нет аромата уникального поведения уникального объекта - который должен быть единственным классом.

  2. Вы должны указать имя родительского объекта при наследовании. Это не автоматически. Допустим, объект C наследуется от объекта A . Таким образом, внутри методов объекта C вам необходимо упомянуть объект A при наследовании от него. (TList.prototype.AddElement.call(this, Ele);) Что, если объект B окажется позже? Вам придется изменить все методы наследования C для вызова из B . Это ни в коем случае не хорошее наследство.


Я хотел преодолеть эти проблемы в методе MDN. Я придумал следующую модель без this, без call и без apply. Кодекс прост и понятен. Вам не нужно упоминать имя родительского объекта при наследовании от него. Таким образом, новый класс может появиться между родителем и ребенком в любое время без особых изменений. Пожалуйста, обсудите это и укажите на недостатки этой модели.

Вот функция, которая возвращает родительский объект.

function tList() {

   var ret = Object.create(null);

   ret.list = [];

   ret.addElement = fucntion(ele) {
      ret.list.push(ele)
   };

   return ret;

}


//You can create tList object like this: 
var myList = tList();
myList.addElement('foo');

Теперь приходит дочерний объект:

function tAlertList() {

   var ret = Object.create(tList());

   //Lets inherit with the fashion of overriding a virtual method
   ret.addElement = function(ele) {

      //Here is new code
      alert('Adding element ' + ele);

      //Automatic inheritance now
      Object.getPrototypeOf(ret).addElement(ele);
   }

   return ret;
}

//Just create the child object and use it
var myAlertList = tAlertList();
myAlertList.addElement('buzz') ;

Вы можете иметь внучатых детей возражать и наследовать от родителя, не упоминая их имена. Допустим, вы должны поместить tCustomList между tList и tAlertList. Все, что вам нужно сделать, это сказать tAlertList наследовать от tCustomList в одном месте. (var ret = Object.create(tCustomList());) Все остальное остается прежним.

Вот скрипка.

0
Charlie 3 Апр 2017 в 05:15