Bagaimana cara mewarisi dari kelas di javascript?

99

Di PHP / Java seseorang dapat melakukan:

class Sub extends Base
{
}

Dan secara otomatis semua metode publik / dilindungi, properti, bidang, dll dari kelas Super menjadi bagian dari Sub kelas yang dapat diganti jika perlu.

Apa yang setara dengan itu di Javascript?

Klik Suka
sumber
1
Lihat di sini: stackoverflow.com/questions/1908443/…
Darin Dimitrov
Apakah cara crockford itu masih berhasil? ZParenizor.inherits (Parenizor);
Loren Shqipognja
Lihat juga: Kelas Memperluas JavaScript
user56reinstatemonica8

Jawaban:

80

Saya telah mengubah cara saya melakukan ini sekarang, saya mencoba menghindari penggunaan fungsi konstruktor dan prototypepropertinya, tetapi jawaban lama saya dari tahun 2010 masih ada di bagian bawah. Saya sekarang lebih suka Object.create(). Object.createtersedia di semua browser modern.

Saya harus mencatat bahwa Object.createbiasanya jauh lebih lambat daripada menggunakan newdengan konstruktor fungsi.

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

Salah satu manfaat besar menggunakan Object.create adalah dapat meneruskan argumen defineProperties , yang memberi Anda kontrol signifikan atas bagaimana properti di kelas dapat diakses dan dihitung, dan saya juga menggunakan fungsi untuk membuat instance, ini berfungsi sebagai konstruktor dengan cara, karena Anda bisa melakukan inisialisasi di akhir, bukan hanya mengembalikan instance.

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

Ini adalah jawaban asli saya dari tahun 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"
Bjorn
sumber
5
Bagaimana dengan nilai sub.prototype.constructor? Saya pikir itu harus diatur juga ke nilai sub.
maximus
Selain Anda menggunakan kata kunci yang dipesan ('super') sebagai nama kelas, saya tidak bisa menjalankan contoh Anda: jsbin.com/ixiyet/8/edit
MOnsDaR
@MOnsDaR Saya mengganti namanya menjadi Base
Bjorn
Jika saya gunakan alert()untuk melihat instance.showColor()pengembalian apa yang masih saya dapatkan undefined. jsbin.com/uqalin/1
MOnsDaR
1
@MOnsDaR itu karena konsol log, tidak mengembalikan apa pun untuk peringatan untuk ditampilkan. Apakah Anda melihat pernyataan return di showColor?
Bjorn
190

Di JavaScript Anda tidak memiliki kelas tetapi Anda bisa mendapatkan warisan dan perilaku yang digunakan kembali dalam banyak cara:

Warisan pseudo-klasik (melalui pembuatan prototipe):

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

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

Harus digunakan dengan newoperator:

var subInstance = new Sub();

Aplikasi fungsi atau "rangkaian konstruktor":

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


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

Pendekatan ini juga harus digunakan dengan newoperator:

var subInstance = new Sub();

Perbedaannya dengan contoh pertama adalah ketika kita applymenjadi Superkonstruktor ke thisobjek di dalamnya Sub, ia menambahkan properti yang ditugaskan ke thison Super, langsung pada instance baru, misalnya subInstanceberisi properti member1dan member2langsung ( subInstance.hasOwnProperty('member1') == true;).

Dalam contoh pertama, properti tersebut dicapai melalui rantai prototipe , mereka ada di [[Prototype]]objek internal .

Warisan parasit atau Pembangun Daya:

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

  return obj;
}

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

Pendekatan ini pada dasarnya didasarkan pada "penambahan objek", Anda tidak perlu menggunakan newoperator, dan seperti yang Anda lihat, thiskata kunci tidak terlibat.

var subInstance = createSub();

ECMAScript Edisi ke-5. Object.createmetode:

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

Metode di atas adalah teknik pewarisan prototipe yang diusulkan oleh Crockford .

Contoh objek mewarisi dari contoh objek lain, itu saja.

Teknik ini bisa lebih baik daripada sederhana "objek augmentation" karena sifat diwariskan tidak disalin semua contoh objek baru, karena dasar objek ditetapkan sebagai [[Prototype]]dari diperpanjang objek, dalam contoh di atas subInstancemengandung fisik hanya member3properti.

