مقایسهی نهایی: Class در برابر Prototype-based Inheritance
مقدمه
در طول این فصل، از Prototype خام شروع کردیم، به Constructor Function رسیدیم، سپس Class را بهعنوان نحوهی نگارش مدرنتر آن دیدیم، و در نهایت Inheritance، Private Fields و Static Members را در هر دو سبک بررسی کردیم. در این درس پایانی فصل، تمام این مسیر را جمعبندی میکنیم و بهطور مستقیم و کنار هم، تفاوتهای واقعی Class با Prototype-based Inheritance سنتی را روشن میسازیم.
یادآوری: Class روی چه چیزی بنا شده است؟
نکتهای که در سراسر این فصل تأکید کردیم این بود: Class یک مکانیزم مستقل و جدید در جاوااسکریپت نیست؛ بلکه لایهای خواناتر (Syntactic Sugar) روی همان Prototype Chain است که از ابتدای فصل با آن کار کردیم. برای اثبات نهایی این موضوع:
class User {
constructor(name) {
this.name = name;
}
}
console.log(typeof User); // "function"
console.log(User.prototype.constructor === User); // true
همانطور که میبینید، حتی یک Class، در سطح زیرین، همچنان یک تابع است که prototype دارد؛ دقیقاً همان ساختاری که در درس دوم همین فصل با Constructor Function سنتی دیدیم.
مقایسهی کنار هم: همان مثال، دو سبک
برای جمعبندی نهایی، بیایید یک بار دیگر، اینبار بهطور فشرده، یک الگوی وراثتی کامل را در هر دو سبک کنار هم ببینیم:
تحلیل کد
هر دو dogOld و dogNew رفتار کاملاً یکسانی دارند: هر دو speak را از طریق Prototype Chain به ارث بردهاند و هر دو instanceof والد خودشان هستند. تفاوت واقعی این دو نسخه، صرفاً در میزان کد لازم و وضوح آن است؛ نسخهی DogOld به سه خط دستکاری صریح prototype نیاز داشت، درحالیکه DogNew با یک خط extends همان نتیجه را گرفت.
جدول جامع تفاوتها
| ویژگی | Prototype-based (سنتی) | Class (مدرن) |
|---|---|---|
| تعریف الگو | تابع معمولی + دستکاری prototype | کلیدواژهی class |
| تعریف متد مشترک | ConstructorName.prototype.method = ... | مستقیم داخل بدنهی class |
| وراثت | Object.create + دستکاری دستی constructor | extends |
| فراخوانی constructor والد | Parent.call(this, ...) | super(...) |
| فراخوانی متد Override شدهی والد | Parent.prototype.method.call(this) | super.method() |
| فراخوانی بدون new | اجرا میشود (باگ خاموش) | TypeError صریح |
| حالت اجرا | وابسته به محیط | همیشه Strict Mode |
| Private State | فقط با ترفند Closure (Module Pattern) | پشتیبانی رسمی با # |
| Static Members | مستقیم روی خودِ تابع (دستی) | کلیدواژهی static |
| مکانیزم زیرین | Prototype Chain | همان Prototype Chain |
پس چرا اصلاً Prototype خام را یاد گرفتیم؟
ممکن است این سؤال مطرح شود که اگر Class همیشه بهتر و ایمنتر است، چرا این فصل را با Prototype خام شروع کردیم؟ دلیل این است که Class صرفاً یک نما (Syntax) روی همان مکانیزم است؛ بدون فهم Prototype Chain، بسیاری از رفتارهای Class -مثل اینکه چرا متدها بین تمام Instance ها مشترک هستند، یا اینکه instanceof واقعاً چه چیزی را بررسی میکند- غیرقابلدرک باقی میمانند. کدهای قدیمیتر جاوااسکریپت (و بسیاری از کتابخانههای موجود) نیز همچنان از سبک سنتی استفاده میکنند؛ توانایی خواندن و فهم آنها، بدون این پیشزمینه ممکن نیست.
کدام سبک باید استفاده شود؟
برای نوشتن کد جدید، Class تقریباً همیشه انتخاب بهتری است؛ به دلایلی که در طول این فصل دیدیم: نحو خواناتر، فراخوانی ایمنتر (خطا در صورت فراموش کردن new)، پشتیبانی رسمی از Private Fields، و extends/super برای وراثت بهجای دستکاری دستی prototype. دانستن سبک سنتی، عمدتاً برای خواندن و نگهداری کدهای قدیمیتر، و برای درک عمیقتر اینکه در پسزمینهی Class واقعاً چه اتفاقی میافتد، ارزشمند است.
جمعبندی
Class و Prototype-based Inheritance، از نظر مکانیزم زیرین، دقیقاً یک چیز هستند: هر دو بر پایهی Prototype Chain عمل میکنند. تفاوت اصلی آنها در نحو (Syntax) و چند رفتار ایمنی است: Class خواناتر است، بدون new با خطای صریح متوقف میشود، همیشه در Strict Mode اجرا میشود، و امکانات رسمی مثل Private Fields (#) و extends/super را در اختیار میگذارد که در سبک سنتی، باید بهصورت دستی و با کد بیشتر پیادهسازی میشدند.
