یکی از ویژگیهای کلیدی که کلاسها با خود به ارمغان میآورند (علاوه بر کپسولهسازی راحت با فیلدهای خصوصی)، مبحث ارثبری (Inheritance) است. ارثبری یعنی یک Object میتواند بخش بزرگی از رفتارهای یک Object دیگر را «قرض» بگیرد، در حالی که بخشهای خاصی از آن را با منطق خودش بازنویسی (Override) یا تقویت کند.
برای مثال، فرض کنید کلاس Color ما اکنون نیاز دارد از شفافیت (Transparency) نیز پشتیبانی کند. شاید وسوسه شویم که یک فیلد جدید برای مشخص کردن میزان شفافیت به همان کلاس اضافه کنیم:
class Color {
#values;
constructor(r, g, b, a = 1) {
this.#values = [r, g, b, a];
}
get alpha() {
return this.#values[3];
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha value must be between 0 and 1");
}
this.#values[3] = value;
}
}
اما این کار یعنی تمام نمونهها—حتی اکثریت قاطع آنها که شفاف نیستند (و مقدار تکمیلی alpha آنها 1 است)—باید این مقدار اضافی را در حافظه حمل کنند که اصلاً ظریف و بهینه نیست. علاوه بر این، اگر ویژگیهای برنامه مدام رشد کنند، کلاس Color ما بسیار حجیم و نگهداری آن دشوار خواهد شد.
در برنامهنویسی شیءگرا، برای حل این مشکل یک Derived class (کلاس مشتقشده یا فرزند) میسازیم. کلاس فرزند به تمام ویژگیهای عمومی (Public) کلاس والد (Parent class) دسترسی دارد. در جاوااسکریپت، کلاسهای مشتقشده با عبارت extends اعلان میشوند که نشان میدهد این کلاس از چه کلاسی ارثبری میکند.
class ColorWithAlpha extends Color {
#alpha;
constructor(r, g, b, a) {
super(r, g, b); // فراخوانی سازنده والد
this.#alpha = a;
}
get alpha() {
return this.#alpha;
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha value must be between 0 and 1");
}
this.#alpha = value;
}
}
در این کد چند نکتهی بسیار مهم وجود دارد که باید فوراً به آنها توجه کرد:
super در متد سازندهاولین نکته این است که در constructor، ما متد ()super را صدا زدهایم. این یک قانون و الزام ساختاری در جاوااسکریپت است که قبل از دسترسی به کلمه کلیدی this، حتماً باید ()super را فراخوانی کنید. دستور ()super در واقع سازندهی کلاس والد را صدا میزند تا کلمه کلیدی this را مقداردهی اولیه کند؛ این کار اینجا تقریباً معادلِ عبارت (this = new Color(r, g, b است. شما میتوانید قبل از دستور ()super کدهایی بنویسید، اما به هیچ وجه اجازه ندارید قبل از آن به this دسترسی داشته باشید؛ زبان برنامهنویسی مانع از دسترسی شما به this مقداردهینشده میشود.
پس از اینکه کلاس والد کارش با تغییر و مقداردهی this تمام شد، کلاس فرزند میتواند منطق اختصاصی خودش را پیاده کند. در اینجا ما یک فیلد خصوصی به نام alpha# اضافه کردیم و یک جفت getter/setter نیز برای تعامل با آن قرار دادیم.
یک کلاس مشتقشده، تمام متدهای کلاس والد خود را به ارث میبرد. برای مثال، دسترسیدهندهی get red را که در بخشهای قبلی به کلاس Color اضافه کرده بودیم در نظر بگیرید؛ با اینکه ما چنین ویژگیای را داخل کلاس ColorWithAlpha تعریف نکردهایم، اما هنوز هم میتوانیم به red دسترسی داشته باشیم چون این رفتار توسط کلاس والد مشخص شده است:
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color.red); // خروجی: 255
کلاسهای فرزند میتوانند متدهای کلاس والد را Override (بازنویسی) کنند. به عنوان مثال، تمام کلاسها به صورت ضمنی و خودکار از کلاس پایه Object ارثبری میکنند که متدهای پایهای مثل ()toString را تعریف میکند. اما متد پیشفرض ()toString به بیفایده بودن معروف است، چون در بیشتر موارد صرفاً عبارت [object Object] را چاپ میکند:
console.log(red.toString()); // [object Object]
در عوض، کلاس ما میتواند این متد را بازنویسی کند تا مقادیر RGB رنگ را چاپ کند:
class Color {
#values;
// …
toString() {
return this.#values.join(", ");
}
}
console.log(new Color(255, 0, 0).toString()); // '255, 0, 0'
درون کلاسهای فرزند، شما میتوانید با استفاده از کلمه کلیدی super به متدهای کلاس والد دسترسی داشته باشید. این قابلیت به شما اجازه میدهد متدهای کمکیِ تقویتشده بسازید و از تکرار کد (Code duplication) جلوگیری کنید:
class ColorWithAlpha extends Color {
#alpha;
// …
toString() {
// متد toString() کلاس والد را صدا میزند و خروجی آن را تکمیل میکند
return `${super.toString()}, ${this.#alpha}`;
}
}
console.log(new ColorWithAlpha(255, 0, 0, 0.5).toString()); // '255, 0, 0, 0.5'
وقتی از دستور extends استفاده میکنید، متدهای استاتیک (Static methods) نیز از یکدیگر ارثبری میکنند؛ بنابراین میتوانید آنها را هم بازنویسی یا تقویت کنید:
class ColorWithAlpha extends Color {
// …
static isValid(r, g, b, a) {
// متد استاتیک isValid() کلاس والد را صدا میزند و شرط جدید را به آن اضافه میکند
return super.isValid(r, g, b) && a >= 0 && a <= 1;
}
}
console.log(ColorWithAlpha.isValid(255, 0, 0, -1)); // false
کلاسهای فرزند به فیلدهای خصوصی کلاس والد دسترسی ندارند. این یکی دیگر از جنبههای کلیدی سختگیرانه بودن (hard private) فیلدهای خصوصی در جاوااسکریپت است. حوزه دسترسی (Scope) فیلدهای خصوصی کاملاً به بدنه همان کلاسی که در آن اعلان شدهاند محدود است و به هیچ کد بیرونی اجازه دسترسی نمیدهد:
class ColorWithAlpha extends Color {
log() {
console.log(this.#values);
// خطای سینتکس:
// SyntaxError: Private field '#values' must be declared in an enclosing class
}
}
یک قانون مهم: هر کلاس در جاوااسکریپت فقط میتواند از یک کلاس دیگر ارثبری کند (Single inheritance). این قانون جلوی مشکلات پیچیده در ارثبری چندگانه، مانند «مسئله الماس» (Diamond problem) را میگیرد. با این حال، به دلیل ماهیت داینامیک جاوااسکریپت، هنوز هم میتوان با ترکیب کلاسها (Class composition) و Mixins به افکت ارثبری چندگانه دست یافت.
در نهایت، نمونههای ساختهشده از کلاس فرزند، نمونهای از کلاس والد (پایه) نیز محسوب میشوند:
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color instanceof Color); // true
console.log(color instanceof ColorWithAlpha); // true
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript