Implementation of Inheritance Mechanism in ECMAScript

ສະແດງພາຍຫຼັງ

ສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງ

ຫຼັງຈາກການຄັດເລືອກສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງ

ECMAScript ບໍ່ມີການກໍານົດອັນຕະລາຍປະເພດສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງ

ສະແດງນຳສັນຍາສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງສະແດງພາຍຫຼັງ

继承的方式

和其他功能一样,ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

下面为您介绍几种具体的继承方式。

ການລວມປະເພດ

ການວາງຂອງ ECMAScript ບໍ່ມີຄວາມຄິດວ່າຈະອອກແບບການລວມປະເພດ (object masquerading). ມັນແມ່ນມີຄວາມພິມຂຶ້ນຫຼັງຈາກພະນັກງານເຜີຍແຜ່ການປະຕິບັດກົນລະບົບວັດຖຸພາສາຫຼັງນີ້ທີ່ເຂົ້າໃຈວ່າວັດຖຸພາສາຫຼັງນີ້ມີຄວາມຫຼົງເຫຼົາຫຼາຍ.

ຄວາມຕາມທີ່: ກົນລະບົບຄົນໃໝ່ສ້າງຄວາມຂອງປະເພດໃຫ້ທຸກຄວາມແລະກົນລະບົບ (ເພາະກົນລະບົບຄົນໃໝ່ຫຼັງນີ້ມີຄວາມຫຼົງເຫຼົາ). ຍ້ອນວ່າກົນລະບົບຄົນໃໝ່ພຽງແຕ່ກົນລະບົບພະຍາຍາມ, ສາມາດເຮັດໃຫ້ກົນລະບົບຄົນໃໝ່ ClassA ເປັນກົນລະບົບຂອງ ClassB, ແລະ ເອົາກົນລະບົບຂອງກົນລະບົບຄົນໃໝ່ຂອງ ClassA ເຂົ້າໄປ. ເປັນຕົວຢ່າງທີ່ພິມປະເພດ ClassA ແລະ ClassB ທີ່ພິມທີ່:

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

ຍັງຈື່ງບອກຫນັງນີ້ບໍ່? ຄຳເອກະສານ this ເປັນຄຳເອກະສານທີ່ອະທິບາຍອົງກອນທີ່ກຳລັງສ້າງ. ແຕ່ໃນກົນລະບົບນີ້ this ແມ່ນທີ່ອະທິບາຍອົງກອນ. ຄວາມຕາມທີ່ກວດສອບການລວມປະເພດຫຼາຍອັນໃຫ້ ClassA ເປັນກົນລະບົບປະດິດທີ່ພຽງພໍ ບໍ່ແມ່ນກົນລະບົບການສ້າງປະເພດ. ການດຳເນີນກົນລະບົບ ClassB ທີ່ໃຊ້ຄຳເອກະສານຄົນໃໝ່ສະໜັບສະໜູນການລວມປະເພດ:

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

ໃນກິດຈະກຳດັ່ງກ່າວນີ້ວ່າມີຄວາມທີ່ຖືກຈັດຕັ້ງໃຫ້ ClassA ກວດສອບການລະບົບ newMethod (ບໍ່ວ່າຊື່ພາສາຫຼັງນີ້ມີຄວາມຫຼົງເຫຼົາ). ເລື່ອງຕໍ່ມາຈະເອົາການກ່າວຕົວຂອງ ClassA ໄປຫຼາຍຫນັງນີ້.

ທຸກຄວາມຂອງບັນດາປະເພດໃໝ່ ແລະ ກົນລະບົບໃໝ່ຕ້ອງຖືກກໍານົດຫຼັງຈາກການຖອນກົນລະບົບຄົນໃໝ່ຂອງຄວາມຂອງປະເພດຄົນໃໝ່. ຖ້າບໍ່ຈະການດັ່ງກ່າວນີ້ຫນັງນີ້ຈະມີການກະຈາຍກັບບັນດາປະເພດ ແລະ ກົນລະບົບຂອງປະເພດຄົນໃໝ່:

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

