Pengaturan mekanisme warisan ECMAScript

Pengimplementasian mekanisme pewarisan

Untuk melaksanakan mekanisme pewarisan dengan ECMAScript, Anda dapat memulai dari kelas dasar yang akan diwarisi. Semua kelas yang didefinisikan pengembang dapat digunakan sebagai kelas dasar. Karena alasan keamanan, kelas lokal dan kelas tuan tidak dapat digunakan sebagai kelas dasar, untuk mencegah akses publik kode yang dicompile di tingkat browser, karena kode ini dapat digunakan untuk serangan malicios.

Setelah memilih kelas dasar, Anda dapat membuat kelas turunnya. Apakah Anda menggunakan kelas dasar sepenuhnya tergantung pada keputusan Anda. Kadang-kadang, Anda mungkin ingin membuat kelas dasar yang tidak dapat digunakan langsung, yang hanya digunakan untuk memberikan fungsi umum kepada kelas turun. Dalam hal ini, kelas dasar dianggap seperti kelas abstrak.

Walaupun ECMAScript tidak mendefinisikan kelas abstrak secara ketat seperti bahasa lain, kadang-kadang ia membangun beberapa kelas yang tidak diizinkan digunakan. Biasanya, kelas seperti ini disebut kelas abstrak.

Kelas turun yang dibuat akan mewarisi semua properti dan metode kelas atas, termasuk implementasi konstruktur dan metode. Ingat, semua properti dan metode adalah publik, jadi kelas turun dapat mengakses metode ini langsung. Kelas turun juga dapat menambah properti dan metode yang belum ada di kelas atas, atau dapat menimbulkan properti dan metode kelas atas.

Cara keberlanjutan

Seperti fungsi lainnya, cara eksekusi keberlanjutan ECMAScript bukan hanya satu. Ini disebabkan karena mekanisme keberlanjutan di JavaScript bukan yang diatur jelas, tetapi yang disimulasikan. Ini berarti detil keberlanjutan yang ada bukan disesuaikan sepenuhnya oleh pemroses. Sebagai pengembang, Anda memiliki hak untuk memutuskan cara keberlanjutan yang paling sesuai.

Di bawah ini akan disajikan beberapa cara keberlanjutan yang khusus.

Objek meniru

Ketika merancang ECMAScript asli, tidak diusulkan untuk merancang objek meniru (object masquerading). Ini adalah kemampuan yang terkenal setelah para pengembang memahami bagaimana fungsi bekerja, terutama bagaimana untuk menggunakan kata kunci this dalam lingkungan fungsi.

Prinsipnya seperti berikut: konstruktur menggunakan kata kunci this untuk memberikan nilai kepada semua properti dan metode (yaitu cara konstruktur yang diumumkan). Karena konstruktur hanya sebuah fungsi, sehingga dapat mempergunakan konstruktur ClassA sebagai method ClassB, dan kemudian memanggilnya. ClassB akan menerima properti dan metode yang diatur dalam konstruktur ClassA. Contohnya, definsikan ClassA dan ClassB seperti berikut:

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

Ingatkan? Kunci this mengacu kepada objek yang sedang dibuat konstruktur. Tetapi dalam method ini, this mengacu kepada objek milik. Prinsip ini adalah untuk membangun mekanisme keberlanjutan ClassA sebagai fungsi biasa, bukannya konstruktur. Dengan cara berikut, konstruktur ClassB dapat mencapai mekanisme keberlanjutan:

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

Dalam kode ini, newMethod diberikan kepada ClassA (ingat, nama fungsi hanya mengacu ke alamat yang menghadapinya). kemudian memanggil method, yang disampaikan adalah parameter sColor konstruktur ClassB. Baris kode terakhir menghapus referensi ClassA, sehingga dalam masa mendatang tidak dapat dipanggil lagi.

Semua properti dan metode yang baru harus diatur setelah baris kode newMethod dihapus. Jika tidak, mungkin akan menimpa properti dan metode superclass yang relevan:

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

Untuk membuktikan kode sebelumnya efektif, dapat menjalankan contoh di bawah ini:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName(); // keluar "John"

Objek meniru dapat mencapai keberlanjutan

Yang menarik, objek meniru dapat mendukung keberlanjutan. Ini berarti kelas dapat mengeksyen beberapa superclass. Dengan mekanisme keberlanjutan yang digambarkan dalam UML seperti yang terlihat di bawah ini:

mekanisme warisan UML contoh peta

Contoh, jika ada dua kelas ClassX dan ClassY, ClassZ ingin mengeksyen keduanya, dapat digunakan kode di bawah ini:

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

TIY

这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。

由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。

call() 方法

call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:

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

在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 obj 的 color 属性等于 blue。调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。

