ECMAScript کا یجی کا عمل

继承机制的实现

要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。

选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。

尽管 ECMAScript 并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。

ایک فرع کلاس جوائز کیا جائیگا کہ وہ تمام پرانے کلاس کی اور پرانے کلاس کی تمام خاصیت اور طریقوں کو وراثت لیتی ہو، جس میں تعمیر کا طریقہ اور طریقوں کا عمل شامل ہوتا ہے۔ یاد رکھو، تمام خاصیت اور طریقوں نہایت عام ہیں، لہذا فرع کلاس اس طریقوں کو سادے طریقے سے استعمال کرسکتا ہے۔ فرع کلاس پرانے کلاس میں نہیں موجود نئی خاصیت اور طریقوں کو بھی شامل کرسکتا ہے، یا پرانے کلاس کی خاصیت اور طریقوں کو بھی کوریج کرسکتا ہے۔

روش‌های ارث‌گیری

مانند سایر امکانات، روش‌های تحقق ارث در ECMAScript بیش از یک راه دارند. این به این دلیل است که مکانیزم ارث در JavaScript به صورت مشخص تعیین نشده است، بلکه از طریق تقلید تحقق یافته است. این به این معناست که تمام جزئیات ارثگیری به طور کامل توسط تفسیرگر پردازش نمی‌شود. به عنوان توسعه‌دهنده، شما حق دارید تصمیم بگیرید که کدام روش ارثگیری مناسب‌تر است.

در اینجا چندین روش خاص از ارث‌گیری برای شما معرفی می‌شود.

مخفی‌سازی اشیاء

در زمان طراحی ECMAScript، اصلاً قصد طراحی object masquerading (مخفی‌سازی اشیاء) نبود. این یک توسعه بود که پس از اینکه توسعه‌دهندگان شروع به درک نحوه کارکرد توابع و به ویژه استفاده از کلیدواژه this در محیط توابع کردند، به وجود آمد.

مکانیزم زیر است: سازنده از کلیدواژه this برای تعیین تمام ویژگی‌ها و روش‌ها استفاده می‌کند (یعنی از روش‌های سازنده تعریف شده در کلاس). چون سازنده فقط یک تابع است، می‌توان از سازنده ClassA به عنوان یک متد برای ClassB استفاده کرد و آن را فراخوانی کرد. ClassB ویژگی‌ها و روش‌های تعریف شده در سازنده ClassA را دریافت می‌کند. به عنوان مثال، ClassA و ClassB را به صورت زیر می‌توان تعریف کرد:

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

یادتان می‌آید؟ کلیدواژه this به کلاسی که در حال حاضر توسط سازنده ایجاد می‌شود اشاره دارد. اما در این متد، این این این اشاره‌گر به کلاسی که این متد به آن تعلق دارد اشاره دارد. این اصل این است که ClassA را به عنوان یک متد معمولی برای ایجاد مکانیزم ارث استفاده می‌کند، نه به عنوان یک سازنده. مکانیزم ارث با استفاده از سازنده ClassB به صورت زیر قابل تحقق است:

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

در این کد، به ClassA روش newMethod اختصاص داده شده است (لطفاً به خاطر بسپارید که نام‌های متد فقط اشاره‌گرهایی به آن هستند). سپس این متد فراخوانی می‌شود و به آن پارامترهای سازنده ClassB sColor داده می‌شود. در آخرین خط کد، اشاره‌گر به 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"

object masquerading می‌تواند多重 ارث را تحقق بخشد

جالب است که object masquerading از多重 ارث پشتیبانی می‌کند. یعنی یک کلاس می‌تواند از چندین کلاس مادر ارث ببرد. مکانیزم多重 ارث که با 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 کا اوبجیکٹ کا اوبجیکٹ بنے، لہذا first پارامتر this ہوتا ہے۔ دوسرے پارامتر sColor دونوں کلاسوں کے لئے منفرد پارامتر ہوتا ہے۔

apply() کا مطلب

apply() کا مطلب دو پارامتر ہوتا ہے، وہ اینکشن کا اوبجیکٹ اور فونکشن کو منتقل کئے جانے والے پارامتروں کا آرائی اور کس طرح سے مثال:

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 اختصاص داده شود. دومین پارامتر آرایه‌ای از دو رشته است که با پارامترهای sPrefix و sSuffix در روش sayColor() هماهنگ هستند و پیام نهایی "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);
    };
}

به طور مشابه، اولین پارامتر همچنان this است، و دومین پارامتر آرایه‌ای است که فقط یک مقدار color دارد. می‌توانید کل شی arguments کلاس 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 (زنجیره‌ی prototype)

این شکل از ارث‌برداری در ECMAScript برای prototype chaining استفاده می‌شود. در فصل قبل روش تعریف کلاس‌ها با استفاده از prototype معرفی شد. prototype chaining این روش را گسترش داد و به یک روش جالب برای عملکرد مکانیزم ارث‌برداری تبدیل شد.

در فصل قبل یاد گرفتیم که، نمونه prototype یک قالب است که همه‌ی اشیاء قابل اجرا بر اساس این قالب هستند. به طور خلاصه، هر یک از ویژگی‌ها و روش‌های نمونه prototype به همه‌ی نمونه‌های کلاس منتقل می‌شوند. از این طریق، مکانیزم ارث‌برداری از طریق prototype‌های زنجیره‌ای (prototype chaining) عمل می‌کند.

