JavaScript'te bir sınıftan nasıl miras alınır?


100

PHP / Java'da şunlar yapılabilir:

class Sub extends Base
{
}

Ve otomatik olarak Super sınıfının tüm genel / korumalı yöntemleri, özellikleri, alanları vb. Sub sınıfın bir parçası haline gelir ve gerekirse geçersiz kılınabilir.

Bunun Javascript'teki karşılığı nedir?





Crockford yöntemi hala çalışıyor mu? ZParenizor.inherits (Parenizor);
Loren Shqipognja

Yanıtlar:


80

Şimdi bunu yapma şeklimi değiştirdim, yapıcı işlevlerini ve özelliklerini kullanmaktan kaçınmaya çalışıyorum prototype, ancak 2010'daki eski cevabım hala altta. Şimdi tercih ediyorum Object.create(). Object.createtüm modern tarayıcılarda mevcuttur.

Bunun Object.creategenellikle bir işlev yapıcısı ile kullanmaktan çok daha yavaş olduğunu not etmeliyim 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 kullanmanın en büyük avantajlarından biri, bir defineProperties argümanını geçirebilmektir , bu da size sınıftaki özelliklere nasıl erişilip numaralandırılabileceği konusunda önemli bir kontrol sağlar ve ayrıca örnekler oluşturmak için işlevleri kullanıyorum, bunlar şu işlevi görür bir bakıma yapıcılar, çünkü sadece örneği döndürmek yerine sonunda başlatma yapabilirsiniz.

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

Bu, 2010'daki orijinal cevabım:

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 değerine ne dersiniz? Bence alt değere de ayarlanmalıdır.
maximus


@MOnsDaR Onu Base
Bjorn olarak

alert()Neyin instance.showColor()getirisini görmek için kullanırsam , hala alıyorum undefined. jsbin.com/uqalin/1
MOnsDaR

1
@MOnsDaR, çünkü konsol günlükleri tutuyor, uyarının gösterilmesi için hiçbir şey döndürmüyor. ShowColor'da bir return ifadesi görüyor musunuz?
Bjorn

191

JavaScript'te sınıflarınız yoktur, ancak kalıtım ve davranışın birçok şekilde yeniden kullanılmasını sağlayabilirsiniz:

Sözde klasik kalıtım (prototipleme yoluyla):

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

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

newOperatörle birlikte kullanılmalıdır :

var subInstance = new Sub();

İşlev uygulaması veya "yapıcı zinciri oluşturma":

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


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

Bu yaklaşım aynı zamanda newoperatörle birlikte kullanılmalıdır :

var subInstance = new Sub();

İlk örnekle fark ne zaman olduğunu yapıcı nesne içine , bu tahsis özellikler ekler üzerinde doğrudan yeni örneği, örneğin özelliklerini içeren ve direkt olarak ( ).applySuperthisSubthisSupersubInstancemember1member2subInstance.hasOwnProperty('member1') == true;

İlk örnekte, bu özelliklere prototip zinciri aracılığıyla ulaşılır , bunlar dahili bir [[Prototype]]nesnede bulunur.

Parazitik miras veya Güç Oluşturucular:

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

  return obj;
}

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

Bu yaklaşım temelde "nesne büyütmeye" dayanmaktadır, newoperatörü kullanmanıza gerek yoktur ve gördüğünüz gibi thisanahtar kelime dahil değildir.

var subInstance = createSub();

ECMAScript 5. Baskı. Object.createyöntem:

// 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';

Yukarıdaki yöntem, Crockford tarafından önerilen bir prototip kalıtım tekniğidir .

Nesne örnekleri diğer nesne örneklerinden miras alır, hepsi bu.

Çünkü kalıtsal özellikler, baştan yeni nesne örnekleri kopyalanmaz, çünkü bu teknik daha iyi basit bir "nesne büyütme" den olabilir taban nesne olarak ayarlanır [[Prototype]]ait genişletilmiş nesne Yukarıdaki örnekte de, subInstancefiziksel olarak sadece içeren member3özellik.


