Javascript kalıtımı: süper yapıcı mı yoksa prototip zinciri mi kullanılıyor?


82

Son zamanlarda MDC'de JavaScript çağrı kullanımını okudum

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

aşağıda gösterilen örneğin bir bağlantısını hala anlamıyorum.

Neden burada mirası böyle kullanıyorlar?

Prod_dept.prototype = new Product();

bu gerekli mi? Çünkü içinde süper yapıcıya bir çağrı var

Prod_dept()

neyse, böyle

Product.call

bu sıradan bir davranış mı? Ne zaman süper yapıcı için çağrı kullanmak veya prototip zincirini kullanmak daha iyidir?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

İşleri netleştirdiğiniz için teşekkürler


Bunu kullanma şekliniz neredeyse doğrudur, ancak yeni anahtar sözcüğü kullanarak temeli örneklemek yerine Object.create () kullanmak isteyebilirsiniz (temel oluşturucu bağımsız değişkenlere ihtiyaç duyarsa sorunlara neden olabilir). Blogumla
Jon

2
Ayrıca, Product () işlevinin etkili bir şekilde iki kez çağrıldığını unutmayın.
event_jr

Yanıtlar:


109

Gerçek sorunun cevabı, ikisini birden yapmanız gerektiğidir:

  • Prototipin üst öğenin bir örneğine ayarlanması, prototip zincirini başlatır (miras), bu yalnızca bir kez yapılır (prototip nesnesi paylaşıldığı için).
  • Ebeveynin yapıcısının çağrılması nesnenin kendisini başlatır, bu her örneklemede yapılır (her oluşturduğunuzda farklı parametreler geçirebilirsiniz).

Bu nedenle, devralmayı ayarlarken ebeveynin kurucusunu çağırmamalısınız. Yalnızca bir başkasından miras alan bir nesnenin örneğini oluştururken.

Chris Morgan'ın cevabı neredeyse tamamlandı, küçük bir ayrıntı (yapıcı özelliği) eksik. Devralmayı kurmak için bir yöntem önereyim.

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

Dersler oluştururken daha fazla sözdizimsel şeker için blog gönderime bakın. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Teknik Ext-JS ve http://www.uselesspickles.com/class_library/ ' den kopyalanmış ve https://stackoverflow.com/users/1397311/ccnokes adresinden bir yorum


6
EcmaScript5 + 'da (tüm modern tarayıcılar), bunu şu şekilde tanımlarsanız numaralandırılamaz hale getirebilirsiniz Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); Bu şekilde, javascript bir işlevin yeni bir örneğini oluşturduğunda (yapıcı numaralandırılabilir olarak ayarlanmıştır) tam olarak aynı "davranışı" elde edersiniz = otomatik olarak yanlış)
Adaptabi

2
Uzatma yöntemini iki satıra basitleştiremez misiniz? Yani: sub.prototype = Object.create (base.prototype); sub.prototype.constructor = alt;
Appetere

@Steve Evet, bunu ilk yazdığımda, Object.createpek desteklenmiyordu ... onu güncelleyebilirsin. Çoğu Object.createçoklu dolgunun, başlangıçta gösterdiğim teknik kullanılarak uygulandığını unutmayın .
Juan Mendes

1
Öyleyse, yalnızca alt nesneye ve örneklerine, bu durumda "Köpek" nesnesine yöntemler eklemek isteseydim, iki prototipi genişletme işlevinizde şu şekilde birleştirir miydiniz : jsfiddle.net/ccnokes/75f9P ?
ccnokes

1
@ elad.chen Cevap zincirlerinde anlattığım yaklaşım prototip, mixinler genellikle tüm özellikleri prototipe değil bir örneğe kopyalar. Stackoverflow.com/questions/7506210/… sayfasına
Juan Mendes

30

Bunu yapmak için ideal bir yol olduğunu değil yapmak Prod_dept.prototype = new Product();bu çağrıları, çünkü Productyapıcı. Dolayısıyla ideal yol, kurucu dışında onu klonlamaktır, bunun gibi bir şey:

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

Daha sonra, inşa sırasında süper kurucu çağrılır, bu da istediğiniz şeydir, çünkü o zaman parametreleri de geçirebilirsiniz.

Google Kapatma Kitaplığı gibi şeylere bakarsanız, nasıl yaptıklarını görürsünüz.


Bu kurucuya vekil kurucuyu devralmak için kullanılan diyorum. Örneğiniz, mirası kurduktan sonra hala yapıcı özelliğini sıfırlamayı unutuyor, böylece yapıcıyı doğru şekilde algılayabilirsiniz
Juan Mendes

1
@Juan: Tamam, eklenmek için güncellendi Prod_dept.prototype.constructor = Prod_dept;.
Chris Morgan

