Implementation of Inheritance Mechanism in ECMAScript

Pagpapatupad ng mekanismo ng pagpapatirang

Para sa pagpapatupad ng mekanismo ng pagpapatirang sa ECMAScript, maaaring magsimula mula sa base class na ito. Ang lahat ng klase na binanggit ng mga developer ay maaaring maging base class. Para sa seguridad, ang lokal na klase at ang host class ay hindi puwedeng maging base class, upang maiwasan ang pampublikong access sa pinaghahahalalang kodigo ng browser, dahil ang mga ito ay maaaring gamitin para sa malasweerteng pag-atake.

Pagkatapos ng pagpili ng base class, maaari nang lumikha ng kanyang subclass. Ang paggamit ng base class ay kailangan mong pasiya. May mga oras na gusto mong gumawa ng isang base class na hindi puwedeng direktang gamitin, na lamang ginagamit upang magbigay ng pangkalahatang function sa subclass. Sa ganitong sitwasyon, ang base class ay tinuturing na abstract class.

Bagaman ang ECMAScript ay walang matinding paglilinaw kagaya ng ibang wika sa paglalarawan ng abstract class, minsan ito ay gumagawa ng ilang uri ng klase na hindi puwedeng gamitin. Karaniwang tinatawag na abstract class ang ganitong klase.

Ang nilikha na subclass ay magpapatirang ng lahat ng mga katangian at mga paraan ng super class, kasama ang paglilikha at pagpapatupad ng mga paraan. Tandaan, ang lahat ng mga katangian at mga paraan ay pampubliko, kaya ang subclass ay maaaring direktang ma-access ang mga ito. Ang subclass ay maaaring magdagdag ng bagong mga katangian at mga paraan na wala sa super class, o maaaring mapalitan ang mga katangian at mga paraan ng super class.

Paraan ng pag-inherit

Tulad ng ibang mga function, ang paraan ng pagpapatupad ng inheritance sa ECMAScript ay hindi naman pinagbatid, kundi sa pamamagitan ng pagmimok. Ito nangangahulugan na ang lahat ng detalye ng inheritance ay hindi naman pinapagmamanupatin ng interpreter. Bilang developer, mayroon kang karapatan na magpili ng pinakamayaang paraan ng inheritance.

Nakikilala namin sa inyo ang ilang espesipikong paraan ng inheritance.

Object masquerading

Sa orihinal na konsepto ng ECMAScript, walang nais na pagdesenyo ang object masquerading. Ito ay binuo lamang pagkatapos na naiintindihan ng mga developer ang mga mekanismo ng function, lalo na kung paano gamitin ang keyword na this sa kapaligiran ng function.

Ang prinsipyo nito ay tulad ng sumusunod: ang constructor ay gumagamit ng keyword na this upang magbigay ng lahat ng attribute at method (dahil sa klase na naisang ipakilala ng constructor). Dahil ang constructor ay isang function, maaring gamitin ang constructor ng ClassA bilang method ng ClassB, pagkatapos ay tinatawag ito. Ang ClassB ay makakatanggap ng attribute at method na nabuo ng constructor ng ClassA. Halimbawa, maaring idedefinir ang ClassA at ClassB sa pamamagitan ng sumusunod:

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

Naisko ba nating alam? Ang keyword na this ay tumutukoy sa kasalukuyang object na ginagawa ng constructor. Subalit sa ganitong method, ang this ay tumutukoy sa may kapangyarihan na object. Ang prinsipyo na ito ay gumawa ng ClassA bilang regular function upang buuin ang mechanism ng inheritance, hindi bilang constructor. Ganito na maaaring makuha ang mechanism ng inheritance sa pamamagitan ng paggamit ng constructor na ClassB:

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

Sa ganitong code, pinapagbigay ng newMethod sa ClassA (tandaan lamang, ang pangalan ng function ay lamang ang pointer dito). Pagkatapos, tinawag ang method, na pinagkakaloob ng argumento na galing sa constructor ng ClassB na sColor. Ang huling linya ng code ay inalis ang reference sa ClassA, kaya hindi na ito maaaring magamit sa hinaharap.

Lahat ng bagong attribute at method ay dapat idedefinir ang pagkatapos mabura ang bagong method code line. Sa kabilang banda, maaaring mapalitan ang kaugnay na attribute at method ng super klase:

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

Upang patunayan na ang nakaraang code ay epektibo, maaring magpatakbo ng sumusunod na halimbawa:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//បញ្ចេញ "blue"
objB.sayColor();	//បញ្ចេញ "red"
objB.sayName(); // Maglulista ng "John"

Ang object masquerading ay maaaring makuha ang multiple inheritance

Kasaliwan, ang object masquerading ay sumusuporta sa multiple inheritance. Ganito, ang isang klase ay maaaring umakyat sa maraming super klase. Ang multiple inheritance mechanism na inilalarawan ng UML ay tulad ng sumusunod:

Inheritance Mechanism UML Diagram Example

Halimbawa, kung mayroong dalawang klase na ClassX at ClassY, ang ClassZ ay nais umakyat sa dalawang klase, maaring gamitin ang sumusunod na code:

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

