JavaScript'in Prototip Tabanlı Kalıtımına İyi Bir Örnek


89

10 yıldan fazla bir süredir OOP dilleriyle programlama yapıyorum ancak şimdi JavaScript öğreniyorum ve prototip tabanlı kalıtımla ilk kez karşılaşıyorum. İyi kodlar okuyarak en hızlı öğrenme eğilimindeyim. Prototip mirasını doğru şekilde kullanan bir JavaScript uygulamasının (veya kitaplığının) iyi yazılmış bir örneği nedir? Prototip kalıtımın nasıl / nerede kullanıldığını (kısaca) açıklayabilir misiniz, böylece okumaya nereden başlayacağımı bilirim?


1
Üs kütüphanesine bakma şansın oldu mu? Gerçekten güzel ve oldukça küçük. Beğendiyseniz, cevabımı cevap olarak işaretlemeyi düşünün. TIA, Roland.
Roland Bouman

Sanırım seninle aynı gemideyim. Ben de bu prototip dili hakkında biraz bilgi edinmek istiyorum, sadece oop çerçeveler veya benzerleriyle sınırlı kalmamak, harika olsa bile, öğrenmemiz gerekiyor, değil mi? Bunu benim için sadece bazı çerçeveler yapmaz, onu kullanacak olsam bile. Ancak yeni dillerde yeni yollarla yeni şeyler yaratmayı öğrenin, kutunun dışında düşünün. Senin tarzından hoşlanıyorum. Bana yardım etmeye ve belki de sana yardım etmeye çalışacağım. Bir şey bulur bulmaz size haber vereceğim.
marcelo-ferraz

Yanıtlar:


48

Douglas Crockford'un JavaScript Prototypal Mirası hakkında güzel bir sayfası var :

Beş yıl önce JavaScript'te Classical Inheritance yazdım . JavaScript'in sınıfsız, prototip bir dil olduğunu ve klasik bir sistemi simüle etmek için yeterli ifade gücüne sahip olduğunu gösterdi. O zamandan beri, her iyi programcının yapması gerektiği gibi, programlama tarzım gelişti. Prototipçiliği tamamen kucaklamayı öğrendim ve kendimi klasik modelin sınırlarından kurtardım.

Dean Edward's Base.js , Mootools's Class veya John Resig'in Simple Inheritance çalışmaları JavaScript'te klasik miras alma yollarıdır .


Sınıfsız newObj = Object.create(oldObj);istiyorsanız neden basitçe değil ? Aksi takdirde, oldObjyapıcı işlevinin prototip nesnesiyle değiştirin çalışması gerekir mi?
Cyker

76

Daha önce de belirtildiği gibi, Douglas Crockford'un filmleri neden ve nasıl olduğu hakkında iyi bir açıklama yapıyor. Ancak bunu birkaç satır JavaScript'e koymak gerekirse:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

Ancak bu yaklaşımla ilgili sorun, her yarattığınızda nesneyi yeniden oluşturmasıdır. Diğer bir yaklaşım, nesnelerinizi prototip yığını üzerinde şu şekilde bildirmektir:

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = ​function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

İç gözlem söz konusu olduğunda hafif bir dezavantaj var. Damping testOne, daha az yararlı bilgilerle sonuçlanacaktır. Ayrıca "testOne" içindeki "privateVariable" özel özelliği, tüm durumlarda paylaşılır, ayrıca shesek'in yanıtlarında yararlı bir şekilde bahsedilir.


