Розширений клас JavaScript


77

У мене базовий клас:

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?


Відповіді:


148

Оновлено нижче для 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

Якщо вам потрібно підтримати їх, зв’язаний документ 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


3
Що робити, якщо конструктору Person потрібні параметри, наприклад Person (ім'я) ...?
Пхунг Д.,

3
@PhamHuyAnh Просто зробіть щось на зразокfunction Person(n) { this.name = n; }
Олівер Спрін

Student.prototype = new Person();Цей рядок призводить до помилки, якщо я отримую доступ до параметра у супер класі.
Макс

1
Student.prototype = Object.create(Person.prototype);Класичний метод створення працює тут
Макс

1
Я намагаюся створити функцію розширення, яка робить все це самостійно. Чи можна якось перемістити "Person.call (this);" або з прикладу нижче "Monster.apply (this, аргументи);" до такої функції? У мене проблеми з цим.
sebastian_t

19

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
        }

}

2
Це набагато чистіше, може підтвердити роботу в chrome 51.0, Firefox 47.
Reahreic

1
Використовуйте try{}catch(e){}блоки для управління цим, і повідомте кінцевому користувачеві, щоб оновив свій браузер, якщо це потрібно.
Абденнур ТУМІ

10

Спробуйте це:

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

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

Редагувати: я розмістив тут швидку демонстрацію http://jsbin.com/anekew/1/edit . Зверніть увагу, що extendsце зарезервоване слово в JS, і ви можете отримувати попередження під час прив’язки коду, ви можете просто назвати його 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"

Використовуючи це, Monkeyне успадковує Monsterвластивості ( health). Ви також отримаєте "ReferenceError: Monkey is not defined", якщо Monkeyне визначено до дзвінкаMonkey.extends(Monster)
Філ

@Phil, це декларація функції, яку вона підняла, вона повинна працювати. Єдина "проблема", яку ви отримаєте від цього коду, - "extends - це зарезервоване слово", але ви можете легко змінити його на будь-який інший ідентифікатор.
elclanrs

1
Дякую друже. Це чудово. Я можу застосувати його в Node.js для створення базового класу для конструкторів і т. Д., Тому мені не потрібно створювати монго-зв’язки і т. Д. Кожного разу, коли я створюю клас конструкту
Mattijs,

6

Якщо вам не подобається підхід до прототипу, оскільки він насправді не поводиться добре в ООП, ви можете спробувати наступне:

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;
    }
})

Використання легкої бібліотеки (зменшене 2 тис.): Https://github.com/haroldiedema/joii


Дякую за посилання на цю бібліотеку !! Це виглядає приголомшливо: D Здається, це те, що я так довго шукав! <3
x3ro

1

Я можу запропонувати один варіант, просто прочитав у книзі, здається найпростішим:

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

1

Це розширення (вибачте за каламбур) рішення 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;
}

В останню частину підготовки ми вдосконалюємо наш прототип Function за допомогою нової хитрості-покери Девіда:

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();
        }
    }
);

0

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

     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');
    }
  }])

0

Для автодидактів:

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...

Створіть "екземпляр" за допомогою getter і setter:

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

0

Короткий зміст:

Існує кілька способів вирішити проблему розширення функції конструктора за допомогою прототипу в 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__властивістю, встановленою для об'єкта Monster. Після цього ми можемо ініціалізувати всі властивості об'єкта, у цьому прикладі ми призначаємо рахунок бананів новоствореному об'єкту.


0

абсолютно мінімальною (і правильною, на відміну від багатьох відповідей вище) є:

function Monkey(param){
  this.someProperty = param;
}
Monkey.prototype = Object.create(Monster.prototype);
Monkey.prototype.eatBanana = function(banana){ banana.eat() }

Це все. Ви можете прочитати тут довше пояснення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.