وراثت بین الگوها: Inheritance مبتنی بر Prototype و مبتنی بر Class
مقدمه
تا اینجا یاد گرفتیم چگونه یک الگوی واحد برای ساخت شیءهای مشابه بسازیم؛ چه با Constructor Function و prototype، چه با Class. اما گاهی چند الگو با یکدیگر رابطهی سلسلهمراتبی دارند؛ برای نمونه، یک Admin در واقع نوعی User است، اما با چند قابلیت اضافی. در این درس بررسی میکنیم چگونه یک الگو میتواند از الگوی دیگری وراثت (Inheritance) ببرد؛ هم به روش سنتی مبتنی بر Prototype، و هم به روش مدرن مبتنی بر Class.
Inheritance مبتنی بر Prototype (روش سنتی)
برای اینکه یک Constructor Function، از Constructor Function دیگری وراثت ببرد، باید دو کار انجام دهیم: اول، Prototype تابع فرزند را طوری تنظیم کنیم که به Prototype تابع والد زنجیر شود؛ دوم، Constructor والد را از داخل Constructor فرزند فراخوانی کنیم تا Property های اختصاصی هم بهدرستی مقداردهی شوند.
تحلیل کد
سه بخش کلیدی این کد را جداگانه بررسی میکنیم:
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اشاره میکرد) را کاملاً با یک شیء جدید جایگزین کرد، این خط، Propertyconstructorرا که در درس قبل دیدیم، بهدرستی رویAdminبازمیگرداند.
به همین دلیل، admin.greet() کار میکند: جاوااسکریپت greet را روی خودِ admin پیدا نمیکند، سپس روی Admin.prototype هم پیدا نمیکند، اما با ادامهی Prototype Chain به User.prototype میرسد و آنجا greet را پیدا میکند.
Inheritance مبتنی بر Class (روش مدرن)
حالا دقیقاً همین رابطهی وراثتی را با Class بازنویسی میکنیم:
تحلیل رفتار جاوااسکریپت
کلیدواژهی 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) دقیقاً همین امکان را میدهد:
تحلیل کد
در این مثال، متد 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 متکی هستند.
