نحوه‌ی نوین تعریف الگوی شیء در جاوااسکریپت: Class

مقدمه

در دو درس قبل، دیدیم که چگونه با ترکیب Constructor Function و prototype، می‌توان الگوهای قابل‌استفاده‌ی مجدد برای ساخت شیء طراحی کرد. ES6 یک نحوه‌ی نگارش جدید و خواناتر برای همین الگو معرفی کرد که به آن Class می‌گویند. نکته‌ی مهم این است که Class یک مکانیزم کاملاً جدید نیست؛ بلکه لایه‌ای خواناتر (Syntactic Sugar) روی همان مکانیزم Prototype است که در دو درس قبل دیدیم.

Syntax پایه‌ی Class

همان الگوی User که در درس قبل با Constructor Function نوشتیم را این‌بار با Class بازنویسی می‌کنیم:

تعریف یک Class ساده
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

در این ساختار، متد ویژه‌ای به نام constructor نقش همان بدنه‌ی تابع سازنده‌ای را دارد که در درس قبل با function User(name, age) { ... } می‌نوشتیم؛ هر بار که new User(...) فراخوانی شود، این متد به‌طور خودکار اجرا می‌شود. متد greet، بدون هیچ کار اضافه‌ای از سوی ما، به‌طور خودکار روی User.prototype قرار می‌گیرد؛ دقیقاً همان کاری که در درس قبل باید صراحتاً با User.prototype.greet = function () {...} انجام می‌دادیم.

اثبات این‌که Class همان Prototype است

می‌توانیم مستقیماً بررسی کنیم که Class در پس‌زمینه، دقیقاً همان مکانیزم Prototype را به کار می‌گیرد:

console.log(typeof User); // "function" — یک Class در واقع نوعی تابع است
console.log(user1.greet === user2.greet); // true — متد مشترک، یک نسخه در حافظه
console.log(Object.getPrototypeOf(user1) === User.prototype); // true

همان‌طور که در خط اول می‌بینید، typeof User برابر "function" است؛ این نشان می‌دهد که Class در جاوااسکریپت، در سطح زیرین، خودش نوعی تابع خاص است. نتیجه‌ی خط دوم دقیقاً همان چیزی است که در درس قبل، هنگام قرار دادن متد روی prototype، دیدیم: تمام Instance ها در یک نسخه‌ی مشترک از متد شریک هستند.

چند تفاوت مهم Class با Constructor Function سنتی

با اینکه Class در نهایت به همان مکانیزم Prototype ترجمه می‌شود، چند تفاوت رفتاری مهم با Constructor Function سنتی دارد:

۱. Class بدون new قابل فراخوانی نیست

در درس قبل دیدیم که فراموش کردن new هنگام فراخوانی یک Constructor Function سنتی، به یک باگ خاموش (بدون خطا) منجر می‌شد. اما Class این اشتباه را صریحاً با خطا گزارش می‌کند:

فراخوانی Class بدون new
خروجی کنسول اینجا نمایش داده می‌شود...

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

این کد با خطای TypeError: Class constructor User cannot be invoked without 'new' متوقف می‌شود. این یکی از دلایل اصلی است که Class امروزه ایمن‌تر از Constructor Function سنتی محسوب می‌شود؛ اشتباه فراموش کردن new، که در درس قبل به‌سادگی رخ می‌داد و مشکلی خاموش تولید می‌کرد، اینجا بلافاصله و به‌وضوح گزارش می‌شود.

۲. بدنه‌ی Class همیشه در Strict Mode اجرا می‌شود

برخلاف کد معمولی که به‌طور پیش‌فرض در حالت غیر-Strict اجرا می‌شود (مگر اینکه صراحتاً "use strict" نوشته شود)، تمام کدی که داخل بدنه‌ی یک Class نوشته می‌شود، به‌طور خودکار در Strict Mode اجرا می‌شود؛ دقیقاً همان رفتاری که در فصل this و Binding، هنگام بررسی Default Binding، درباره‌ی تفاوت Strict Mode و حالت عادی دیدیم.

۳. متدهای Class به‌طور پیش‌فرض Enumerable نیستند

همان‌طور که در فصل حلقه‌ها، هنگام بررسی for...in دیدیم، این حلقه تمام Property های قابل شمارش (Enumerable) را پیمایش می‌کند؛ از جمله آن‌هایی که از Prototype Chain به ارث رسیده‌اند. اما متدهایی که داخل یک Class تعریف می‌شوند، به‌طور پیش‌فرض Non-Enumerable هستند؛ به همین دلیل، برخلاف متدی که با User.prototype.greet = ... اضافه شود، متد greet داخل Class هرگز توسط for...in پیمایش نمی‌شود. جزئیات کامل مفهوم Enumerable را در فصل Object Model پیشرفته، هنگام بررسی Property Descriptors، خواهیم دید.

Getter و Setter داخل Class

Class امکان تعریف Getter و Setter را نیز به‌شکلی خوانا فراهم می‌کند:

استفاده از Getter و Setter در Class
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

در این مثال، area یک Getter است؛ با اینکه مثل یک Property معمولی (بدون پرانتز) خوانده می‌شود (circle.area، نه circle.area())، در واقع هر بار که خوانده شود، محاسبه‌ی Math.PI * radius * radius دوباره اجرا می‌شود. diameter نیز یک Setter است؛ وقتی چیزی به circle.diameter نسبت داده شود، به‌جای ذخیره‌ی مستقیم آن، تابع Setter اجرا می‌شود و radius را بر اساس آن محاسبه می‌کند. جزئیات کامل Getter و Setter را در فصل Object Model پیشرفته با جزئیات بیشتری بررسی خواهیم کرد.

Method Shorthand در Class در برابر Object Literal

دقت کنید که Syntax متدهای Class -بدون کلیدواژه‌ی function و بدون کاما بین متدها- شباهت زیادی به Method Shorthand در Object Literal دارد که در فصل شیء دیدیم؛ اما این دو ساختار کاملاً متفاوت هستند: یک Object Literal، مستقیماً یک شیء واحد می‌سازد، درحالی‌که یک Class، یک الگو (Blueprint) برای ساخت چندین Instance با new فراهم می‌کند.

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

ویژگیConstructor Function سنتیClass
تعریف Property اختصاصیthis در بدنه‌ی تابعthis در متد constructor
تعریف متد مشترکدستی روی ConstructorName.prototypeمستقیماً داخل بدنه‌ی class
فراخوانی بدون newاجرا می‌شود (باگ خاموش)خطای صریح TypeError
حالت اجرابر اساس محیط (Strict یا نه)همیشه Strict Mode
Enumerable بودن متدهاEnumerable (قابل پیمایش با for...in)Non-Enumerable

جمع‌بندی

Class یک نحوه‌ی نگارش خواناتر برای همان مکانیزم Constructor Function و Prototype است که در دو درس قبل دیدیم؛ متد constructor جایگزین بدنه‌ی تابع سازنده می‌شود و متدهای دیگر، به‌طور خودکار روی prototype قرار می‌گیرند. با این حال، Class چند تفاوت رفتاری مهم دارد: بدون new قابل فراخوانی نیست (برخلاف تابع سنتی)، همیشه در Strict Mode اجرا می‌شود، و متدهای آن به‌طور پیش‌فرض Non-Enumerable هستند. Class همچنین امکان تعریف خوانای Getter و Setter را نیز فراهم می‌کند.