@ChrisMorgan ben sorun numunede son satırı anlama yaşıyorum: Prod_dept.prototype.constructor = Prod_dept;. Her şeyden önce, neden gerekli ve neden Prod_deptyerine işaret ediyor Product?
Lasse Christiansen

1
@ LasseChristiansen-sw_lasse: Prod_dept.prototypeçıktısının prototipi olarak kullanılacak şeydir new Prod_dept(). (Tipik olarak bu prototip, instance.__proto__bu bir uygulama ayrıntısı olmasına rağmen mevcuttur .) constructorNedenine gelince — bu dilin standart bir parçasıdır ve bu nedenle tutarlılık için sağlanmalıdır; varsayılan olarak doğrudur, ancak prototipi tamamen değiştirdiğimiz için doğru değeri tekrar atamalıyız, aksi takdirde bazı şeyler mantıklı olmayacaktır (bu durumda, bir Prod_deptörneğin sahip olacağı anlamına gelir this.constructor == Product, ki bu kötüdür).
Chris Morgan

6

JavaScript'te Nesne Yönelimli Programlama yaptıysanız, aşağıdaki gibi bir sınıf oluşturabileceğinizi bileceksiniz:

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

Şimdiye kadar sınıf insanımızın sadece iki özelliği var ve ona bazı yöntemler vereceğiz. Bunu yapmanın temiz bir yolu, 'prototip' nesnesini kullanmaktır. JavaScript 1.1'den başlayarak, prototip nesnesi JavaScript'te tanıtıldı. Bu, bir nesnenin tüm örneklerine özel özellikler ve yöntemler ekleme sürecini basitleştiren yerleşik bir nesnedir. Aşağıdaki gibi 'prototip' nesnesini kullanarak sınıfımıza 2 yöntem ekleyelim:

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

Şimdi sınıf Kişimizi tanımladık. Kişiden bazı özellikleri devralan Manager adında başka bir sınıf tanımlamak istersek ne olur? Yönetici sınıfımızı tanımladığımızda tüm bu özellikleri yeniden tanımlamanın bir anlamı yok, onu sadece Person sınıfından miras alacak şekilde ayarlayabiliriz. JavaScript'in yerleşik mirası yoktur, ancak kalıtımı uygulamak için aşağıdaki gibi bir teknik kullanabiliriz:

Inheritance_Manager = {};// Bir miras yöneticisi sınıfı oluşturuyoruz (isim keyfi)

Şimdi miras sınıfımıza baseClass ve subClassas argümanlarını alan ext adında bir yöntem verelim. Extend yöntemi içinde, miras işlevi miras () {} adında bir iç sınıf oluşturacağız. Bu iç sınıfı kullanmamızın nedeni, baseClass ve subClass prototipleri arasındaki karışıklığı önlemektir. Daha sonra, miras sınıfımızın prototipini aşağıdaki kodda olduğu gibi baseClass prototipine işaret ediyoruz: miras.prototype = baseClass. prototip; Daha sonra miras prototipini aşağıdaki gibi alt sınıf prototipine kopyalıyoruz: subClass.prototype = new inheritance (); Sonraki şey, alt sınıfımız için yapıcıyı şu şekilde belirtmektir: subClass.prototype.constructor = subClass; Alt sınıf prototiplememizi bitirdikten sonra, bazı temel sınıf işaretçileri ayarlamak için sonraki iki kod satırını belirleyebiliriz.

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

İşte uzatma fonksiyonumuzun tam kodu:

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

Artık mirasımızı uyguladığımıza göre, onu sınıflarımızı genişletmek için kullanmaya başlayabiliriz. Bu durumda, Kişi sınıfımızı aşağıdaki gibi bir Yönetici sınıfına genişleteceğiz:

Yönetici sınıfını tanımlıyoruz

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

Onu kişiden miras almasını sağlarız

Inheritance_Manager.extend(Manager, Person);

Fark ettiyseniz, Inheritance_Manager sınıfımızın ext yöntemini çağırdık ve bizim durumumuzda subClass Manager'ı ve ardından baseClass Person'u geçtik. Burada sıranın çok önemli olduğunu unutmayın. Onları değiştirirseniz, miras hiç de istediğiniz gibi çalışmayacaktır. Ayrıca, alt sınıfımızı gerçekten tanımlamadan önce bu kalıtımı belirtmeniz gerekeceğini unutmayın. Şimdi alt sınıfımızı tanımlayalım:

Aşağıdaki gibi daha fazla yöntem ekleyebiliriz. Manager sınıfımız, ondan miras aldığı için her zaman Person sınıfında tanımlanan yöntemlere ve özelliklere sahip olacaktır.

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

Şimdi test etmek için, biri Kişi sınıfından ve diğeri miras alınan sınıf Yöneticisinden olmak üzere iki nesne oluşturalım:

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

Şu adresten tam kod ve daha fazla yorum almaktan çekinmeyin: http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx

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.