Как наследовать от класса в javascript?


100

В PHP / Java можно:

class Sub extends Base
{
}

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

Что эквивалентно этому в Javascript?





Этот способ Крокфорда все еще работает? ZParenizor.inherits (Паренизор);
Loren Shqipognja 07

Ответы:


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

Помимо того, что вы используете зарезервированные ключевые слова ('super') в качестве имен
классов

@MOnsDaR Я переименовал его в Base
Bjorn

Если я использую, alert()чтобы посмотреть, что instance.showColor()возвращается, я все равно получаю undefined. jsbin.com/uqalin/1
MOnsDaR

1
@MOnsDaR, потому что консольные журналы не возвращают никаких предупреждений. Вы видите выражение возврата в showColor?
Bjorn

191

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

Разница с первым примером является то , что , когда мы конструктор для объекта внутри , он добавляет свойства , назначенные на непосредственно на новом экземпляре, например , содержит свойство и непосредственно ( ).applySuperthisSubthisSupersubInstancemember1member2subInstance.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метод :)
Christian C. Salvadó

1
Это неправильное наследование, так как у вас будут члены экземпляра Super в прототипе Sub. Следовательно, все экземпляры Sub будут использовать одну и ту же member1переменную, что совершенно нежелательно. Конечно, они могут его переписать, но в этом нет смысла. github.com/dotnetwise/Javascript-FastClass - лучшее решение для сахара.
Adaptabi

Привет @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().
  • Чтобы позвонить другому участнику, используйте, например super.getName(),.

Есть еще кое-что об использовании классов. Если вы хотите углубиться в тему, я рекомендую « Классы в ECMAScript 6 » доктора Акселя Раушмайера. *

источник


1
Под капотом classи extendsесть (очень полезный) синтаксический сахар для цепочки прототипов: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

просто для вашей информации «instance.name» здесь «mycar.name» вернет имя класса. Это поведение ES6 и ESnext по умолчанию. Здесь для mycar.name будет возвращено 'Vehicle'
Шилджо Полсон 08

7

Ну, в JavaScript нет «наследования классов», есть только «наследование прототипа». Таким образом, вы не создаете класс «грузовик», а затем помечаете его как подкласс «автомобиль». Вместо этого вы создаете объект «Джек» и говорите, что он использует «Джон» в качестве прототипа. Если Джон знает, сколько "4 + 4", то и Джек это знает.

Я предлагаю вам прочитать статью Дугласа Крокфорда о прототипном наследовании здесь: http://javascript.crockford.com/prototypal.html Он также показывает, как вы можете сделать JavaScript "похожим" на наследование, как в других объектно-ориентированных языках, а затем объясняет, что это на самом деле означает нарушение javaScript не предназначенным для использования способом.


Предположим, прототип Джека - Джон. Во время выполнения я добавил Джону свойство / поведение. Получу ли я это свойство / поведение от Джека?
Рам Бавиредди

Вы обязательно это сделаете. Например, именно так люди обычно добавляют метод «trim ()» ко всем строковым объектам (он не является встроенным). См. Пример здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
наивисты 07

6

Я считаю эту цитату наиболее поучительной:

По сути, «класс» JavaScript - это просто объект Function, который служит конструктором, плюс присоединенный объект-прототип. ( Источник: Гуру Кац )

Мне нравится использовать конструкторы, а не объекты, поэтому я неравнодушен к методу «псевдоклассического наследования», описанному здесь 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, а также с другими вещами, я смог придумать изящное решение для моего наследования с использованием apply, как показано здесь:

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. Это просто создает дополнительное звено в цепочке прототипов. В__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.