Як успадкувати від класу в javascript?


99

У PHP / Java можна зробити:

class Sub extends Base
{
}

І автоматично всі публічні / захищені методи, властивості, поля тощо класу Super стають частиною підкласу, який при необхідності може бути замінений.

Що еквівалент тому в Javascript?





Чи працює цей крокфордський спосіб? ZParenizor.inherits (Паренізор);
Loren Shqipognja

Відповіді:


80

Я змінив, як це роблю зараз, намагаюся уникати використання конструкторських функцій та їх prototypeвластивості, але моя стара відповідь з 2010 року все ще внизу. Я зараз віддаю перевагу Object.create(). Object.createдоступний у всіх сучасних браузерах.

Слід зазначити, що Object.createзазвичай набагато повільніше, ніж використання newз конструктором функцій.

//The prototype is just an object when you use `Object.create()`
var Base = {};

//This is how you create an instance:
var baseInstance = Object.create(Base);

//If you want to inherit from "Base":
var subInstance = Object.create(Object.create(Base));

//Detect if subInstance is an instance of Base:
console.log(Base.isPrototypeOf(subInstance)); //True

jsfiddle

Однією з найбільших переваг використання Object.create є можливість передавати аргумент defineProperties , який дає вам значний контроль над тим, як властивості в класі можна отримати доступ і перерахувати, і я також використовую функції для створення екземплярів, вони служать конструктори таким чином, як ви можете робити ініціалізацію в кінці, а не просто повертати екземпляр.

var Base = {};

function createBase() {
  return Object.create(Base, {
    doSomething: {
       value: function () {
         console.log("Doing something");
       },
    },
  });
}

var Sub = createBase();

function createSub() {
  return Object.create(Sub, {
    doSomethingElse: {
      value: function () {
        console.log("Doing something else");
      },
    },
  }); 
}

var subInstance = createSub();
subInstance.doSomething(); //Logs "Doing something"
subInstance.doSomethingElse(); //Logs "Doing something else"
console.log(Base.isPrototypeOf(subInstance)); //Logs "true"
console.log(Sub.isPrototypeOf(subInstance)); //Logs "true

jsfiddle

Це моя оригінальна відповідь з 2010 року:

function Base ( ) {
  this.color = "blue";
}

function Sub ( ) {

}
Sub.prototype = new Base( );
Sub.prototype.showColor = function ( ) {
 console.log( this.color );
}

var instance = new Sub ( );
instance.showColor( ); //"blue"

5
Як щодо значення sub.prototype.constructor? Я думаю, що це теж слід встановити на нижнє значення.
maximus

Крім того, що ви використовуєте зарезервовані ключові слова ("супер") як назви класів, я не зміг запустити ваш приклад: jsbin.com/ixiyet/8/edit
MOnsDaR

@MOnsDaR Я перейменував його на Базу
Бьорн

Якщо я використовую, alert()щоб побачити, які instance.showColor()прибутки я все-таки отримую undefined. jsbin.com/uqalin/1
MOnsDaR

1
@MOnsDaR тому, що вона консолює журнали, вона не повертає нічого, щоб відобразити попередження. Чи бачите випис повернення в showColor?
Бьорн

190

У JavaScript у вас немає класів, але ви можете отримати спадщину та повторне використання поведінки багатьма способами:

Псевдокласичне успадкування (шляхом прототипування):

function Super () {
  this.member1 = 'superMember1';
}
Super.prototype.member2 = 'superMember2';

function Sub() {
  this.member3 = 'subMember3';
  //...
}
Sub.prototype = new Super();

Потрібно використовувати з newоператором:

var subInstance = new Sub();

Застосування функцій або "ланцюжок конструкторів":

function Super () {
  this.member1 = 'superMember1';
  this.member2 = 'superMember2';
}


function Sub() {
  Super.apply(this, arguments);
  this.member3 = 'subMember3';
}

Цей підхід також слід використовувати разом з newоператором:

var subInstance = new Sub();

Різниця з першим прикладом є те , що , коли ми конструктор для об'єкта всерединіapplySuperthisSub , він додає властивості , призначені thisна Superбезпосередньо на новому екземплярі, наприклад , subInstanceмістить властивість member1і member2безпосередньо (subInstance.hasOwnProperty('member1') == true; ).