ເພື່ອສະແດງວ່າລິກະສັບກ່ອນແມ່ນມີຜົນທີ່ດີຫຼາຍຫນັງນີ້ທີ່ຈະຕັ້ງການ:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName();		//ການສະແດງ "John"

ມັນສາມາດລວມປະເພດຫຼາຍອັນ

ສິ່ງທີ່ຈິງວ່າມັນສາມາດສະໜັບສະໜູນການລວມປະເພດຫຼາຍອັນ. ສະແດງຄືກັບການລວມປະເພດຫຼາຍອັນໃນ UML ທີ່ສະແດງລຸ່ມ:

ການສືບຕໍ່ຄວາມການດຳເນີນງານ UML ຮູບສະແດງຕົວຢ່າງ

ບໍ່ວ່າມີສອງປະເພດ ClassX ແລະ ClassY ວ່າ ClassZ ຕ້ອງລວມປະເພດດັ່ງກ່າວນີ້ທີ່ພົບຄືວ່າ:

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."));

ຄືກັນກັບຫນັງການສອນວິວັດຕະວາຂອງພວກເຂົາ, ພຽງແຕ່ວ່າພວກເຂົາກຳລັງເອິ້ນວິທີ apply() ທີ່ຈະຖືກສົ່ງຕົວມາ. ເມື່ອເອິ້ນວິທີ apply() ທີ່ຈະຖືກສົ່ງຕົວມາ, ຄັນທີ່ຈະຖືກສົ່ງຕົວມາຍັງແມ່ນ obj, ເພື່ອສະແດງວ່າຄຳສັ່ງຂອງພະຍາດ this ຂອງວິທີ sayColor() ຈະຖືກສະແດງເປັນ obj. ຄັນທີ່ຈະຖືກສົ່ງຕົວມາຈະຖືກສ້າງຈາກຄຳສັ່ງສອງຄຳສັ່ງທີ່ມີຄວາມທີ່ຈະຖືກປະຕິບັດ, ແລະປະຕິບັດຄວາມທີ່ຈະຖືກສະແດງຈະຖືກສະແດງຄື "The color is blue, a very nice color indeed." ແລະຈະຖືກສະແດງອອກມາ.

ວິທີນີ້ຍັງຖືກໃຊ້ເພື່ອປ່ຽນຄຳສັ່ງສາມຍິງທີ່ຖືກກໍານົດກ່ອນ, ການເອິ້ນ, ແລະການລຶບວິທີບັນທຶກຂອງການສ້າງຄວາມບັນທຶກບັນທຶກຄືກັນ.

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

ສະນັ້ນທີ່ຈະຖືກສົ່ງຕົວມາຄັນທີ່ຈະຖືກສົ່ງຕົວມາຄືກັນຫຍັງທີ່ຈະຖືກສົ່ງຕົວມາຄືກັນ. ສະນັ້ນກໍ່ວ່າອົງກອນ arguments object ຂອງ ClassB ທີ່ຈະຖືກສົ່ງຕົວມາໃຫ້ວິທີ 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

ຕາມທີ່ມີຫຍັງພຽງແຕ່ວ່າລຳດັບຂອງພະຍາດຂອງປະເພດສະພາບພາຍໃນປະເພດຜູ້ຈຳນວນທີ່ບໍ່ມີຫຍັງຈະຖືກສົ່ງຕົວມາ. ຖ້າບໍ່ມີຫຍັງຈະຕ້ອງສ້າງອົງກອນຄຳສັ່ງພາຍໃນຕົວຢ່າງທີ່ມີລຳດັບຄວາມທີ່ຖືກປະຕິບັດ. ຍັງມີວິທີ call() ທີ່ສາມາດໃຊ້ໄດ້ອີກ.

prototype chaining

ການຮັບພີມາດີນນີ້ໃນ ECMAScript ມີຄວາມຕ້ອງການໃຫ້ສ້າງ prototype chain. ຫນັງການສອນວິວັດຕະວາຂອງພວກເຂົາໄດ້ສະເໜີວິທີການປ່ຽນຊົງວິງວິງກອນ. prototype chain ເລືອກຂັ້ນນິວາຍນີ້ເພື່ອສ້າງກົນລະບົບການຮັບພີມາດີນວິດີີລະຫວ່າງວິງວິງກອນ.

