ECMAScript Miras Mekanizması Uygulaması

Miras mekanizmasının gerçekleştirilmesi

ECMAScript'te miras mekanizmasını gerçekleştirmek için, miras alınacak temel sınıftan başlayabilirsiniz. Tüm geliştirici tanımlı sınıflar temel sınıf olarak kullanılabilir. Güvenlik nedenlerinden ötürü, yerel sınıflar ve ev sahibi sınıflar temel sınıf olarak kullanılamaz, bu da bu tür kodların halka açık olarak erişilmesini önler, çünkü bu tür kodlar kötü niyetli saldırılar için kullanılabilir.

Temel sınıf seçildikten sonra, onun alt sınıfını oluşturabilirsiniz. Temel sınıfın kullanılıp kullanılmayacağı tamamen sizin kararınıza bağlıdır. bazen doğrudan kullanılmayan, sadece alt sınıflara genel fonksiyonlar sağlamak için kullanılan bir temel sınıf oluşturmak isteyebilirsiniz. Bu durumda, temel sınıf soyut sınıf olarak kabul edilir.

ECMAScript, diğer diller gibi soyut sınıfları kesin olarak tanımlamaz, ancak bazen bazı kullanılmayan sınıflar oluşturur. Genellikle bu tür sınıflara soyut sınıf adı verilir.

Oluşturulan alt sınıf, ebeveyn sınıfının tüm özelliklerini ve yöntemlerini, yapıcı fonksiyon ve yöntem gerçeklemelerini içerecektir. Unutmayın, tüm özellikler ve yöntemler ortak kullanılır, bu yüzden alt sınıf bu yöntemleri doğrudan erişebilir. Alt sınıf, ebeveyn sınıfında olmayan yeni özellikler ve yöntemler ekleyebilir, aynı zamanda ebeveyn sınıfının özelliklerini ve yöntemlerini de geçersiz kılabilir.

Miras yöntemleri

Diğer işlevler gibi, ECMAScript'in miras almak için kullandığı yöntemler birden fazladır. Bu, JavaScript'teki miras mekanizmasının açıkça tanımlanmamış olmasından kaynaklanır, aksine, taklit edilmiştir. Bu, tüm miras ayrıntılarının tamamen yorumlayıcı tarafından işlenmediği anlamına gelir. Geliştirici olarak, en uygun miras yöntemini seçme hakkınız vardır.

Aşağıda bazı spesifik miras yöntemlerini tanıtacağız.

Nesne sahteciliği

Eski ECMAScript'i tasarlamışken, nesne sahteciliği (object masquerading) için tasarlanmamıştır. Bu, geliştiriciler fonksiyonların nasıl çalıştığını, özellikle this anahtar kelimesinin fonksiyon ortamında nasıl kullanıldığını anlamaya başladıktan sonra gelişmiştir.

Bu prensip şu şekilde çalışır: Yapıcı fonksiyon, this anahtar kelimesi ile tüm özelliklere ve yöntemlere değer atar (yani sınıf tanımlama yöntemi ile yapıcı fonksiyonu kullanır). Yapıcı fonksiyon sadece bir fonksiyon olduğundan, ClassA yapıcı fonksiyonunu ClassB yöntemi olarak kullanabilir ve onu çağırabilir. ClassB, ClassA yapıcı fonksiyonunda tanımlanan özellikleri ve yöntemleri alır. Örneğin, aşağıdaki şekilde ClassA ve ClassB tanımlanabilir:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor) {
}

Hatırlıyor musunuz? this anahtar kelimesi, yapıcı fonksiyon tarafından oluşturulan mevcut nesneye işaret eder. Ancak bu yöntemde, this işaret eden nesnenin sahibidir. Bu prensip, ClassA'yı miras mekanizmasını kurmak için normal bir fonksiyon olarak kullanmayı, yapıcı fonksiyon olarak kullanmaktan ziyade. Aşağıdaki şekilde kullanarak miras mekanizmasını gerçekleştirebilirsiniz:

function ClassB(sColor) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
}

Bu kod parçasında, ClassA'ya newMethod adında bir yöntem atandı (fonksiyonun adı sadece onun işaretçisidir). Daha sonra bu yöntemi çağırdık ve ona ClassB yapıcı fonksiyonunun parametresi olan sColor'ı verdik. Son satırda ClassA'ya olan referans silindi, bu yüzden bundan sonra onu çağıramayacaksınız.

