مقایسه‌ی نهایی: 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 + دستکاری دستی constructorextends
فراخوانی 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 را در اختیار می‌گذارد که در سبک سنتی، باید به‌صورت دستی و با کد بیشتر پیاده‌سازی می‌شدند.