У першому прикладі ці властивості досягаються через прототип ланцюга , вони існують на внутрішньому [[Prototype]]об'єкті.

Успадковування паразитів або конструктори живлення:

function createSuper() {
  var obj = {
    member1: 'superMember1',
    member2: 'superMember2'
  };

  return obj;
}

function createSub() {
  var obj = createSuper();
  obj.member3 = 'subMember3';
  return obj;
}

Цей підхід в основному заснований на "збільшенні об'єктів", вам не потрібно використовувати newоператор, і, як бачите, thisключове слово не бере участь.

var subInstance = createSub();

ECMAScript 5-е видання. Object.createметод:

// Check if native implementation available
if (typeof Object.create !== 'function') {
  Object.create = function (o) {
    function F() {}  // empty constructor
    F.prototype = o; // set base object as prototype
    return new F();  // return empty object with right [[Prototype]]
  };
}

var superInstance = {
  member1: 'superMember1',
  member2: 'superMember2'
};

var subInstance = Object.create(superInstance);
subInstance.member3 = 'subMember3';

Вищеописаний метод є прототиповою методикою успадкування, запропонованою Крокфордом .

Екземпляри об'єктів успадковують від інших екземплярів об'єкта, ось і все.

Цей метод може бути краще , ніж просто «об'єкт збільшення» , тому що спадкові властивості не копіюються всі нові екземпляри об'єктів, так як базова об'єкт встановлений як [[Prototype]]частина розширеного об'єкта, в наведеному вище прикладі , subInstanceмістить фізично тільки member3властивість.


3
не використовуйте екземпляри для успадкування - використовуйте ES5 Object.create()або власну clone()функцію (наприклад, mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) для успадкування безпосередньо від об'єкта прототипу; дивіться коментарі до stackoverflow.com/questions/1404559/… для пояснення
Крістоф

Спасибі @Christoph, я збирався згадати Object.createметод :)
CMS

1
Це не належне успадкування, оскільки у прототипу Sub будуть мати членів екземплярів Super. Отже, всі екземпляри Sub будуть мати однакову member1змінну, що зовсім не бажано. Звичайно, вони можуть це переписати, але це просто не має сенсу. github.com/dotnetwise/Javascript-FastClass - кращий цукровий розчин.
Адаптабі

Привіт @CMS, ви можете пояснити, будь ласка, чому мені потрібно створити екземпляр батьківського класу в першому прикладі для установки успадкування для підкласу? Я говорю про цю лінію: Sub.prototype = new Super();. Що робити, якщо обидва класи ніколи не будуть використовуватися під час виконання сценарію? Це виглядає як питання про продуктивність. Чому мені потрібно створити батьківський клас, якщо дочірній клас насправді не використовується? Чи можете ви докладно уточнити? Ось проста демонстрація випуску: jsfiddle.net/slavafomin/ZeVL2 Дякую!
Слава Фомін II

У всіх прикладах - крім останнього - є "клас" для Super і "клас" для Sub, і тоді ви створюєте екземпляр Sub. Чи можете ви додати порівняльний приклад для прикладу Object.create?
Лука

49

Для тих, хто переходить на цю сторінку у 2019 році чи пізніше

З останньою версією стандарту ECMAScript (ES6) ви можете використовувати ключове слово class.

Зауважте, що визначення класу не є регулярним object; отже, між членами класу немає коми. Щоб створити екземпляр класу, ви повинні використовувати newключове слово. Щоб успадкувати базовий клас, використовуйте extends:

class Vehicle {
   constructor(name) {
      this.name = name;
      this.kind = 'vehicle';
   }
   getName() {
      return this.name;
   }   
}

// Create an instance
var myVehicle = new Vehicle('rocky');
myVehicle.getName(); // => 'rocky'

Щоб успадкувати базовий клас, використовуйте extends:

class Car extends Vehicle {
   constructor(name) {
      super(name);
      this.kind = 'car'
   }
}

var myCar = new Car('bumpy');

myCar.getName(); // => 'bumpy'
myCar instanceof Car; // => true
myCar instanceof Vehicle; // => true

З похідного класу ви можете використовувати супер з будь-якого конструктора або методу для доступу до його базового класу:

  • Щоб викликати батьківський конструктор, використовуйте super().
  • Для виклику іншого члена, використання, наприклад, super.getName().