Tüm yeni özellikler ve yöntemler, yeni yöntemi silmiş olduğunuz kod satırı ardından tanımlanmalıdır. Aksi takdirde, üst sınıfın ilgili özelliklerini ve yöntemlerini kapatabilirsiniz:

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

Önceki kodun etkili olduğunu kanıtlamak için aşağıdaki örneği çalıştırabilirsiniz:

var objA = new ClassA("mavi");
var objB = new ClassB("kırmızı", "John");
objA.sayColor();	//Çıktı "mavi"
objB.sayColor();	//Çıktı "kırmızı"
objB.sayName();		// Çıktı "John"

Nesne sahteciliği çoklu mirası gerçekleştirebilir

İlginç bir şekilde, nesne sahteciliği çoklu mirası destekler. Yani, bir sınıf birden fazla üst sınıfı miras alabilir. Çoklu miras mekanizması UML ile gösterildiği gibi aşağıda görülebilir:

UML 继承机制 图示实例

Örneğin, iki sınıf varsa ClassX ve ClassY, ClassZ bu iki sınıfı miras almak istiyorsa, aşağıdaki kodu kullanabilirsiniz:

function ClassZ() {
    this.newMethod = ClassX;
    this.newMethod();
    delete this.newMethod;
    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

TIY

Burada bir dezavantaj var, ClassX ve ClassY adında iki sınıfın aynı adlı özellik veya yöntemleri varsa ve ClassY'nin yüksek öncelikli olması gerekiyorsa, bu küçük sorun var. Çünkü bu sınıflardan sonraki sınıftan miras alır. Bu küçük sorun dışında, nesne sahteciliği ile çoklu miras mekanizmasını kolayca uygulayabilirsiniz.

Bu tür miras yöntemlerinin popülerliği nedeniyle, ECMAScript'in üçüncü sürümünde Function nesnesine iki yeni yöntem eklendi, bu da call() ve apply() yöntemleridir.

call() yöntemi

call() yöntemi klasik nesne sahtecilik yöntemleriyle en benzer yöntemdir. İlk parametre this nesnesi olarak kullanılır. Diğer parametreler doğrudan fonksiyona iletilir. Örneğin:

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "mavi";
sayColor.call(obj, "The color is ", "a very nice color indeed.");

Bu örnekte, sayColor() fonksiyonu nesne dışında tanımlanmıştır, bu yüzden bu anahtar kelime this'e ait değildir. Nesne obj'inin color özelliği maviye eşittir. call() yöntemini çağırırken, ilk parametre obj'dir, bu da sayColor() fonksiyonundaki this anahtar kelimesine obj değerinin atanması gerektiğini belirtir. İkinci ve üçüncü parametreler de string'tir. Bu parametreler sayColor() fonksiyonundaki sPrefix ve sSuffix ile eşleşir, son olarak oluşturulan "The color is blue, a very nice color indeed." mesajı gösterilir.

Bu yöntemi nesne miras mekanizması olan nesnelerle birlikte kullanmak için, sadece ön üç satırın atama, çağrı ve silme kodlarını değiştirmeniz yeterlidir:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.call(this, sColor);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

TIY

Burada, ClassA sınıfındaki anahtar kelime this'in yeni oluşturulan ClassB nesnesine eşit olması gerektiğini düşünelim, bu yüzden this ilk parametredir. İkinci parametre sColor iki sınıf için de benzersiz bir parametredir.

apply() yöntemi

apply() yöntemi iki parametreye sahiptir, bu parametreler this nesnesi ve fonksiyona iletilen parametrelerin dizisi olarak kullanılır. Örneğin:

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "mavi";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));

Bu örnek, önceki örnekle aynıdır, ancak şimdi apply() yöntemi çağrılır. apply() yöntemi çağrılırken, ilk parametre hala obj'dir, bu da sayColor() fonksiyonundaki this anahtar kelimesine obj değerinin atanması gerektiğini gösterir. İkinci parametre, sayColor() fonksiyonundaki sPrefix ve sSuffix ile uyumlu iki stringten oluşan bir dizi. Son olarak oluşturulan mesaj "The color is blue, a very nice color indeed." olacaktır ve gösterilecektir.

Bu yöntem, önceki üç satırın atama, çağrı ve yeni yöntemi silme kodunu da değiştirmek için kullanılır:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.apply(this, new Array(sColor));
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

Aynı şekilde, ilk parametre hala this'tir, ikinci parametre color değerine sahip tek bir değer içeren bir dizi. ClassB'nin tüm arguments nesnesini apply() yöntemine ikinci parametre olarak geçebilirsiniz:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.apply(this, arguments);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

TIY