Ang halimbawa na ito ay katulad ng nakaraang halimbawa, ngunit ngayon ay tinatawag ang method na apply(). Kapag tinatawag ang method na apply(), ang unang argumento ay ang obj, na nangangahulugan na dapat ipagbigay ang halaga ng keyword na this sa function na sayColor(). Ang ikalawang argumento ay ang array na binubuo ng dalawang string, na tumutugma sa mga argumento na sPrefix at sSuffix sa function na sayColor(), at ang huling mensahe na binubuo ay "The color is blue, a very nice color indeed." na ipapakita.

Ang method na ito ay ginagamit din upang palitan ang tatlong linya ng assignment, pagtawag at pagtanggal ng bagong method:

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

Katulad din, ang unang argumento ay ang this, ang ikalawang argumento ay ang array na may isang halaga na color. Maaaring ilagay ang buong object na arguments ng ClassB bilang ikalawang argumento sa method na 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

Siyempre, kailangan lamang na ang pagkakabanggit ng mga argumento sa super klase ay magkakasunod-sunod sa pagkakabanggit ng mga argumento sa sub klase upang maipasa ang mga argumento object. Kung hindi, dapat lumikha ng isang bagong array, na ilagay ang mga argumento sa tamang pagkakasunod-sunod. Gayundin, maaari ring gamitin ang call() method.

Prototype chaining (prototype chaining)

Ang ganitong uri ng pagpapatuloy ay orihinal na ginagamit sa prototype chain sa ECMAScript. Sa nakaraang kabanata, pinakiusapan ang paglilipat ng prototype ng klase. Ang prototype chain ay pinapalawak ang ganitong paraan, na nagbibigay ng isang kagila-gilalas na paraan upang maisakatuparan ang mekanismo ng pagpapatuloy.

Sa nakaraang kabanata, natutunan natin na ang prototype ay isang template, ang lahat ng mga bagay na dapat magiging instance ay gumagamit nito bilang batayan. Sa kabuuan, ang anumang katangian at paraan ng prototype ay ipinasa sa lahat ng mga instance ng klase. Ang prototype chain ay gumagamit ng ganitong mekanismo upang maisakatuparan ang mekanismo ng pagpapatuloy.

Kung ayon sa paraan ng prototype ang muling paglilipat ng klase sa nakaraang halimbawa, sila ay magiging gayon na:

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

Ang kahihinatnan ng paraan ng prototype ay sa nakapintang mga linya ng kodigo. Dito, ang prototype attribute ng ClassB ay ginawang instance ng ClassA. Ito ay napaka-interesante, dahil gusto nating magkaroon ng lahat ng attribute at method ng ClassA, ngunit hindi namin gusto magbigay ng bawat isa sa prototype attribute ng ClassB. Mayroon bang mas mahusay na paraan kaysa sa ipaalam ng instance ng ClassA sa prototype attribute?

Pansin:Tawagan ang constructor ng ClassA, walang inilalagay na argumento. Ito ay pangkaraniwang gawain sa prototype chain. Tiyaking walang argumento ang constructor.

Kasama ng pagpapaubos ng bagay, lahat ng mga attribute at method ng subclass ay dapat magpakita sa prototype attribute pagkatapos na ipaalam, sapagkat ang lahat ng mga method na inialam bago pa ito ay mawawala. Bakit? Dahil ang prototype attribute ay napalitan ng bagong bagay, ang orihinal na bagay na nagdagdag ng mga method ay mapapawalan. Kaya, ang mga kodong dapat idagdag sa ClassB klase para sa name attribute at sayName() method ay tulad nang ito:

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

Maaari mong pagsuri ang mga kodong ito sa pamamagitan ng pagpapatay ng mga sumusunod:

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

TIY

Bilang karagdagan, sa prototype chain, ang paglilipat ng instanceof operator ay napaka-kakaibang paraan. Para sa lahat ng mga instance ng ClassB, instanceof ay naglalaro ng true para sa parehong ClassA at ClassB. Halimbawa:

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

Sa mundo ng mahinang uri ng ECMAScript, ito ay napaka-kapaki-pakinabang na kasangkapan, ngunit hindi ito maaaring gamitin kapag gumagamit ng pagpapaubos ng bagay.

Ang kapintasan ng prototype chain ay hindi sumusuporta sa maraming pagkakasunod. Tandaan, ang prototype chain ay gagamitin ang isa pang uri ng bagay upang magsusulat muli ang prototype attribute ng klase.

Mga hibrido

Ang paraan ng pagkakasunod na ito ay gumagamit ng constructor function para sa paglalarawan ng klase, hindi sa anumang prototype. Ang pangunahing problema ng pagpapaubos ng bagay ay dapat gamitin ang paraan ng constructor, ito ay hindi ang pinakamahusay na pagpipilian. Gayunpaman, kung gamitin ang prototype chain, hindi na maaaring gamitin ang constructor function na may argumento. Paano ang pagpipilian ng developer? Ang sagot ay napakasimple, gamitin pareho ang dalawa.

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