3
miras için örnekleri kullanmayın - doğrudan prototip nesnesinden miras almak için ES5 Object.create()veya özel bir clone()işlev (ör. mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) kullanın; için yorumları görmek stackoverflow.com/questions/1404559/... bir açıklama
Christoph

Teşekkürler Object.create
@Christoph

1
Super'in örnek üyelerini Sub'ın prototipinde bulunduracağınız için bu uygun bir miras değildir. Dolayısıyla, Sub'ın tüm örnekleri aynı member1değişkeni paylaşacak ve bu hiç de istenmiyor. Elbette yeniden yazabilirler, ancak bu hiç mantıklı değil. github.com/dotnetwise/Javascript-FastClass daha iyi bir şeker çözümüdür.
Adaptabi

Merhaba @CMS, lütfen açıklar mısınız, alt sınıf için kalıtımı ayarlamak için neden ilk örnekte üst sınıfın örneğini oluşturmam gerekiyor? Ben bu hat bahsediyorum: Sub.prototype = new Super();. Ya komut dosyası yürütme sırasında her iki sınıf da asla kullanılmayacaksa? Performans sorunu gibi görünüyor. Çocuk sınıfı gerçekten kullanılmıyorsa neden ebeveyn sınıfı oluşturmam gerekiyor? Lütfen detaylandırır mısınız? İşte sorunun basit gösterimi: jsfiddle.net/slavafomin/ZeVL2 Teşekkürler!
Slava Fomin II

Tüm örneklerde - sonuncusu hariç - Süper için bir "sınıf" ve Sub için bir "sınıf" vardır ve ardından bir Sub örneğini oluşturursunuz. Object.create örneği için karşılaştırılabilir bir örnek ekleyebilir misiniz?
Luke

49

Bu sayfaya 2019 yılı ve sonrasında ulaşanlar için

ECMAScript standardının (ES6) en son sürümü ile anahtar kelimeyi kullanabilirsiniz class.

Sınıf tanımının normal olmadığını unutmayın object; dolayısıyla sınıf üyeleri arasında virgül yoktur. Bir sınıfın bir örneğini oluşturmak için newanahtar kelimeyi kullanmanız gerekir . Bir temel sınıftan miras almak için şunu kullanın 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'

Bir temel sınıftan miras almak için şunu kullanın 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

Türetilmiş sınıftan, temel sınıfına erişmek için herhangi bir yapıcıdan veya yöntemden super'i kullanabilirsiniz:

  • Ana kurucuyu çağırmak için şunu kullanın: super().
  • Başka bir üyeyi aramak için, örneğin, kullanın super.getName().

Sınıfları kullanmanın daha fazlası var. Konuyu daha derinlemesine incelemek istiyorsanız, Dr. Axel Rauschmayer'in " ECMAScript 6'daki Sınıflar " ı tavsiye ederim . *

kaynak


1
Kapağın altında, classve extendsprototip zinciri için (son derece yararlı) sözdizimi şeker: stackoverflow.com/a/23877420/895245
Ciro Santilli郝海东冠状病六四事件法轮功

sadece bilginiz için 'instance.name' burada 'mycar.name' sınıfın adını döndürecektir. Bu, ES6 ve ESnext'in varsayılan davranışıdır. Mycar.name için burada 'Araç' dönecek
Shiljo Paulson

7

JavaScript'te "sınıf kalıtımı" yoktur, sadece "prototip kalıtımı" vardır. Yani bir sınıf "kamyon" yapıp onu "otomobil" alt sınıfı olarak işaretlemezsiniz. Bunun yerine, bir nesne "Jack" yaparsınız ve prototip olarak "John" kullandığını söylersiniz. John "4 + 4" ün ne kadar olduğunu biliyorsa, Jack de bilir.

Douglas Crockford'un prototip kalıtım hakkındaki makalesini buradan okumanızı öneririm: http://javascript.crockford.com/prototypal.html Ayrıca JavaScript'in diğer OO dillerinde olduğu gibi nasıl "benzer" kalıtıma sahip olmasını sağlayabileceğinizi gösterir ve ardından bunu açıklar aslında javaScript'i kullanılması amaçlanmayan bir şekilde kırmak anlamına gelir.


