Ş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:
- Tamamen yeni bir boş nesne oluşturulur ve bu nesneye atanır
a1.a1 = {}
a1.__proto__özelliği, A.prototypenoktalarla aynı şeyi işaret edecek şekilde atanır (başka bir boş nesne {})
İş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 .