وراثت بین الگوها: Inheritance مبتنی بر Prototype و مبتنی بر Class

مقدمه

تا اینجا یاد گرفتیم چگونه یک الگوی واحد برای ساخت شیءهای مشابه بسازیم؛ چه با Constructor Function و prototype، چه با Class. اما گاهی چند الگو با یکدیگر رابطه‌ی سلسله‌مراتبی دارند؛ برای نمونه، یک Admin در واقع نوعی User است، اما با چند قابلیت اضافی. در این درس بررسی می‌کنیم چگونه یک الگو می‌تواند از الگوی دیگری وراثت (Inheritance) ببرد؛ هم به روش سنتی مبتنی بر Prototype، و هم به روش مدرن مبتنی بر Class.

Inheritance مبتنی بر Prototype (روش سنتی)

برای این‌که یک Constructor Function، از Constructor Function دیگری وراثت ببرد، باید دو کار انجام دهیم: اول، Prototype تابع فرزند را طوری تنظیم کنیم که به Prototype تابع والد زنجیر شود؛ دوم، Constructor والد را از داخل Constructor فرزند فراخوانی کنیم تا Property های اختصاصی هم به‌درستی مقداردهی شوند.

پیاده‌سازی Inheritance با Prototype
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

سه بخش کلیدی این کد را جداگانه بررسی می‌کنیم:

  • User.call(this, name) — همان‌طور که در فصل this و Binding درباره‌ی call دیدیم، این خط باعث می‌شود بدنه‌ی User با this برابر با شیء تازه‌ساخته‌شده‌ی Admin اجرا شود؛ به همین دلیل this.name = name داخل User، دقیقاً روی همان شیء Admin عمل می‌کند.
  • Admin.prototype = Object.create(User.prototype) — این خط، دقیقاً طبق چیزی که در درس اول همین فصل درباره‌ی Object.create دیدیم، یک شیء جدید می‌سازد که Prototype آن، User.prototype است؛ و همین شیء را به‌عنوان prototype جدید Admin قرار می‌دهد. نتیجه این است که Admin.prototype اکنون خودش در Prototype Chain به User.prototype متصل است.
  • Admin.prototype.constructor = Admin — چون خط قبل، prototype پیش‌فرض Admin (که constructor آن به‌درستی به Admin اشاره می‌کرد) را کاملاً با یک شیء جدید جایگزین کرد، این خط، Property constructor را که در درس قبل دیدیم، به‌درستی روی Admin بازمی‌گرداند.

به همین دلیل، admin.greet() کار می‌کند: جاوااسکریپت greet را روی خودِ admin پیدا نمی‌کند، سپس روی Admin.prototype هم پیدا نمی‌کند، اما با ادامه‌ی Prototype Chain به User.prototype می‌رسد و آن‌جا greet را پیدا می‌کند.

Inheritance مبتنی بر Class (روش مدرن)

حالا دقیقاً همین رابطه‌ی وراثتی را با Class بازنویسی می‌کنیم:

پیاده‌سازی Inheritance با extends و super
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

کلیدواژه‌ی extends دقیقاً همان دو کاری را که در نسخه‌ی سنتی به‌صورت دستی انجام دادیم -یعنی زنجیر کردن prototype ها و اصلاح constructor- به‌طور خودکار انجام می‌دهد. کلیدواژه‌ی super(name) نیز جایگزین همان User.call(this, name) است؛ یعنی constructor کلاس والد را با همان this شیء تازه‌ساخته‌شده فراخوانی می‌کند. یک قانون مهم و اجباری اینجا وجود دارد: در یک Class فرزند که constructor مخصوص به خودش را دارد، پیش از استفاده از this، باید حتماً super(...) فراخوانی شود؛ در غیر این صورت، جاوااسکریپت خطا می‌دهد.

نتیجه‌ی نهایی این دو پیاده‌سازی، از نظر رفتاری کاملاً یکسان است: admin.greet() از طریق Prototype Chain به User.prototype (که در نسخه‌ی Class، همان چیزی است که extends در پس‌زمینه ساخته) دسترسی پیدا می‌کند.

فراخوانی متد Override شده از والد با super

گاهی می‌خواهیم متدی را در کلاس فرزند بازنویسی (Override) کنیم، اما همچنان بخشی از رفتار نسخه‌ی والد را هم حفظ کنیم؛ super در داخل متدها (نه فقط در constructor) دقیقاً همین امکان را می‌دهد:

استفاده از super برای فراخوانی متد والد
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

در این مثال، متد greet در Admin، متد هم‌نام خودش را در User بازنویسی (Override) کرده است؛ اگر super.greet() فراخوانی نمی‌شد، نسخه‌ی User اصلاً اجرا نمی‌شد. با این خط، ابتدا نسخه‌ی User اجرا می‌شود و سپس ادامه‌ی بدنه‌ی متد Admin؛ به همین دلیل هر دو پیام چاپ می‌شوند.

instanceof در زنجیره‌ی وراثت

همان‌طور که در فصل انواع داده دیدیم، instanceof کل Prototype Chain را بررسی می‌کند؛ به همین دلیل یک Instance از Admin، هم instanceof Admin است و هم instanceof User:

console.log(admin instanceof Admin); // true
console.log(admin instanceof User);  // true — چون Admin از User وراثت می‌برد

جدول جمع‌بندی

مفهومروش Prototype (سنتی)روش Class (مدرن)
زنجیر کردن Prototype هاObject.create(Parent.prototype)extends
فراخوانی constructor والدParent.call(this, ...)super(...)
فراخوانی متد Override شده‌ی والدParent.prototype.method.call(this)super.method()
اصلاح دستی constructorلازم استخودکار

جمع‌بندی

Inheritance به یک الگو (Constructor Function یا Class) اجازه می‌دهد Property ها و متدهای الگوی دیگری را به ارث ببرد. در روش سنتی، این کار با تنظیم دستی prototype از طریق Object.create و فراخوانی صریح Constructor والد با call انجام می‌شود. در روش مدرن، کلیدواژه‌ی extends این زنجیره‌سازی را خودکار می‌کند و super جایگزین فراخوانی صریح Constructor یا متد والد می‌شود. هر دو روش، در نهایت به همان مکانیزم زیرین Prototype Chain متکی هستند.