Існує більше використання класів. Якщо ви хочете заглибитися в цю тему, я рекомендую « Класи в ECMAScript 6 » доктора Акселя Раушмаєра. *

джерело


1
Під капотом, classі extendsце (ультра корисний) синтаксис для ланцюга прототипів: stackoverflow.com/a/23877420/895245
Чіро Сантіллі郝海东冠状病六四事件法轮功

тільки для вашої інформації 'instance.name' тут 'mycar.name' поверне ім’я класу. Це поведінка ES6 та ESnext за замовчуванням. Тут для mycar.name повернеться "Транспортний засіб"
Шильйо Полсон

7

Ну, в JavaScript немає "спадкування класів", є просто "успадкування прототипу". Таким чином, ви не складаєте клас "вантажівка", а потім позначаєте його як підклас "автомобільний". Натомість ви робите об’єкт "Джек" і кажете, що він використовує "Джон" як прототип. Якщо Джон знає, скільки "4 + 4", значить і Джек.

Я пропоную вам прочитати статтю Дугласа Крокфорда про прототипічне успадкування тут: http://javascript.crockford.com/prototypal.html Він також показує, як ви можете зробити так, щоб JavaScript успадкував "схожий на" схожість, як і в інших мовах OO, а потім пояснює, що це насправді означає зламати javaScript таким чином, щоб його не передбачалося використовувати.


Припустимо, прототипом Джека є Джон. Під час роботи я додав до Джона властивості / поведінку. Я отримаю це майно / поведінку від Джека?
Рам Бавіредді

Ви впевнені, що будете. Наприклад, таким чином люди зазвичай додають метод "trim ()" до всіх рядкових об'єктів (він не вбудований). Приклад див. Тут: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
наївісти

6

Я вважаю цю цитату найяскравішою:

По суті, JavaScript- клас "клас" - це лише функція "Функція", яка виконує функції конструктора плюс доданий об'єкт-прототип. ( Джерело: Гуру Кац )

Мені подобається використовувати конструктори, а не об'єкти, тому я частково ставлюся до методу "псевдокласичного успадкування", описаного тут CMS . Ось приклад багаторазового успадкування з ланцюжком прототипу :

// Lifeform "Class" (Constructor function, No prototype)
function Lifeform () {
    this.isLifeform = true;
}

// Animal "Class" (Constructor function + prototype for inheritance)
function Animal () {
    this.isAnimal = true;
}
Animal.prototype = new Lifeform();

// Mammal "Class" (Constructor function + prototype for inheritance)
function Mammal () {
    this.isMammal = true;
}
Mammal.prototype = new Animal();

// Cat "Class" (Constructor function + prototype for inheritance)
function Cat (species) {
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger);
// The console outputs a Cat object with all the properties from all "classes"

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

// You can see that all of these "is" properties are available in this object
// We can check to see which properties are really part of the instance object
console.log( "tiger hasOwnProperty: "
    ,tiger.hasOwnProperty("isLifeform") // false
    ,tiger.hasOwnProperty("isAnimal")   // false
    ,tiger.hasOwnProperty("isMammal")   // false
    ,tiger.hasOwnProperty("isCat")      // true
);

// New properties can be added to the prototypes of any
// of the "classes" above and they will be usable by the instance
Lifeform.prototype.A    = 1;
Animal.prototype.B      = 2;
Mammal.prototype.C      = 3;
Cat.prototype.D         = 4;

console.log(tiger.A, tiger.B, tiger.C, tiger.D);
// Console outputs: 1 2 3 4

// Look at the instance object again
console.log(tiger);
// You'll see it now has the "D" property
// The others are accessible but not visible (console issue?)
// In the Chrome console you should be able to drill down the __proto__ chain
// You can also look down the proto chain with Object.getPrototypeOf
// (Equivalent to tiger.__proto__)
console.log( Object.getPrototypeOf(tiger) );  // Mammal 
console.log( Object.getPrototypeOf(Object.getPrototypeOf(tiger)) ); // Animal
// Etc. to get to Lifeform

Ось ще один хороший ресурс від MDN , і ось jsfiddle, щоб ви могли його спробувати .


4