CMS
sumber
3
jangan gunakan instance untuk pewarisan - gunakan ES5 Object.create()atau clone()fungsi kustom (misalnya mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) untuk mewarisi langsung dari objek prototipe; lihat komentar ke stackoverflow.com/questions/1404559/… untuk penjelasannya
Christoph
Terima kasih @Christoph, saya akan menyebutkan Object.createmetode ini :)
CMS
1
Ini bukan warisan yang tepat, karena Anda akan memiliki anggota contoh Super pada prototipe Sub. Karenanya semua instance Sub akan berbagi member1variabel yang sama , yang tidak diinginkan sama sekali. Tentu saja mereka bisa menulis ulang, tapi itu tidak masuk akal. github.com/dotnetwise/Javascript-FastClass adalah solusi gula yang lebih baik.
Adaptabi
Halo @CMS, bisakah Anda menjelaskan, mengapa saya perlu membuat instance kelas induk pada contoh pertama untuk menyiapkan warisan untuk sub-kelas? Aku sedang berbicara tentang baris ini: Sub.prototype = new Super();. Bagaimana jika kedua kelas tidak akan pernah digunakan selama eksekusi skrip? Sepertinya masalah kinerja. Mengapa saya perlu membuat kelas induk jika kelas anak tidak benar-benar digunakan? Bisakah Anda menjelaskan lebih lanjut? Berikut demonstrasi sederhana dari masalah ini: jsfiddle.net/slavafomin/ZeVL2 Terima kasih!
Slava Fomin II
Dalam semua contoh - kecuali yang terakhir - terdapat "kelas" untuk Super dan "kelas" untuk Sub, lalu Anda membuat instance Sub. Bisakah Anda menambahkan contoh yang sebanding untuk contoh Object.create?
Lukas
49

Bagi mereka yang mencapai halaman ini pada 2019 atau setelahnya

Dengan versi standar ECMAScript (ES6) terbaru , Anda dapat menggunakan kata kunci class.

Perhatikan bahwa definisi kelas bukanlah regular object; karenanya tidak ada koma di antara anggota kelas. Untuk membuat instance kelas, Anda harus menggunakan newkata kunci. Untuk mewarisi dari kelas dasar, gunakan 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'

Untuk mewarisi dari kelas dasar, gunakan 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

Dari kelas turunan, Anda dapat menggunakan super dari konstruktor atau metode apa pun untuk mengakses kelas dasarnya:

  • Untuk memanggil konstruktor induk, gunakan super().
  • Untuk memanggil anggota lain, gunakan, misalnya super.getName(),.

Ada lebih banyak hal menggunakan kelas. Jika Anda ingin menggali lebih dalam tentang subjek ini, saya merekomendasikan “ Classes in ECMAScript 6 ” oleh Dr. Axel Rauschmayer. *

sumber

Merlin
sumber
1
Di bawah tenda, classdan extendsgula sintaks (sangat berguna) untuk rantai prototipe: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
hanya untuk informasi Anda 'instance.name' di sini 'mycar.name' akan mengembalikan nama kelas. Ini adalah perilaku default ES6 dan ESnext. Di sini untuk mycar.name akan mengembalikan 'Kendaraan'
Shiljo Paulson
7

Nah, di JavaScript tidak ada "class inheritance", yang ada hanya "prototype inheritance". Jadi Anda tidak membuat kelas "truk" dan kemudian menandainya sebagai subkelas dari "mobil". Sebaliknya, Anda membuat objek "Jack" dan mengatakan bahwa objek tersebut menggunakan "John" sebagai prototipe. Jika John tahu, berapa "4 + 4" itu, maka Jack juga mengetahuinya.

Saya sarankan Anda membaca artikel Douglas Crockford tentang warisan prototipe di sini: http://javascript.crockford.com/prototypal.html Dia juga menunjukkan bagaimana Anda dapat membuat JavaScript memiliki warisan "serupa" seperti dalam bahasa OO lainnya dan kemudian menjelaskan bahwa ini sebenarnya berarti memecah javaScript dengan cara yang tidak dimaksudkan untuk digunakan.

orang naif
sumber
Mari kita asumsikan prototipe Jack adalah John. Selama run-time saya menambahkan properti / perilaku ke John. Apakah saya akan mendapatkan properti / perilaku itu dari Jack?
Ram Bavireddi
Anda pasti akan melakukannya. Misalnya, ini adalah cara orang biasanya menambahkan metode "trim ()" ke semua objek string (ini bukan built-in) Lihat contoh di sini: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
naif
6

