JavaScript'te 'prototip' veya 'bu' kullanımı?


776

Arasındaki fark nedir

var A = function () {
    this.x = function () {
        //do something
    };
};

ve

var A = function () { };
A.prototype.x = function () {
    //do something
};


BU anahtar kelime kavramı açıkça burada açıklanmıştır scotch.io/@alZami/understanding-this-in-javascript
AL-zami

1
"Bu" iş parçacığının okunması JS'nin ne kadar korkunç olduğunu ve ilkelerinin birçok geliştirici için ne kadar belirsiz olduğunu gösterir. Daha kolay anlaşılır dillerle tam olarak yanlış olan nedir? Bence iş geliştiriciler, iş ya da geliştirme çalışmalarına hiç ya da çok az değer katan kafa karıştırıcı teknolojileri reddetmek için seslerini yükseltiyorlar.
NoChance

Nesnede a1.x !== a2.x:; prototip üzerinde:a1.x === a2.x
Juan Mendes

Yanıtlar:


467

Örneklerin çok farklı sonuçları vardır.

Farklılıklara bakmadan önce aşağıdakilere dikkat edilmelidir:

  • Bir kurucu prototipi , örneğin özel [[Prototype]]mülkü aracılığıyla yöntemler ve değerleri örnekler arasında paylaşmanın bir yolunu sunar .
  • Bir fonksiyon var bu işlev denir nasıl ya kullanımıyla ayarlanır bağlama (burada ele alınmamıştır). Bir fonksiyon, bir nesne (örneğin denir Burada myObj.method()daha sonra) bu yöntem, referanslar içinde nesne. Nerede bu çağrıyla veya kullanımı ile ayarlı değil bağlama , (bir tarayıcıda pencerede) küresel nesneye varsayılan veya katı modda, tanımlanmamış kalır.
  • JavaScript nesne yönelimli bir dildir, yani çoğu değer işlevler de dahil olmak üzere nesnelerdir. (Dizeler, sayılar ve boolean'lar nesne değildir .)

İşte söz konusu parçacıklar:

var A = function () {
    this.x = function () {
        //do something
    };
};

Bu durumda, değişkene Abir işleve başvuru olan bir değer atanır. Bu işlev kullanılarak çağrıldığında A(), işlev bu çağrıyla ayarlanmaz, bu nedenle varsayılan olarak global nesneyi kullanır ve ifade this.xetkilidir window.x. Sonuç, sağ taraftaki işlev ifadesine bir referansın atanmasıdır window.x.

Bu durumuda:

var A = function () { };
A.prototype.x = function () {
    //do something
};

çok farklı bir şey oluyor. İlk satırda, değişkene Abir işleve başvuru atanır. JavaScript'te, tüm işlev nesneleri varsayılan olarak bir prototip özelliğine sahiptir, bu nedenle A.prototype nesnesi oluşturmak için ayrı bir kod yoktur .

İkinci satırda, A.prototype.x işlevine bir başvuru atanır. Bu, yoksa bir x özelliği oluşturur veya varsa yeni bir değer atar. Dolayısıyla, nesnenin x özelliğinin ifadeye dahil edildiği ilk örnekle fark .

Başka bir örnek aşağıdadır. Birincisine benzer (ve belki de sormak istediğiniz şey):

var A = new function () {
    this.x = function () {
        //do something
    };
};

Bu örnekte newişleç, işlevin yapıcı olarak çağrılması için işlev ifadesinden önce eklenmiştir. İle çağrıldığında new, fonksiyon var bu kimin özel yeni Nesne başvurmak için ayarlanır [[Prototype]]mülkiyet şantiye kamu referans olarak ayarlanır prototip . Bu nedenle, atama deyiminde, xözellik bu yeni nesne üzerinde oluşturulur. Yapıcı olarak çağrıldığında, işlev varsayılan olarak bu nesnesini döndürür, bu nedenle ayrı bir return this;ifadeye gerek yoktur .

A'nın x özelliğine sahip olup olmadığını kontrol etmek için :

console.log(A.x) // function () {
                 //   //do something
                 // };

Yapıcıya referans vermenin tek yolu A.constructor aracılığıyla olduğundan, bu yeninin nadir bir kullanımıdır . Bunu yapmak çok daha yaygın olurdu:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Benzer bir sonuç elde etmenin başka bir yolu, hemen çağrılan bir işlev ifadesini kullanmaktır:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

Bu durumda, Asağ taraftaki işlevi çağırmanın dönüş değerini atar. Çünkü Burada yine, bu çağrıda ayarlanmamış, küresel nesne başvurusu ve edecektir this.xetkilidir window.x. İşlev bir şey döndürmediği Aiçin değeri olacaktır undefined.

İki yaklaşım arasındaki bu farklılıklar, Javascript nesnelerinizi JSON'a / JSON'dan serileştirdiğinizde ve serileştirdiğinizde de kendini gösterir. Nesnenin prototipinde tanımlanan yöntemler, nesneyi serileştirdiğinizde serileştirilmez; örneğin, bir nesnenin yalnızca veri bölümlerini serileştirmek istediğinizde uygun olabilir, ancak yöntemleri:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

İlgili sorular :

Sidenote: İki yaklaşım arasında önemli bir bellek tasarrufu olmayabilir, ancak yöntemleri ve özellikleri paylaşmak için prototipi kullanmak, muhtemelen kendi kopyasına sahip her örneğin daha az bellek kullanacaktır.

JavaScript düşük seviyeli bir dil değildir. Prototipleme veya diğer kalıtım kalıplarını belleğin tahsis edilme biçimini açıkça değiştirmenin bir yolu olarak düşünmek çok değerli olmayabilir.


49
@keparo: Yanılıyorsun. Her nesnenin bir [dahili] prototip nesnesi vardır (bu olabilir null), ancak bu, prototypeişlevlerde olan ve tüm örneklerin prototipinin kurulduklarında ayarlandığı özellikten çok farklıdır new. Bu gerçekten 87 upvotes var inanamıyorum :-(
Bergi

8
"The language is functional"işlevsel olanın bu olduğuna emin misiniz?
phant0m

23
@Bergi'nin prototipler hakkında söylediklerini ikinciyim. İşlevlerin bir prototip özelliği vardır. İşlevler dahil tüm nesneler, bazı tarayıcılarda Object.getPrototypeOf (myObject) veya myObject .__ proto__ ile erişilebilen başka bir dahili özelliğe sahiptir. Proto özelliği nesnenin prototip zincirinde üst (veya nesne ki bu amaç, devralır gelen) göstermektedir. Prototype özelliği (yalnızca işlevlerde bulunur), yeni anahtar sözcüğü kullanarak yeni nesneler oluşturmak için işlevi kullanan tüm nesnelerin üst öğesi olacak nesneyi belirtir.
Jim Cooper

11
Bu makale oldukça yanıltıcı ve karıştırır nasıl bu ayarlanır. Yeniden yazma üzerinde çalışma.
RobG

37
Bu cevap oldukça tuhaf ve sorunun cevabını tamamen özlüyor gibi görünüyor. Soru, yapıcı içinde prototip ve tür özelliklerini tanımlamakla ilgili çok yaygın bir soru gibi görünüyor, ancak cevabın yarısı, Abir işlev olarak kullanırsanız ne olacağı ve diğer yarısı ise belirsiz ve alışılmadık yollarla ilgili basit bir şey.
JLRishe

235

Diğerlerinin ilk sürümü söylediği gibi, "this" kullanmak, A sınıfının her örneğinin "x" işlevinin kendi bağımsız kopyasına sahip olmasını sağlar. Oysa "prototip" kullanmak, her A sınıfı örneğinin "x" yönteminin aynı kopyasını kullanacağı anlamına gelir.

İşte bu ince farkı göstermek için bazı kodlar:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Diğerlerinin de belirttiği gibi, bir yöntemi veya diğerini seçmenin çeşitli nedenleri vardır. Örneğim sadece farkı açıkça göstermeyi amaçlıyor.


5
Bu olmasını beklediğim şeydi, ancak yukarıdaki gibi Ax'i değiştirdikten sonra yeni bir nesne başlattığımda, A'yı bir singleton gibi kullanmazsam hala 'A' görüntülerim. jsbin.com/omida4/2/edit
jellyfishtree

19
Çünkü benim örneğim yanlıştı. Sadece iki yıldır yanlış. İç çekmek. Ancak mesele hala geçerli. Örneği, gerçekten işe yarayan biriyle güncelledim. Gösterdiğiniz için teşekkürler.
Benry

4
Statik bir yöntem! : D

6
evet ... 'prototip', oluşturulan tüm örnekler tarafından paylaşılacak statik veya sınıf düzeyi anlamına gelir ... 'bu' her örneğin kendi kopyasına sahip olacağı bir örnek yöntemdir
Aneer Dev

7
Statik değil. Statik, çoğu OO dilinde kullanıldığı gibi this, yöntemin sahibi olan nesneye bağımlılık olmadığı anlamına gelir . yani yöntemin sahibi olan bir nesne yoktur. Bu durumda this, örnekte A sınıfında gösterildiği gibi bir nesne vardır .
CJStuart

152

Şu 2 örneği ele alalım:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Buradaki çoğu insan (özellikle en yüksek puanlı cevaplar) NEDEN'i açıklamadan nasıl farklı olduklarını açıklamaya çalıştı. Bunun yanlış olduğunu düşünüyorum ve önce temel ilkeleri anlarsanız, fark belirginleşecektir. İlk önce temel ilkeleri açıklamaya çalışalım ...

a) İşlev, JavaScript'teki bir nesnedir. JavaScript'teki HER nesne dahili bir özellik alır (yani, Chrome gibi tarayıcılar dışında diğer özellikler gibi erişemezsiniz), genellikle olarak adlandırılır __proto__( anyObject.__proto__referansta bulunduğunu görmek için gerçekten Chrome'a yazabilirsiniz . JavaScript'te bir özellik = bir nesnenin içindeki bir değişken, başka bir şey yok. Değişkenler ne yapar?

Peki bu __proto__özellik neyi gösteriyor? Genellikle başka bir nesne (nedenini daha sonra açıklayacağız). Özelliğin JavaScript'i __proto__başka bir nesneyi DEĞİL olarak göstermesini zorlamanın tek yolu kullanmaktır var newObj = Object.create(null). Bunu __proto__yapsanız bile, STILL özelliği nesnenin bir özelliği olarak var olur, sadece başka bir nesneyi göstermez, işaret eder null.

İşte çoğu insanın kafası karışıyor:

JavaScript'te yeni bir işlev oluşturduğunuzda (bu da bir nesne, unutmayın?), Tanımlandığı anda, JavaScript otomatik olarak adlandırılan bu işlevde yeni bir özellik oluşturur prototype. Dene:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypetesisten TAMAMEN FARKLI __proto__. Örneğimizde, 'A' artık 'prototip' ve olarak adlandırılan İKİ özelliğe sahiptir __proto__. Bu insanlar için büyük bir karışıklık. prototypeve __proto__özellikler hiçbir şekilde ilişkili değildir, ayrı değerlere işaret eden ayrı şeylerdir.

Merak edebilirsiniz: JavaScript'in neden __proto__her bir nesnede özelliği var? Bir kelime: delegasyon . Bir nesnedeki bir özelliği çağırdığınızda ve nesnenin sahip olmadığı, JavaScript tarafından başvurulan nesneyi __proto__olup olmadığını görmek için onu arar . Eğer sahip değilse, o nesnenin __proto__özelliğine bakar ve böylece zincir bitene kadar devam eder. Böylece prototip zincirinin adı . Tabii ki, __proto__bir nesneye işaret etmez ve bunun yerine null, iyi şanslar gösterirse, JavaScript bunu fark eder ve undefinedmülk için size geri döner .

Ayrıca, prototypeişlevi tanımladığınızda neden JavaScript bir işlev için çağrılan bir özellik oluşturduğunu merak ediyor olabilirsiniz. Sizi kandırmaya çalışıyor Çünkü evet sizi aldatmasına o sınıf tabanlı diller gibi çalıştığını.

Örneğimizle devam edelim ve aşağıdakilerden bir "nesne" oluşturalım A:

var a1 = new A();

Bu şey olduğunda arka planda bir şey oluyor. a1yeni, boş bir nesne atanmış sıradan bir değişkendir.

newİşlevi bir işlev çağırma işleminden önce kullanmış olmanız A()arka planda ek bir şey yaptı. newAnahtar kelime hangi şimdi başvuruları yeni bir nesne oluşturulur a1ve bu nesne boştur. Ek olarak olanlar:

Her fonksiyon tanımında, yaratılan yeni bir özellik prototype( __proto__özellikten farklı olarak erişebilirsiniz ) oluşturulduğunu söyledik ? Bu özellik şu anda kullanılıyor.

Şimdi, yeni pişirilmiş boş bir a1nesneye sahip olduğumuz noktadayız . JavaScript'teki tüm nesnelerin, boş veya başka bir nesne olsun, bir __proto__şeye ( a1ayrıca sahip) işaret eden bir dahili özelliğe sahip olduğunu söyledik. Ne newoperatör yapar o setleri olmasıdır __proto__işlevin işaret edecek özelliğini prototypeözelliği. Tekrar okuyun. Temel olarak bu:

a1.__proto__ = A.prototype;

Bunun A.prototypeboş bir nesneden başka bir şey olmadığını söyledik (tanımlamadan önce başka bir şeye değiştirmediğimiz sürece a1). Şimdi temelde a1.__proto__aynı şeye A.prototypeişaret ediyor, yani bu boş nesne. Her ikisi de, bu çizgi gerçekleştiğinde oluşturulan aynı nesneyi işaret ediyor:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Şimdi, var a1 = new A()ifade işlendiğinde başka bir şey daha oluyor . Temel A()olarak yürütülür ve A böyle bir şeyse:

var A = function() { this.hey = function() { alert('from A') } };

İçindeki tüm şeyler function() { }idam edilecek. Çizgiye ulaştığınızda this.hey.., thisolarak değiştirilir a1ve bunu alırsınız:

a1.hey = function() { alert('from A') }

Neden thisdeğişiklik yapıldığını açıklamayacağım , a1ancak bu daha fazla bilgi edinmek için harika bir cevap .

Özetlemek gerekirse, yaptığınız zaman var a1 = new A()arka planda 3 şey oluyor:

  1. Tamamen yeni bir boş nesne oluşturulur ve bu nesneye atanır a1.a1 = {}
  2. a1.__proto__özelliği, A.prototypenoktalarla aynı şeyi işaret edecek şekilde atanır (başka bir boş nesne {})

  3. İşlev A(), this1. adımda oluşturulan yeni, boş nesneye ayarlanarak yürütülür (neden thisdeğişiklik yaptığına dair yukarıda atıfta bulunduğum yanıtı okuyun a1)

Şimdi başka bir nesne oluşturmaya çalışalım:

var a2 = new A();

Adım 1,2, 3 tekrar edilecektir. Bir şey fark ettin mi? Anahtar kelime tekrar. Adım 1: a2yeni bir boş nesne olacak, adım 2: __proto__özelliği aynı şeyi A.prototypeişaret edecek ve en önemlisi adım 3: işlev A()AGAIN yürütülür, yani bir işlev içeren özellik a2elde edilir hey. a1ve a2adında iki AYRI özelliklere sahip hey2 AYRI fonksiyonlara işaret! Şimdi aynı iki farklı nesnede aynı şeyi yapan yinelenen işlevler var, ayy ... 1000 nesne varsa new A, tüm işlev bildirimleri 2 sayısı gibi bir şeyden daha fazla bellek aldıktan sonra bunun bellek sonuçlarını hayal edebilirsiniz . bunu nasıl önleyebiliriz?

__proto__Özelliğin neden her nesnede var olduğunu hatırlıyor musunuz? Böylece, yoManözelliği a1( varsa) alırsanız, mülküne __proto__danışılır, ki bu bir nesne ise (ve çoğu durumda), içerip içermediğini kontrol eder yoManve içermiyorsa, nesnenin __proto__vb. bilgilerine başvurur . Varsa, o özellik değerini alır ve size görüntüler.

Birisi bu gerçeği + oluşturduğunuzda a1, __proto__mülkünün aynı (boş) nesneyi A.prototypeişaret ettiği ve bunu işaret ettiği gerçeğini kullanmaya karar verdi :

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Güzel! Şimdi, oluşturduğunuzda a1, yine yukarıdaki 3 adımın hepsinden geçer ve 3. adımda function A(), hiçbir şey yapmaz, çünkü yürütülecek hiçbir şey yoktur. Ve eğer yaparsak:

a1.hey

İçermediğini görecek ve a1sahip olup olmadığını görmek heyiçin __proto__özellik nesnesini kontrol edecektir .

Bu yaklaşımla, her yeni nesne oluşturmada işlevlerin çoğaltıldığı 3. adımdaki bölümü ortadan kaldırıyoruz. Ayrı bir mülk yerine a1ve a2sahip olmak yerine hey, şimdi HİÇBİRİ ona sahip değildir. Hangi, sanırım, şimdiye kadar kendini anladın. Bu iyi bir şey ... anlarsanız __proto__ve bunun Function.prototypegibi sorular oldukça açık olacaktır.

NOT: Bazı insanlar iç Prototip özelliğini olarak adlandırmama eğilimindedir __proto__, bu adı posta yoluyla Functional.prototypeözelliği iki farklı şey olarak açıkça ayırt etmek için kullandım .


1
Gerçekten kapsamlı ve bilgilendirici bir cevap. Yukarıdaki nesne yapılarını (A.prototype.hey vs object this.hey) kullanarak bazı bellek testleri yaptım ve her birinin 1000 örneğini oluşturdum. Nesne özelliği yaklaşımı için bellek ayak izi, prototipe kıyasla yaklaşık 100 kb daha büyüktü. Daha sonra aynı amaç için "aptal" olarak adlandırılan başka bir işlev ekledim ve doğrusal olarak 200 kb'ye yükseldi. Anlamlı değil, fıstık da değil.
jookyone

Daha da ilginç olanı, prototip yönteminin yerel olarak çalışan object özellik yönteminden biraz daha yavaş olmasıdır. Genel olarak, javascript'in 10k üzerindeki numaraların veri manipülasyonu için kullanılması gerektiğinden emin değilim, bu nedenle potansiyel bellek etkilerine dayanan yaklaşımları değiştirmek için herhangi bir nedeni yok sayıyorum. Bu noktada çalışma bir sunucuya yüklenmelidir.
jookyone

Mesele şu ki __proto__ve .prototypetamamen farklı şeyler.
Wayou

1
Ben memnun hissetmiyorum sadece size bir upvote ... Aferin ver!
Kristianmitk

58

Çoğu durumda bunlar esasen aynıdır, ancak her sürüm için ayrı bir işlev yerine işlevin yalnızca bir örneği olduğundan ikinci sürüm bellek tasarrufu sağlar.

İlk formu kullanmanın bir nedeni "özel üyeler" e erişmektir. Örneğin:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Javascript'in kapsam belirleme kuralları nedeniyle, private_var, this.x öğesine atanan işlev için kullanılabilir, ancak nesnenin dışında kullanılamaz.


1
Bu üyeye, prototipler aracılığıyla özel üyelere nasıl erişileceğine ilişkin bir örnek için stackoverflow.com/a/1441692/654708 bakın .
GFoley83

@ GFoley83 cevap olmadığını değil göstermektedir - prototip yöntemleri yalnızca verilen nesnenin "kamu" özelliklerine erişebilirsiniz. Özel üyelere yalnızca ayrıcalıklı yöntemler (prototipte değil) erişebilir.
Alnitak

27

İlk örnek yalnızca bu nesne için arabirimi değiştirir. İkinci örnek, o sınıfın tüm nesnelerinin arabirimini değiştirir.


Her ikisi de x, prototipine A'nın yeni bir örneği atanan tüm nesneler için işlevi kullanılabilir hale getirecektir :function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams

21

Bunun thisyerine kullanımdaki nihai sorun, prototypebir yöntemi geçersiz kılarken, temel sınıfın yapıcısının yine de geçersiz kılınmış yönteme başvurmasıdır. Bunu düşün:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

karşı:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Bunun bir sorun olmadığını düşünüyorsanız, özel değişkenler olmadan yaşayabileceğinize ve birini gördüğünüzde bir sızıntıyı bilecek kadar deneyimli olup olmadığınıza bağlıdır. Ayrıca, yöntem tanımlarından sonra yapıcı mantığını koymak zorunda kalmak da elverişsizdir.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

karşı:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

20

Her nesne bir prototip nesnesine bağlıdır. Varolmayan bir özelliğe erişmeye çalışırken JavaScript, nesnenin o prototip nesnesine bakar ve varsa onu döndürür.

prototypeBir fonksiyon yapıcı malı kullanırken bu fonksiyonu ile oluşturulan tüm örneklerini prototip nesnesine işaret eder new.


İlk örneğinizde, işlevle xoluşturulan her örneğe bir özellik ekliyorsunuz A.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

İkinci örnekte, prototip nesnesine tüm örneklerin Aişaret ettiği bir özellik ekliyorsunuz .

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

Sonuç olarak, ilk örnekte , her örneğe işlevin bir kopyası atanır . İkinci örnekte , işlevin tek bir kopyası tüm örnekler tarafından paylaşılır .


1
Sorunun en doğru yanıtı olduğu için bunu oyladı.
Nick Pineda

1
Ben doğrudan yaklaşım sevdim !! kusuyor!
Prens Vijay Pratap

16

Fark ne? => Çok.

Bence, thissürüm kapsülleme, yani veri gizleme etkinleştirmek için kullanılır. Özel değişkenlerin manipüle edilmesine yardımcı olur.

Aşağıdaki örneğe bakalım:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Şimdi, prototypeyapı aşağıdaki gibi uygulanabilir:

Farklı yetişkinlerin yaşları farklıdır, ancak tüm yetişkinler aynı haklara sahiptir.
Bu yüzden, bunu bunun yerine prototip kullanarak ekliyoruz.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Şimdi uygulamaya bakalım.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Bu yardımcı olur umarım.


3
+1 Diğerlerine göre çok daha az kıvrımlı ve daha grafiksel bir cevap. Ancak bu (iyi) örnekleri vermeden önce biraz daha ayrıntılandırmalısınız.
yerforkferchips

1
"Bu sürüm kapsülleme, yani veri gizleme etkinleştirmek için kullanılır" hakkında emin değilim. Bir işlev içindeki özellik "this.myProperty = ..." 'de olduğu gibi "this" kullanılarak tanımlanırsa, böyle bir özellik "private" değildir ve "new" kullanılarak sınıf dışındaki nesnelerden erişilebilir.
NoChance

14

Prototip sınıfın şablonu; gelecekteki tüm örnekleri için geçerlidir. Oysa bu nesnenin belirli bir örneğidir.


14

Bunun ölümle sonuçlandığını biliyorum ama hız farklılıklarının gerçek bir örneğini göstermek istiyorum.

Doğrudan nesne üzerinde işlev

Prototip üzerinde fonksiyon

Burada print, Chrome'da bir yöntemle 2.000.000 yeni nesne oluşturuyoruz . Bir dizideki her nesneyi saklıyoruz. printPrototipin takılması yaklaşık 1/2 sürer.


13

Bir JavaScript eğitim kursu sırasında öğrendiğim daha kapsamlı bir cevap vereyim.

Yanıtların çoğu bu farktan bahsetti, yani fonksiyon prototiplenirken tüm (gelecekteki) örneklerle paylaşılır. Sınıftaki işlevi bildirmek her örnek için bir kopya oluşturur.

Genel olarak doğru ya da yanlış yoktur, daha çok gereksinimlerinize bağlı olarak bir tat meselesi veya bir tasarım kararıdır. Ancak prototip, bu cevabın sonunda göreceğinizi umduğum gibi, nesne yönelimli bir şekilde geliştirmek için kullanılan tekniktir.

Sorunuzda iki örnek gösterdiniz. İki tanesini daha açıklamaya çalışacağım ve eğer uygunsa farklılıkları açıklamaya çalışacağım. Düzenlemek / uzatmaktan çekinmeyin. Tüm örneklerde, konumu olan ve hareket edebilen bir araba nesnesiyle ilgilidir.

Nesne Dekoratör kalıbı

Bu modelin günümüzde hala alakalı olup olmadığından emin değilim, ancak var. Ve bunu bilmek güzel. Dekoratör işlevine bir nesneyi ve bir özelliği iletmeniz yeterlidir. Dekoratör nesneyi özellik ve yöntemle döndürür.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Fonksiyonel Sınıflar

JavaScript'teki bir işlev özel bir nesnedir. Bir işlev çağrılmaya ek olarak, diğer nesneler gibi özellikleri de depolayabilir.

Bu durumda Car, alıştığınız gibi çağrılabilen bir işlevdir ( ayrıca nesne düşünün ). Bir özelliğe sahiptir methods( moveişlevi olan bir nesne ). Ne zaman Carçağrılır extendbiraz sihir yapar ve genişletir denir işlevi, Carfonksiyonu içinde tanımlanmış yöntemlerle (nesne düşünüyorum) methods.

Bu örnek, farklı olsa da, sorudaki ilk örneğe en yakın olanıdır.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Prototypal Sınıfları

İlk iki örüntü, paylaşılan yöntemleri tanımlamak için tekniklerin kullanılması veya yapıcı gövdesinde satır içi olarak tanımlanan yöntemleri kullanma tartışmasına izin verir. Her iki durumda da her örneğin kendi moveişlevi vardır.

Prototip paterni aynı incelemeye iyi borç vermez, çünkü prototip delegasyonu aracılığıyla işlev paylaşımı prototip paterni için çok amaçtır. Diğerlerinin de belirttiği gibi, daha iyi bir bellek ayak izine sahip olması bekleniyor.

Bununla birlikte, bilinmesi gereken ilginç bir nokta vardır: Her prototypenesnenin, constructoreklendiği işleve (düşünme nesnesine) işaret eden bir Convenience özelliği vardır .

Son üç satırla ilgili olarak:

Bu örnek, in Carbağlantıları prototypeyoluyla bağlanan nesne constructoriçin Carkendi başına, örneğin, Car.prototype.constructorbir Carkendini. Bu, hangi yapıcı işlevinin belirli bir nesneyi oluşturduğunu bulmanızı sağlar.

amy.constructoradlı kullanıcının arama işlemi başarısız olur ve bu nedenle Car.prototypeyapıcı özelliğine sahip olan delege edilir . Ve böylece amy.constructorolduğunu Car.

Ayrıca, amybir instanceof Car. instanceofOperatör sağ işlenen prototip nesnesi (eğer görerek çalışır Car) sol tarafındaki işlenen prototipinde (herhangi bir yerinde bulunabilir amy) zinciri.

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Bazı geliştiriciler başlangıçta karıştırılabilir. Aşağıdaki örneğe bakın:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

instanceofOperatör döner false, çünkü Dogbireyin prototipi yerde bulunamaz fidos prototip zinciri '. fidobir nesne değişmeziyle oluşturulan basit bir nesnedir, yani yalnızca temsilci seçer Object.prototype.

Yalancı kalıplar

Bu, prototip deseninin basitleştirilmiş biçimde başka bir biçimidir ve örneğin Java'da programlayanları yapıcıyı kullandığından daha tanıdıktır new.

Gerçekten prototip paterni ile aynı şeyi yapar, sadece prototip paternin üst üste şekerli şekeridir.

Bununla birlikte, birincil fark, JavaScript motorlarında yalnızca psödoklasik desen kullanılırken uygulanan optimizasyonların olmasıdır. Psödoklasik paterni prototip paterninin muhtemelen daha hızlı bir versiyonunu düşünün; her iki örnekte de nesne ilişkileri aynıdır.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Son olarak, nesne yönelimli programlamanın nasıl yapılabileceğini anlamak çok zor olmamalıdır. İki bölüm vardır.

Prototipte (zincir) ortak özellikleri / yöntemleri tanımlayan bir bölüm.

Ve nesneleri birbirinden ayıran tanımları koyduğunuz başka bir bölüm ( locörneklerde değişken).

JavaScript'te üst sınıf veya alt sınıf gibi kavramları uygulamamıza izin veren şey budur.

Eklemek veya düzenlemek için çekinmeyin. Bir kez daha bunu belki bir topluluk wiki yapabilirim.


Çok kapsamlı bir görevi devirmek değil, ama OO ve Prototipik mirasın aslında farklı düşünce okulları olduğunu düşündüm.
Nick Pineda

Bunlar, ancak farklı teknikler / düşüncelerle "OO" yapabilir, değil mi?
Ely

Gerçekten emin değilim. Birçoğu sadece prototip felsefenin sadece farklı olduğunu ve birçoğunun onu OO ile karşılaştırmaya çalıştığını söylüyor çünkü birçok kişinin kullandığı düşünce okulu.
Nick Pineda

Yani, OO stilini uygulamak istiyorsanız ve dil buna yardımcı olan bir dizi teknik sunuyorsa, mutlaka yanlış değildir.
Ely

11

@Matthew Crumley'in haklı olduğuna inanıyorum. Bunlar işlevsel olmasa da yapısal olarak eşdeğer. Firebug kullanarak oluşturulan nesnelere bakmak newiçin aynı olduklarını görebilirsiniz. Ancak benim tercihim şu olurdu. Ben sadece C # / Java alışkınım gibi görünüyor tahmin ediyorum. Yani, sınıfı tanımlayın, alanları, yapıcıyı ve yöntemleri tanımlayın.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Değişken kapsamının özel olduğunu ima etmek istemiyordum, sadece javascript sınıflarımı nasıl tanımladığımı göstermeye çalışıyordum. Değişken adı bunu yansıtacak şekilde değiştirildi.


2
_instance_var örneğinde olduğu gibi initializeve x methods do not refer to the bir Aörnekte _instance_var` özelliğinde olduğu gibi . Bir örneğin özelliğini this._instance_varkullanmak istiyorsanız kullanın . _instance_varA
Lekensteyn

2
Komik olan, Benry de iki yıl sonra ortaya çıkarılan böyle bir hata yaptı: p
Lekensteyn

10

Diğer cevaplarda tartışıldığı gibi, bu gerçekten bir performans değerlendirmesidir, çünkü prototipteki işlev, her örnekleme için oluşturulan işlev yerine tüm örneklemelerle paylaşılır.

Bunu göstermek için bir jsperf koydum. Sınıfı somutlaştırmak için geçen süre dramatik bir fark var, ancak sadece birçok örnek oluşturuyorsanız gerçekten alakalı.

http://jsperf.com/functions-in-constructor-vs-prototype


8

Statik olarak yazılan dili düşünün, üzerinde prototypedurağan olan ve üzerinde thisolan şeyler örnekle ilgilidir.

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.