3
Testone o notu Do privateVariablebasitçe kapsamında bir değişkendir hayatım bunun üzerine örneği özgü verileri depolamak olmamalı bu yüzden, ve onun tüm örnekleri arasında paylaşılır. (testTwo'da bu, örneğe özgüdür, çünkü her testTwo () çağrısı örnek başına yeni bir kapsam oluşturur)
shesek

Onay verdim çünkü diğer yaklaşımı gösterdin ve neden kopya çıkarıyor diye onu
kullanmıyorsun

Nesneyi her seferinde yeniden oluşturma sorunu, esas olarak her yeni nesne için yeniden oluşturulan yöntemlerden kaynaklanmaktadır. Ancak, yöntemi açıklayarak sorunu hafifletebiliriz Dog.prototype. Yani kullanmak yerine işlevin dışında da this.bark = function () {...}yapabiliriz . ( Bu cevapta daha fazla ayrıntıya bakın )Dot.prototype.bark = function () {...}Dog
C Huang

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
Belki bu bağlantıyı cevabınızla birlikte eklemek resmi daha da tamamlayabilir: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Dynom

14

YUI'ye ve Dean Edward'ın Basekütüphanesine bir göz atardım : http://dean.edwards.name/weblog/2006/03/base/

YUI için , özellikle lang modülüne hızlıca göz atabilirsiniz . YAHOO.lang.extend yöntemi. Ve sonra, bazı widget'ların veya yardımcı programların kaynağına göz atabilir ve bu yöntemi nasıl kullandıklarını görebilirsiniz.


YUI 2, 2011 itibariyle kullanımdan kaldırıldı, bu nedenle bağlantısı langyarı kesildi. YUI 3 için düzeltmek isteyen var mı?
ack

lang in yui 3 bir uzatma yöntemine sahip görünmüyor. ancak cevap uygulamayı örnek olarak kullanmayı amaçladığından sürümün önemi yoktur.
eMBee


5

Bu, Mixu'nun Düğüm kitabından ( http://book.mixu.net/node/ch6.html ) bulduğum en net örnek :

Miras yerine kompozisyonu tercih ederim:

Kompozisyon - Bir nesnenin işlevselliği, diğer nesnelerin örneklerini içererek farklı sınıfların toplamından oluşur. Kalıtım - Bir nesnenin işlevselliği, kendi işlevselliğinden ve üst sınıflarının işlevselliğinden oluşur. Kalıtıma sahip olmanız gerekiyorsa, eski JS'yi kullanın

Kalıtımı uygulamanız gerekiyorsa, en azından başka bir standart dışı uygulama / büyü işlevini kullanmaktan kaçının. Saf ES3'te makul bir kalıtım kopyasını nasıl uygulayabileceğiniz aşağıda açıklanmıştır (prototiplerde özellikleri hiçbir zaman tanımlamama kuralına uyduğunuz sürece):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

Bu, klasik kalıtım ile aynı şey değildir - ancak standart, anlaşılır bir Javascript ve insanların çoğunlukla aradığı işlevselliğe sahiptir: zincirlenebilir kurucular ve üst sınıfın yöntemlerini çağırma yeteneği.


4

ES6 classveextends

ES6 classveextends önceden olası prototip zinciri manipülasyonu için sözdizimi bu nedenle tartışmasız en kanonik kurulumdur.

İlk olarak prototip zinciri ve .mülk arama hakkında daha fazla bilgi edinin : https://stackoverflow.com/a/23877420/895245

Şimdi ne olacağını yapalım:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Önceden tanımlanmış tüm nesneler olmadan basitleştirilmiş diyagram:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

Gördüğüm en iyi örnekler Douglas Crockford'un JavaScript: The Good Parts'da . Dil hakkında dengeli bir görüş elde etmenize yardımcı olmak için kesinlikle satın almaya değer.

Douglas Crockford , JSON formatından sorumludur ve Yahoo'da JavaScript uzmanı olarak çalışmaktadır.


7
sorumluluk sahibi? kulağa neredeyse "suçlu" gibi geliyor :)
Roland Bouman

@Roland Bence JSON, veri depolamak için oldukça güzel ve ayrıntılı olmayan bir format. Yine de onu kesinlikle icat etmedi, format 2002'de Steam'deki yapılandırma ayarları için oradaydı
Chris S

Chris S, ben de öyle düşünüyorum - Keşke hepimiz XML değişim formatı olarak atlayıp hemen JSON'a geçebilseydik.
Roland Bouman

3
İcat edilecek pek bir şey yok: JSON, 1997'den beri dilde olan JavaScript'in kendi nesne değişmez sözdiziminin bir alt kümesidir.
Tim Down

@ Zaman iyi nokta - Başından beri orada olduğunu fark etmemiştim
Chris S


0

Javascript'e Prototip tabanlı kalıtım örneği ekleniyor.

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6, yapıcı ve süper anahtar kelimelerin kullanımıyla kalıtımın çok daha kolay uygulanmasını kullanır.

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.