要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:

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

这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。

apply() 方法

apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:

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

Contoh ini sama seperti contoh sebelumnya, hanya saja sekarang dipanggil method apply(). Ketika memanggil method apply(), parameter pertama tetap adalah obj, yang menunjukkan bahwa keyword this di dalam function sayColor() harus diisi dengan obj. Parameter kedua adalah array yang terdiri dari dua string, yang cocok dengan parameter sPrefix dan sSuffix di dalam function sayColor(), pesan yang dihasilkan tetap adalah "The color is blue, a very nice color indeed." dan akan ditampilkan.

Metode ini juga digunakan untuk menggantikan kode asosiasi, pemanggilan, dan penghapusan method baru di baris yang pertama, kedua, dan ketiga:

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);
    };
}

Sama seperti, parameter pertama tetap adalah this, parameter kedua adalah array dengan hanya satu nilai color. Dapat memasukkan seluruh objek arguments ClassB sebagai parameter kedua ke dalam method apply():

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

Tentu saja, hanya parameter di superclass yang diatur dalam urutan yang sama seperti di subclass yang dapat disampaikan objek parameter. Jika tidak, harus dibuat sebuah array yang berbeda, menempatkan parameter dalam urutan yang benar. Selain itu, dapat digunakan metode call().

Garis keturunan prototype (prototype chaining)

Fungsi pewarisan dalam bentuk ini di ECMAScript sebenarnya digunakan untuk garis keturunan prototype. Bab sebelumnya memperkenalkan cara menentukan prototype kelas. Garis keturunan prototype memperluas cara ini, untuk mencapai mekanisme pewarisan dengan cara yang menarik.

Diketahui di bab sebelumnya, objek prototype adalah suatu templat, objek yang akan diinstansiasi semua berdasarkan templat ini. Secara sederhana, setiap atribut dan metode objek prototype akan disampaikan ke semua instansia kelas. Garis keturunan prototype menggunakan fungsi ini untuk mencapai mekanisme pewarisan.

Jika menggunakan cara prototype untuk menedefinikan kelas di contoh sebelumnya, mereka akan berubah menjadi bentuk berikut:

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

Pencapaian yang mengejutkan dari cara prototipe adalah menonjolkan baris kode biru yang disorot. Di sini, properti prototype ClassB diatur menjadi instansia ClassA. Ini sangat menarik, karena ingin semua properti dan metode ClassA, tetapi tidak ingin menambahkan satu-satu ke properti prototype ClassB. Adakah cara yang lebih baik daripada memberikan instansia ClassA ke properti prototype?

Perhatian:Panggil konstruktur ClassA, tanpa mengirimkan parameter. Ini adalah praktek standar di rantai prototipe. Pastikan bahwa konstruktur tidak memiliki parameter.

Dengan mirip seperti peniruan objek, semua properti dan metode kelas turunan harus muncul setelah properti prototype diisi, karena semua metode yang diisi sebelumnya akan dihapus. Mengapa? Karena properti prototype diganti dengan objek baru, objek yang menambahkan metode asli akan dihancurkan. Jadi, kode untuk menambahkan properti name dan metode sayName() untuk kelas ClassB adalah:

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

Bisa diuji melalui contoh di bawah ini:

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

TIY

Selain itu, dalam rantai prototipe, cara kerja operator instanceof sangat unik. Untuk semua instansia ClassB, instanceof untuk ClassA dan ClassB kembali true. Contoh:

var objB = new ClassB();
alert(objB instanceof ClassA);	// keluar "true"
alert(objB instanceof ClassB);	// keluar "true"

Dalam dunia tipe lemah ECMAScript, ini adalah alat yang sangat berguna, namun tidak dapat digunakan saat memanfaatkan peniruan objek.

Kurangnya rantai prototipe adalah tidak mendukung warisan berbagai tingkatan. Ingat, rantai prototipe akan menulis ulang properti prototype kelas dengan objek tipe lain.

Cara campuran

Pola warisan ini menggunakan fungsi konstruktur untuk menentukan kelas, bukan menggunakan prototipe apapun. Masalah utama peniruan objek adalah harus menggunakan cara konstruktur, ini bukan pilihan terbaik. Namun, jika menggunakan rantai prototipe, konstruktur dengan parameter tidak dapat digunakan. Bagaimana para pengembang memilih? Jawabannya sederhana, kedua-duanya digunakan.

在前一章,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:

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);
};

在此例子中,继承机制由两行突出显示的蓝色代码实现。在第一行突出显示的代码中,在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。在第二行突出显示的代码中,用原型链继承 ClassA 类的方法。由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。

下面的例子测试了这段代码:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName();	//输出 "John"

TIY