Класс Расширения JavaScript


у меня есть базовый класс:

function Monster() {
  this.health = 100;
}

Monster.prototype.growl = function() {
  console.log("Grr!");
}

что я хочу расширить и создать другой класс с:

function Monkey extends Monster() {
  this.bananaCount = 5;
}

Monkey.prototype.eatBanana {
  this.bananaCount--;
  this.health++; //Accessing variable from parent class monster
  this.growl();  //Accessing function from parent class monster
}

Я провел довольно много исследований, и, похоже, есть много запутанных решений для этого в JavaScript. Каков был бы самый простой и надежный способ достижения этого в JS?

9 56

9 ответов:

Обновлено ниже для ES6

март 2013 и ES5

этот документ MDN хорошо описывает расширение классов:

https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript

в частности, вот теперь они справляются:

// define the Person Class
function Person() {}

Person.prototype.walk = function(){
  alert ('I am walking!');
};
Person.prototype.sayHello = function(){
  alert ('hello');
};

// define the Student class
function Student() {
  // Call the parent constructor
  Person.call(this);
}

// inherit Person
Student.prototype = Object.create(Person.prototype);

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;

// replace the sayHello method
Student.prototype.sayHello = function(){
  alert('hi, I am a student');
}

// add sayGoodBye method
Student.prototype.sayGoodBye = function(){
  alert('goodBye');
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

отметим, что Object.create() не поддерживается в некоторых старых браузерах, в том числе IE8:

Object.create browser support

если вы находитесь в положении необходимости поддерживать их, связанный документ MDN предлагает использовать полифилл или следующее приближение:

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

используя это как Student.prototype = createObject(Person.prototype) предпочтительнее использовать new Person() в нем избегает вызова родительской функции конструктора при наследовании прототипа и только вызывает родительский конструктор, когда конструктор наследника находится называемый.

май 2017 и ES6

К счастью, дизайнеры JavaScript услышали наши мольбы о помощи и приняли более подходящий способ решения этой проблемы.

MDN есть еще один отличный пример наследования класса ES6, но я покажу точно такой же набор классов, как и выше, воспроизведенный в ES6:

class Person {
    sayHello() {
        alert('hello');
    }

    walk() {
        alert('I am walking!');
    }
}

class Student extends Person {
    sayGoodBye() {
        alert('goodBye');
    }

    sayHello() {
        alert('hi, I am a student');
    }
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

чистый и понятный, так же, как мы все хотим. Имейте в виду, что хотя ES6 довольно распространен, это не поддерживается везде:

ES6 browser support

ES6 дает вам теперь возможность использовать класс & выходит ключевые слова :

тогда ваш код будет:

у вас есть базовый класс:

class Monster{
       constructor(){
             this.health = 100;
        }
       growl() {
           console.log("Grr!");
       }

}

что вы хотите расширить и создать другой класс с:

class Monkey extends Monster {
        constructor(){
            super(); //don't forget "super"
            this.bananaCount = 5;
        }


        eatBanana() {
           this.bananaCount--;
           this.health++; //Accessing variable from parent class monster
           this.growl(); //Accessing function from parent class monster
        }

}

попробуйте это:

Function.prototype.extends = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

Monkey.extends(Monster);
function Monkey() {
  Monster.apply(this, arguments); // call super
}

Edit: я поставил здесь быструю демонстрацию http://jsbin.com/anekew/1/edit. обратите внимание, что extends - это зарезервированное слово в JavaScript и вы можете получить предупреждение, когда пылеобразования ваш код, вы можете просто назвать его inherits, вот что я обычно делаю.

С этим помощником на месте и с помощью объекта props как только параметр, наследование в JS становится немного проще:

Function.prototype.inherits = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

function Monster(props) {
  this.health = props.health || 100;
}

Monster.prototype = {
  growl: function() {
    return 'Grrrrr';
  }
};

Monkey.inherits(Monster);
function Monkey() {
  Monster.apply(this, arguments);
}

var monkey = new Monkey({ health: 200 });

console.log(monkey.health); //=> 200
console.log(monkey.growl()); //=> "Grrrr"

Если вам не нравится подход прототипа, потому что он действительно не ведет себя хорошо ООП-образом, вы можете попробовать это:

var BaseClass = function() 
{
    this.some_var = "foobar";

    /**
     * @return string
     */
    this.someMethod = function() {
        return this.some_var;
    }
};

var MyClass = new Class({ extends: BaseClass }, function()
{
    /**
     * @param string value
     */
    this.__construct = function(value)
    {
        this.some_var = value;
    }
})

использование облегченной библиотеки (2K minified):https://github.com/haroldiedema/joii

Это расширение (извините за каламбур) решения elclanrs для включения подробностей о методах экземпляра, а также принятия расширяемого подхода к этому аспекту вопроса; я полностью признаю, что это собрано благодаря "JavaScript: окончательное руководство" Дэвида Фланагана (частично скорректировано для этого контекста). Обратите внимание, что это явно более подробное, чем другие решения, но, вероятно, принесет пользу в долгосрочной перспективе.

Сначала мы используем простое "расширение Дэвида" функция, которая копирует свойства в указанный объект:

function extend(o,p) {
    for (var prop in p) {
        o[prop] = p[prop];
    }
    return o;
}

затем мы реализуем его утилиту определения подкласса:

function defineSubclass(superclass,     // Constructor of our superclass
                          constructor,  // Constructor of our new subclass
                          methods,      // Instance methods
                          statics) {    // Class properties
        // Set up the prototype object of the subclass
    constructor.prototype = Object.create(superclass.prototype);
    constructor.prototype.constructor = constructor;
    if (methods) extend(constructor.prototype, methods);
    if (statics) extend(constructor, statics);
    return constructor;
}

для последнего бита подготовки мы улучшаем наш прототип функции с новым jiggery-pokery Дэвида:

Function.prototype.extend = function(constructor, methods, statics) {
    return defineSubclass(this, constructor, methods, statics);
};

после определения нашего класса Monster мы делаем следующее (что повторно используется для любых новых классов, которые мы хотим расширить / наследовать):

var Monkey = Monster.extend(
        // constructor
    function Monkey() {
        this.bananaCount = 5;
        Monster.apply(this, arguments);    // Superclass()
    },
        // methods added to prototype
    eatBanana: function() {
        this.bananaCount--;
        this.health++;
        this.growl();
    }
);

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

     function AbstractClass() {
      this.superclass_method = function(message) {
          // do something
        };
     }

     function Child() {
         AbstractClass.apply(this);
         // Now Child will have superclass_method()
     }

пример на angularjs:

http://plnkr.co/edit/eFixlsgF3nJ1LeWUJKsd?p=preview

app.service('noisyThing', 
  ['notify',function(notify){
    this._constructor = function() {
      this.scream = function(message) {
          message = message + " by " + this.get_mouth();
          notify(message); 
          console.log(message);
        };

      this.get_mouth = function(){
        return 'abstract mouth';
      }
    }
  }])
  .service('cat',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.meow = function() {
      this.scream('meooooow');
    }
    this.get_mouth = function(){
      return 'fluffy mouth';
    }
  }])
  .service('bird',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.twit = function() {
      this.scream('fuuuuuuck');
    }
  }])