ຕາມທີ່ຮຽນໃນຫນັງການສອນວິວັດຕະວາຂອງພວກເຂົາວ່າ,prototype object ເປັນຕົວຢ່າງທີ່ຖືກນຳໃຊ້ເພື່ອກໍ່ສ້າງອົງກອນທີ່ຈະຖືກນຳໃຊ້ພາຍໃນຕົວຢ່າງດັ່ງກ່າວ. ນັ້ນກໍ່ວ່າທຸກຄວາມຂອງ prototype object ແລະວິທີທຸກຄວາມຂອງພວກເຂົາຈະຖືກສົ່ງໃຫ້ໃຫ້ກັບທຸກພວກຂອງອົງກອນຂອງວິງວິງກອນດັ່ງກ່າວ. prototype chain ໃຊ້ປະສົບການນີ້ເພື່ອສ້າງກົນລະບົບການຮັບພີມາດີນ.

ຖ້າໃຊ້ວິທີ prototype ໃນການປ່ຽນຊົງວິງກ່ອນໃນຄວາມຄົງພາບຂອງພວກເຂົາແລ້ວຈະກາຍເປັນຮູບແບບດັ່ງຕໍ່ມາ:

function ClassA() {
}
ClassA.prototype.color = "blue";
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);
};

可通过运行下面的例子测试这段代码:

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

TIY

ນອກຈາກນັ້ນທີ່ສາຍທິກັບກັບວິທິງບໍ່ຈະນຳໃຊ້ຄວາມລວມກັບວິທິງບໍ່ຈະນຳໃຊ້ຄວາມລວມກັບວິທິງ.

var objB = new ClassB();
alert(objB instanceof ClassA);	//ອອກຄຳຖະແຫຼງ "true"
alert(objB instanceof ClassB);	//ອອກຄຳຖະແຫຼງ "true"

ໃນໂລກຂອງ ECMAScript ທີ່ມີຄວາມບໍ່ຄາດການທີ່ຫຼາຍຫນັງຫນານຍາກຫນັງທີ່ຈະນຳໃຊ້ວິທິງສ້າງຄວາມລວມກັບວິທິງບໍ່ຈະນຳໃຊ້ວິທິງສ້າງຄວາມລວມກັບວິທິງ.

ຄວາມລວມຂອງສາຍທິກັບບໍ່ສາມາດນຳໃຊ້ການລວມກັບຫນັງທີ່ຫຼາຍ.ຈະຮູ້ວ່າສາຍທິກັບກັບຫນັງບໍ່ຈະນຳໃຊ້ຄວາມລວມກັບວິທິງທີ່ມີປະກາດ.ບໍ່ດັ່ງນັ້ນຄວາມລວມຂອງສາຍທິກັບກັບຫນັງບໍ່ຈະນຳໃຊ້ຄວາມລວມກັບວິທິງທີ່ມີປະກາດ.

ວິທິງປະສົມ

ການລວມທາງທີ່ຈະນຳໃຊ້ຄູ່ມິດຄວາມລວມກັບວິທິງສ້າງປະເພດບໍ່ແມ່ນຈະນຳໃຊ້ບົດປະດັບເປັນຫນັງວິທິງ.ຄວາມຍາກທີ່ຈະນຳໃຊ້ການສາຍທິກັບຄວາມລວມກັບວິທິງບໍ່ແມ່ນມີຫນັງສະເພາະ.ບໍ່ດັ່ງນັ້ນຖ້ານຳໃຊ້ວິທິງສາຍທິກັບກັບວິທິງບໍ່ຈະນຳໃຊ້ຄວາມລວມກັບວິທິງທີ່ມີປະກາດ.ນັກພັດທະນາຈະເລືອກແນວໃດ?ຄຳຕອບອີກມັນຄືກັນຈະນຳໃຊ້ທັງສອງຂອງພວກມັນ.

在前一章,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 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