Saya menemukan kutipan ini sebagai yang paling mencerahkan:

Intinya, "kelas" JavaScript hanyalah sebuah objek Fungsi yang berfungsi sebagai konstruktor ditambah objek prototipe yang dilampirkan. ( Sumber: Guru Katz )

Saya lebih suka menggunakan konstruktor daripada objek, jadi saya lebih memilih metode "warisan klasik semu" yang dijelaskan di sini oleh CMS . Berikut adalah contoh beberapa pewarisan dengan rantai prototipe :

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

Ini adalah sumber bagus lainnya dari MDN , dan ini adalah jsfiddle sehingga Anda dapat mencobanya .

Luke
sumber
4

Pewarisan Javascript sedikit berbeda dari Java dan PHP, karena tidak benar-benar memiliki kelas. Sebaliknya ia memiliki objek prototipe yang menyediakan metode dan variabel anggota. Anda dapat merangkai prototipe tersebut untuk memberikan warisan objek. Pola paling umum yang saya temukan saat meneliti pertanyaan ini dijelaskan di Jaringan Pengembang Mozilla . Saya telah memperbarui contoh mereka untuk menyertakan panggilan ke metode superclass dan untuk menampilkan log dalam pesan peringatan:

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

Secara pribadi, saya menemukan warisan dalam Javascript canggung, tetapi ini adalah versi terbaik yang saya temukan.

Don Kirkby
sumber
3

Anda tidak bisa (dalam pengertian klasik). Javascript adalah bahasa prototipe. Anda akan melihat bahwa Anda tidak pernah mendeklarasikan sebuah "class" dalam Javascript; Anda hanya mendefinisikan status dan metode suatu objek. Untuk menghasilkan warisan, Anda mengambil beberapa objek dan membuat prototipe. Prototipe diperluas dengan fungsionalitas baru.

DC
sumber
1

Kamu bisa memakai .inheritWith dan .fastClass perpustakaan . Ini lebih cepat dari kebanyakan pustaka populer dan terkadang bahkan lebih cepat dari versi aslinya.

Sangat mudah digunakan:

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

Pemakaian

var s = new Sub();
s.method1(); //prints:
//sub 
//super
Adaptabi
sumber
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
nnattawat
sumber
1

Setelah membaca banyak posting, saya menemukan solusi ini ( jsfiddle di sini ). Seringkali saya tidak membutuhkan sesuatu yang lebih canggih

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();
chauwel
sumber
1

Berkat jawaban CMS dan setelah mengotak-atik beberapa saat dengan prototipe dan Object.create dan yang tidak, saya dapat menemukan solusi yang rapi untuk warisan saya menggunakan apply seperti yang ditunjukkan di sini:

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";
        }
    };
})();
pasx
sumber
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();
Kai Hartmann
sumber
2
Tolong jangan hanya memposting kode, jelaskan apa fungsinya dan bagaimana cara menjawab pertanyaan.
Patrick Hund
1

Kelas ES6:

Javascript tidak memiliki kelas. Kelas dalam javascript hanyalah gula sintaksis yang dibangun di atas pola pewarisan prototipe javascript. Anda dapat menggunakan JS classuntuk menerapkan pewarisan prototipe tetapi penting untuk disadari bahwa Anda sebenarnya masih menggunakan fungsi konstruktor.

Konsep-konsep ini juga berlaku ketika Anda memperluas dari a es6 'kelas' menggunakan kata kunci extends. Ini hanya membuat tautan tambahan dalam rantai prototipe. Itu__proto__

Contoh:

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()juga merupakan cara untuk membuat warisan di JS dalam javascript. Object.create()adalah fungsi yang membuat objek baru, mengambil objek yang sudah ada sebagai argumen. Ini akan menetapkan objek yang diterima sebagai argumen ke__proto__ properti objek yang baru dibuat. Sekali lagi, penting untuk menyadari bahwa kita terikat pada paradigma pewarisan prototipe yang diwujudkan JS.

Contoh:

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

const dog = Object.create(Dog);

dog.bark();

Willem van der Veen
sumber
0

Anda tidak dapat mewarisi dari kelas di JavaScript, karena tidak ada kelas di JavaScript.

Jörg W Mittag
sumber