Для Кулибиных:

function BaseClass(toBePrivate){
    var morePrivates;
    this.isNotPrivate = 'I know';
    // add your stuff
}
var o = BaseClass.prototype;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// MiddleClass extends BaseClass
function MiddleClass(toBePrivate){
    BaseClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = MiddleClass.prototype = Object.create(BaseClass.prototype);
MiddleClass.prototype.constructor = MiddleClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';



// TopClass extends MiddleClass
function TopClass(toBePrivate){
    MiddleClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = TopClass.prototype = Object.create(MiddleClass.prototype);
TopClass.prototype.constructor = TopClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// to be continued...

создать "экземпляр" с геттером и сеттером:

function doNotExtendMe(toBePrivate){
    var morePrivates;
    return {
        // add getters, setters and any stuff you want
    }
}

Я могу предложить один вариант, просто прочитали в книге, кажется, самый простой:

function Parent() { 
  this.name = 'default name';
};

function Child() {
  this.address = '11 street';
};

Child.prototype = new Parent();      // child class inherits from Parent
Child.prototype.constructor = Child; // constructor alignment

var a = new Child(); 

console.log(a.name);                // "default name" trying to reach property of inherited class

резюме:

существует несколько способов, которые могут решить проблему расширения функции конструктора с прототипом в Javascript. Какой из этих методов является "лучшим" решением, основано на мнении. Однако вот два часто используемых метода для расширения прототипа функции конструктора.

ES 2015 классы:

class Monster {
  constructor(health) {
    this.health = health
  }
  
  growl () {
  console.log("Grr!");
  }
  
}


class Monkey extends Monster {
  constructor (health) {
    super(health) // call super to execute the constructor function of Monster 
    this.bananaCount = 5;
  }
}

const monkey = new Monkey(50);

console.log(typeof Monster);
console.log(monkey);

вышеуказанный подход использования ES 2015 классы не более чем синтаксические сахар над шаблоном прототипного наследования в javascript. Вот первый журнал, где мы оцениваем typeof Monster мы можем наблюдать, что это функция. Это потому, что классы-это просто конструкторские функции под капотом. Тем не менее, вам может понравиться этот способ реализации прототипного наследования и окончательно должен выучить его. Он используется в основных системах, таких как ReactJS и Angular2+.

функции фабрике, используя Object.create():

function makeMonkey (bananaCount) {
  
  // here we define the prototype
  const Monster = {
  health: 100,
  growl: function() {
  console.log("Grr!");}
  }
  
  const monkey = Object.create(Monster);
  monkey.bananaCount = bananaCount;

  return monkey;
}


const chimp = makeMonkey(30);

chimp.growl();
console.log(chimp.bananaCount);

этот метод использует Object.create() метод, который принимает объект, который будет прототипом вновь созданный объект возвращается. Поэтому мы сначала создаем прототип объекта в этой функции, а затем вызываем Object.create(), который возвращает пустой объект с __proto__ свойство, установленное для объекта монстра. После этого мы можем инициализировать все свойства объекта, в этом примере мы назначаем bananacount для вновь созданного объект.