اگر کلاس قبل از این مثال را با روش نمونه‌ای دوباره‌سازی کنید، آنها به صورت زیر خواهند شد:

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

پروٹوٹائپ طریقے کا حیرت انگیز مقصد یہ ہے کہ بااختیار کا کد درج کیا گیا ہے۔ یہاں، ClassB کی پروٹوٹائپ پرپریتی کو ClassA کی مثال کو دوبارہ لکھا جائے ہے۔ یہ بات بہت مفید ہے، کیونکہ ClassA کی تمام اثاثے اور طریقوں کو حاصل کرنا چاہیے، لیکن وہ ClassB کی پروٹوٹائپ پرپریتی کو ایک ایک طور پر نہیں دوبارہ لکھنا چاہتا۔ آیا پروٹوٹائپ پرپریتی کو ClassA کی مثال کو دوبارہ لکھنا بہتر طریقہ نہیں ہوگا؟

توجہ داد:ClassA کا تعمیر کار کو بلا پارامتروں چلایا جائے، یہ پروٹوٹائپ چین میں استاندار طریقہ ہے۔ یقین رکھنا چاہیے کہ تعمیر کار کو کوئی پارامتر نہیں دیا جائے۔

اشیاء کی قابلیت کا استعمال کی طرح، فرزند کلاس کی تمام اثاثے اور طریقوں کو پروٹوٹائپ پرپریتی کو دوبارہ لکھنے کے بعد استعمال کیا جانا چاہیے، کیونکہ اس سے پہلے دوبارہ لکھنے کا تمام طریقوں کو مٹا جائے گا۔ چرا؟ کیونکہ پروٹوٹائپ پرپریتی کو نئی اشیاء کی جگہ پر نئی اشیاء کو دوبارہ لکھا جائے گا، جس میں نئی طریقوں کو اضافہ کیا گیا تھا، اس کا استعمال مٹ جائے گا۔ لہذا، 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

علاوہ ازیں، پروٹوٹائپ چین میں instanceof آپریٹر کا چلنا بھی خاص طریقے سے ہوتا ہے۔ ClassB کی تمام مثالوں کے لئے instanceof آپریٹر ClassA اور ClassB دونوں کو true درج کرتا ہے۔ مثلاً:

var objB = new ClassB();
alert(objB instanceof ClassA);	// باخبر کریں "true"
alert(objB instanceof ClassB);	// باخبر کریں "true"

ایکم اسکریپٹ کی کمزوری کی دنیا میں یہ بات بہت مفید ہوتی ہے، لیکن اشیاء کی قابلیت کا استعمال کے دوران اس کا استعمال نہیں کیاجاسکتا۔

پروٹوٹائپ چین کا ناچن کا یہ بات نہیں کہ وہ کثیر وراثت کا استعمال نہیں کرتا۔ یاد رکھو، پروٹوٹائپ چین کا استعمال دوسری نوعیت کی اشیاء کا استعمال کرتا ہے تاکہ کلاس کی پروٹوٹائپ پرپریتی کو دوبارہ لکھ سکتا ہے۔

مکس کریشن

یہ وراثت طریقہ تعمیر کار کا استعمال کرتا ہے، نہ کہ کوئی بھی اُس سے نمونہ کا استعمال ہوتا ہے۔ اشیاء کی قابلیت کا اصل مسئلہ یہ ہے کہ وہ تعمیر کار طریقہ استعمال کرنا ہوتا ہے، یہ بہترین انتخاب نہیں ہے۔ باوجود اس کے اگر پروٹوٹائپ چین استعمال کی جائے تو پارامتراوی تعمیر کار استعمال نہیں کیاجاسکتا۔ طلبکاروں کو کس طرح منتخب کرنا چاہیے؟ جواب سادہ ہے، دونوں دونوں استعمال کریں۔

پچھلے باب میں، ہم نے بہترین طریقے سے کلاس بنانے کا تعارف کیا تھا، یعنی اپنائیں خاصیتوں کو تعمیر فونکشن کے ذریعے اور طریقوں کو prototype کے ذریعے تعارف کیا تھا۔ یہ طریقہ نکا لگا مکینزم کے لئے بھی قابل استعمال ہوتا ہے، یعنی اشیاء کی بنیاد پر تعمیر فونکشن کی خاصیتوں کو نکا لگا کیا جاسکتا ہے اور prototype کی بنیاد پر 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 خاصیت کو نکا لگا کیا گیا ہے۔ دوسرے خوشگوار سطر میں، prototype کی بنیاد پر ClassA کی کلاس کے طریقوں کو نکا لگا کیا گیا ہے۔ اس طرح کا مکینزم prototype کی بنیاد پر استعمال کیا گیا ہے، اس لئے instanceof آپریٹر بھی صحیح طریقے سے چل سکتا ہے۔

درج ذیل مثال اس کو کا ملازمت کرتا ہے:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//خروج "blue"
objB.sayColor();	//خروج "red"
objB.sayName();	//خروج "John"

TIY