Jack'in prototipinin John olduğunu varsayalım. Çalışma zamanı sırasında John'a bir özellik / davranış ekledim. Bu özelliği / davranışı Jack'ten alacak mıyım?
Ram Bavireddi

Elbette yapacaksın. Örneğin, insanlar genellikle tüm dize nesnelerine "trim ()" yöntemini bu şekilde ekler (yerleşik değildir) Burada bir örneğe bakın: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
naivistler

6

Bu alıntıyı en aydınlatıcı buluyorum:

Özünde, bir JavaScript "sınıfı" yalnızca bir yapıcı ve ekli bir prototip nesnesi olarak hizmet eden bir Function nesnesidir. ( Kaynak: Guru Katz )

Nesneler yerine kurucuları kullanmayı seviyorum, bu nedenle burada CMS tarafından açıklanan "sözde klasik kalıtım" yöntemine tarafım . İşte bir prototip zinciri ile çoklu kalıtım örneği :

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

İşte MDN'den başka bir iyi kaynak ve işte bir jsfiddle, böylece deneyebilirsiniz .


4

Javascript kalıtımı Java ve PHP'den biraz farklıdır, çünkü gerçekten sınıfları yoktur. Bunun yerine, yöntemler ve üye değişkenler sağlayan prototip nesnelerine sahiptir. Nesne mirası sağlamak için bu prototipleri zincirleyebilirsiniz. Bu soruyu araştırırken bulduğum en yaygın model Mozilla Geliştirici Ağı'nda açıklanmıştır . Örneğini, bir üst sınıf yöntemine çağrı içerecek ve günlüğü bir uyarı mesajında ​​gösterecek şekilde güncelledim:

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

Şahsen, Javascript'teki kalıtımı garip buluyorum, ancak bu bulduğum en iyi sürüm.


3

yapamazsınız (klasik anlamda). Javascript, prototip bir dildir. Javascript'te asla bir "sınıf" ilan etmediğinizi gözlemleyeceksiniz; yalnızca bir nesnenin durumunu ve yöntemlerini tanımlarsınız. Kalıtım üretmek için bir nesneyi alıp prototipini oluşturursunuz. Prototip, yeni işlevlerle genişletildi.


1

.inheritWithVe .fastClass kitaplık kullanabilirsiniz . Çoğu popüler kitaplıktan ve hatta bazen yerel sürümden daha hızlıdır.

Kullanımı çok kolay:

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

Kullanım

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

Birçok yazıyı okuduktan sonra bu çözümü buldum ( burada jsfiddle ). Çoğu zaman daha karmaşık bir şeye ihtiyacım yok

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'nin cevabı sayesinde ve bir süre prototip ve Object.create ile uğraştıktan sonra, burada gösterildiği gibi application'ı kullanarak mirasım için temiz bir çözüm bulabildim:

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
Lütfen sadece kod yazmayın, ne yaptığını ve soruyu nasıl yanıtladığını açıklayın.
Patrick Hund

1

ES6 sınıfları:

Javascript'in sınıfları yoktur. Javascript'teki sınıflar, javascript'in prototip kalıtım modelinin üzerine inşa edilmiş sözdizimsel şekerlerdir . classJS'yi prototip kalıtımını zorlamak için kullanabilirsiniz, ancak aslında kaputun altındaki yapıcı işlevlerini kullandığınızın farkında olmak önemlidir.

Bu kavramlar es6, extends anahtar sözcüğünü kullanarak bir 'sınıftan' genişlettiğinizde de geçerlidir . Bu sadece prototip zincirinde ek bir bağlantı oluşturur. __proto__

Misal:

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()ayrıca javascript'te JS'de miras oluşturmanın bir yoludur. Object.create()yeni bir nesne oluşturan, mevcut bir nesneyi argüman olarak alan bir işlevdir. Argüman olarak alınan __proto__nesneyi, yeni oluşturulan nesnenin özelliğine atayacaktır . Yine, JS'nin içerdiği prototip miras paradigmasına bağlı olduğumuzu fark etmek önemlidir.

Misal:

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

const dog = Object.create(Dog);

dog.bark();


0

JavaScript'te sınıf olmadığı için JavaScript'teki bir sınıftan miras alamazsınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.