Успадкування Javascript дещо відрізняється від Java та PHP, оскільки насправді не має класів. Натомість у нього є об’єкти-прототипи, які надають методи та змінні учасників. Ви можете зв'язати ці прототипи, щоб забезпечити спадкування об'єктів. Найбільш поширена модель, яку я виявив під час дослідження цього питання, описана в Мережі розробників Mozilla . Я оновив їхній приклад, щоб включити виклик до методу суперкласу та показати журнал у попереджувальному повідомленні:

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  log += 'Shape moved.\n';
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// Override method
Rectangle.prototype.move = function(x, y) {
  Shape.prototype.move.call(this, x, y); // call superclass method
  log += 'Rectangle moved.\n';
}

var log = "";
var rect = new Rectangle();

log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);

Особисто я вважаю спадщину в Javascript незручною, але це найкраща версія, яку я знайшов.


3

ви не можете (у класичному розумінні). Javascript - це прототипічна мова. Ви помітите, що ви ніколи не оголошуєте "клас" у Javascript; ви просто визначаєте стан та методи об’єкта. Для отримання спадщини ви берете якийсь об'єкт і прототипуєте його. Прототип розширений з новою функціональністю.


1

Можна користуватися .inheritWithі .fastClass бібліотекою . Це швидше, ніж більшість популярних бібліотек, а іноді навіть швидше, ніж рідна версія.

Дуже простий у використанні:

function Super() {
   this.member1 = "superMember";//instance member
}.define({ //define methods on Super's prototype
   method1: function() { console.log('super'); } //prototype member
}.defineStatic({ //define static methods directly on Super function 
   staticMethod1: function() { console.log('static method on Super'); }
});

var Sub = Super.inheritWith(function(base, baseCtor) {
   return {
      constructor: function() {//the Sub constructor that will be returned to variable Sub
         this.member3 = 'subMember3'; //instance member on Sub
         baseCtor.apply(this, arguments);//call base construcor and passing all incoming arguments
      },
      method1: function() { 
         console.log('sub'); 
         base.method1.apply(this, arguments); //call the base class' method1 function
      }
}

Використання

var s = new Sub();
s.method1(); //prints:
//sub 
//super

1
function Person(attr){
  this.name = (attr && attr.name)? attr.name : undefined;
  this.birthYear = (attr && attr.birthYear)? attr.birthYear : undefined;

  this.printName = function(){
    console.log(this.name);
  }
  this.printBirthYear = function(){
    console.log(this.birthYear);
  }
  this.print = function(){
    console.log(this.name + '(' +this.birthYear+ ')');
  }
}

function PersonExt(attr){
  Person.call(this, attr);

  this.print = function(){
    console.log(this.name+ '-' + this.birthYear);
  }
  this.newPrint = function(){
    console.log('New method');
  }
}
PersonExt.prototype = new Person();

// Init object and call methods
var p = new Person({name: 'Mr. A', birthYear: 2007});
// Parent method
p.print() // Mr. A(2007)
p.printName() // Mr. A

var pExt = new PersonExt({name: 'Mr. A', birthYear: 2007});
// Overwriten method
pExt.print() // Mr. A-2007
// Extended method
pExt.newPrint() // New method
// Parent method
pExt.printName() // Mr. A

1

Прочитавши багато публікацій, я придумав це рішення ( тут jsfiddle ). Більшу частину часу мені не потрібно щось складніше

var Class = function(definition) {
    var base = definition.extend || null;
    var construct = definition.construct || definition.extend || function() {};

    var newClass = function() { 
        this._base_ = base;        
        construct.apply(this, arguments);
    }

    if (definition.name) 
        newClass._name_ = definition.name;

    if (definition.extend) {
        var f = function() {}       
        f.prototype = definition.extend.prototype;      
        newClass.prototype = new f();   
        newClass.prototype.constructor = newClass;
        newClass._extend_ = definition.extend;      
        newClass._base_ = definition.extend.prototype;         
    }

    if (definition.statics) 
        for (var n in definition.statics) newClass[n] = definition.statics[n];          

    if (definition.members) 
        for (var n in definition.members) newClass.prototype[n] = definition.members[n];    

    return newClass;
}


var Animal = Class({

    construct: function() {        
    },

    members: {

        speak: function() {
            console.log("nuf said");                        
        },

        isA: function() {        
            return "animal";           
        }        
    }
});


var Dog = Class({  extend: Animal,

    construct: function(name) {  
        this._base_();        
        this.name = name;
    },

    statics: {
        Home: "House",
        Food: "Meat",
        Speak: "Barks"
    },

    members: {
        name: "",

        speak: function() {
            console.log( "ouaf !");         
        },

        isA: function(advice) {
           return advice + " dog -> " + Dog._base_.isA.call(this);           
        }        
    }
});


var Yorkshire = Class({ extend: Dog,

    construct: function(name,gender) {
        this._base_(name);      
        this.gender = gender;
    },

    members: {
        speak: function() {
            console.log( "ouin !");           
        },

        isA: function(advice) {         
           return "yorkshire -> " + Yorkshire._base_.isA.call(this,advice);       
        }        
    }
});


var Bulldog = function() { return _class_ = Class({ extend: Dog,

    construct: function(name) {
        this._base_(name);      
    },

    members: {
        speak: function() {
            console.log( "OUAF !");           
        },

        isA: function(advice) {         
           return "bulldog -> " + _class_._base_.isA.call(this,advice);       
        }        
    }
})}();


var animal = new Animal("Maciste");
console.log(animal.isA());
animal.speak();

var dog = new Dog("Sultan");
console.log(dog.isA("good"));
dog.speak();

var yorkshire = new Yorkshire("Golgoth","Male");
console.log(yorkshire.isA("bad"));
yorkshire.speak();

var bulldog = new Bulldog("Mike");
console.log(bulldog.isA("nice"));
bulldog.speak();

1

Завдяки відповіді CMS і після деякого часу, коли ви поспілкувались з прототипом та Object.create, а що ні, мені вдалося придумати акуратне рішення для своєї спадщини, використовуючи застосувати, як показано тут:

var myNamespace = myNamespace || (function() {
    return {

        BaseClass: function(){
            this.someBaseProperty = "someBaseProperty";
            this.someProperty = "BaseClass";
            this.someFunc = null;
        },

        DerivedClass:function(someFunc){
            myNamespace.BaseClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "DerivedClass";
        },

        MoreDerivedClass:function(someFunc){
            myNamespace.DerivedClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "MoreDerivedClass";
        }
    };
})();


1
function Base() {
    this.doSomething = function () {
    }
}

function Sub() {
    Base.call(this); // inherit Base's method(s) to this instance of Sub
}

var sub = new Sub();
sub.doSomething();

2
Будь ласка, не просто поштовий індекс, поясніть, що це робить, і як він відповідає на питання.
Патрік Гунд

1

ES6 класи:

У Javascript немає класів. Заняття javascript - це лише синтаксична побудова цукру на вершині прототипічного успадкування шаблон що JavaScript. Ви можете використовувати JS classдля примусового успадкування прототипів, але важливо усвідомити, що ви фактично все ще використовуєте функції конструктора під кришкою.

Ці поняття також застосовуються, коли ви переходите з а es6 класу за допомогою ключового слова extends. Це просто створює додаткове посилання в ланцюзі прототипу. The__proto__

Приклад:

class Animal {
  makeSound () {
    console.log('animalSound');
  }
}

class Dog extends Animal {
   makeSound () {
    console.log('Woof');
  }
}


console.log(typeof Dog)  // classes in JS are just constructor functions under the hood

const dog = new Dog();

console.log(dog.__proto__ === Dog.prototype);   
// First link in the prototype chain is Dog.prototype

console.log(dog.__proto__.__proto__ === Animal.prototype);  
// Second link in the prototype chain is Animal.prototype
// The extends keyword places Animal in the prototype chain
// Now Dog 'inherits' the makeSound property from Animal

Object.create ()

Object.create() - це також спосіб створити спадщину в JS в JavaScript. Object.create()це функція, яка створює новий об'єкт, а приймає існуючий об'єкт як аргумент. Він призначить об'єкт, який був отриманий як аргумент__proto__ властивості новоствореного об'єкта. Знову важливо усвідомити, що ми пов'язані з прототипною парадигмою успадкування, яку втілює JS.

Приклад:

const Dog = {
  fluffy: true,
  bark: () => {
      console.log('woof im a relatively cute dog or something else??');
  }
};

const dog = Object.create(Dog);

dog.bark();


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