Tabii ki, parametre nesnesinin geçirilmesi ancak ebeveyn sınıftaki parametre sırası ile çocuğundaki parametre sırası tamamen aynı olduğunda mümkündür. Aksi takdirde, doğru sırayla parametreleri yerleştirmek için ayrı bir dizi oluşturulmalıdır. Ayrıca, call() yöntemini de kullanabilirsiniz.

Prototip Zinciri (prototype chaining)

Bu tür miras, ECMAScript'te aslında prototip zinciri için kullanılmıştır. Önceki bölümde sınıfın prototip yöntemiyle tanımlanması anlatılmıştır. Prototip zinciri bu yöntemi genişleterek ilginç bir şekilde miras mekanizmasını gerçekleştirir.

Önceki bölümde öğrendiğimiz gibi, prototype nesnesi bir şablondur ve bu şablon tabanlı tüm nesneler oluşturulur. Kısacası, prototype nesnesinin herhangi bir özelliği ve yöntemi, bu sınıfın tüm örneklerine aktarılır. Prototip zinciri bu özelliği kullanarak miras mekanizmasını gerçekleştirir.

Eğer önceki örnekteki sınıfı prototip yöntemiyle yeniden tanımlarsanız, aşağıdaki gibi olacaklardır:

function ClassA() {
}
ClassA.prototype.color = "mavi";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA();

原型方式的神奇之处在于突出显示的蓝色代码行。这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。这很有意思,因为想要 ClassA 的所有属性和方法,但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?

注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:

function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};

Bu kodu test etmek için aşağıdaki örneği çalıştırabilirsiniz:

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();

TIY

Ayrıca, prototip zincirinde instanceof işlevinin çalışma şekli de çok benzersizdir. ClassB'nin tüm örnekleri için instanceof, hem ClassA hem de ClassB için true döner. Örneğin:

var objB = new ClassB();
alert(objB instanceof ClassA);	// Çıktı "true"
alert(objB instanceof ClassB);	// Çıktı "true"

ECMAScript'in zayıf türdü世界ında, bu son derece faydalı bir araçtır, ancak nesne sahteciliği ile kullanıldığında bunu kullanamazsınız.

Prototip zincirinin dezavantajı, çoklu mirası desteklememesidir. Unutmayın, prototip zinciri, sınıfın prototype özelliğini başka bir tür nesne ile yeniden yazacaktır.

Karışık yöntem

Bu miras alma yöntemi, sınıfı tanımlamak için yapıcı fonksiyonu kullanır, herhangi bir prototip kullanılmaz. Nesne sahteciliği ile ilgili ana sorun, yapıcı fonksiyon yöntemini kullanmak zorunda olmak, bu en iyi seçenek değildir. Ancak, prototip zinciri kullanıldığında, parametreli yapıcı fonksiyon kullanılamaz. Geliştiriciler nasıl seçecekler? Cevap basittir, her ikisini de kullanın.

Önceki bölümde, en iyi sınıf oluşturma yönteminin özellikleri yapıcı fonksiyonla tanımlamak, metodları prototipte tanımlamak olduğunu açıkladık. Bu yöntem, kalıtım mekanizması için de geçerlidir; yapıcı fonksiyonun özelliklerini nesne imitasyonu ile miras alır, prototype nesnesinin metodlarını prototip zinciri ile miras alır. Bu iki yöntemi kullanarak önceki örneği yeniden yazdığımız kod şu şekildedir:}}

function ClassA(sColor) {
    this.color = sColor;
}
ClassA.prototype.sayColor = function () {
    alert(this.color);
};
function ClassB(sColor, sName) {
    ClassA.call(this, sColor);
    this.name = sName;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
    alert(this.name);
};

Bu örnekte, kalıtım mekanizması iki vurgulanmış mavi kod satırı ile gerçekleştirilir. İlk vurgulanmış kod satırında, ClassB yapıcı fonksiyonunda, ClassA sınıfının sColor özelliğini nesne imitasyonu ile miras alır. İkinci vurgulanmış kod satırında, ClassA sınıfının metodlarını prototip zinciri ile miras alır. Bu karışık yöntem prototip zinciri kullanıldığı için instanceof işlevi hala doğru çalışır.

Aşağıdaki örnek, bu kodun test edildiğini gösterir:

var objA = new ClassA("mavi");
var objB = new ClassB("kırmızı", "John");
objA.sayColor();	//Çıktı "mavi"
objB.sayColor();	//Çıktı "kırmızı"
objB.sayName();	//Çıktı "John"

TIY