در برنامهنویسی شیءگرا، کلاس یک الگوی-کد-برنامه قابل توسعه برای ایجاد اشیاء، ارائه مقادیر اولیه برای حالت (متغیرهای عضو) و پیادهسازی رفتار (توابع یا روشهای عضو) است.
در عمل، ما اغلب نیاز به ایجاد بسیاری از اشیاء از یک نوع، مانند کاربران، یا کالاها یا هر چیز دیگری داریم.
همانطور که قبلاً از فصل سازنده، عملگر "new" میدانیم، new function
میتواند در این مورد کمک کند.
اما در جاوااسکریپت مدرن، ساختار «کلاس» پیشرفتهتری وجود دارد که ویژگیهای جدید بسیار خوبی را معرفی میکند که برای برنامهنویسی شیءگرا مفید هستند.
سینتکس «کلاس»
سینتکس پایه اینگونه است:
class MyClass {
// متدهای کلاس
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
سپس از new MyClass()
برای ایجاد یک شیء جدید با تمام متدهای فهرست شده استفاده کنید.
متد constructor()
به طور خودکار توسط new
فراخوانی میشود، بنابراین میتوانیم شیء را در آنجا مقداردهی اولیه کنیم.
برای مثال:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Usage:
let user = new User("John");
user.sayHi();
وقتی new User("John")
فراخوانی میشود:
- یک شیء جدید ایجاد میشود.
constructor
با آرگومان داده شده اجرا میشود و آن را بهthis.name
اختصاص میدهد.
…سپس ما میتوانیم متدهای شیء را فراخوانی کنیم، مانند user.sayHi()
.
یک مشکل رایج برای توسعه دهندگان تازه کار این است که یک کاما بین متدهای کلاس قرار میدهند که منجر به یک خطای سینتکس میشود.
نماد در اینجا نباید با شیء لفظی اشتباه شود. در کلاس، نیازی به کاما نیست.
یک کلاس چیست؟
بنابراین، class
دقیقاً چیست؟ این یک موجودیت کاملاً جدید در سطح زبان نیست، همانطور که ممکن است تصور شود.
بیایید از هر جادویی رونمایی کنیم و ببینیم کلاس واقعاً چیست. این به درک بسیاری از جنبههای پیچیده کمک میکند.
در جاوااسکریپت کلاس نوعی تابع است.
در اینجا، نگاهی بیندازیم:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// اثبات: کاربر یک تابع است
alert(typeof User); // تابع
کاری که ساختار class User {...}
واقعا انجام میدهد این است:
- تابعی به نام
User
ایجاد میکند که نتیجه تعریف کلاس میشود. کد تابع از متد سازنده گرفته شده است (اگر چنین روشی را ننویسیم خالی فرض میشود). - متدهای کلاس، مانند
sayHi
را درUser.prototype
ذخیره میکند.
پس از ایجاد شیء new User
، وقتی متد آن را فراخوانی میکنیم، از نمونه اولیه گرفته میشود، درست همانطور که در فصل F.prototype توضیح داده شد. بنابراین شیء به متدهای کلاس دسترسی دارد.
ما میتوانیم نتیجه تعریف class User
را به صورت زیر نشان دهیم:
این کد برای درونیابی آن است:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// کلاس یک تابع است
alert(typeof User); // تابع
// ... یا به طور دقیق تر، متد سازنده
alert(User === User.prototype.constructor); // true
// :هستند، به عنوان مثال User.prototype متدها در
alert(User.prototype.sayHi); // sayHi کد متد
// دقیقا دو متد در پروتوتایپ وجود دارد
alert(Object.getOwnPropertyNames(User.prototype)); // sayHi ،سازنده
فقط یک سینتکس برای زیباکردن(syntactic sugar) نیست
گاهی اوقات مردم میگویند که class
یک «سینتکس برای زیباکردن(syntactic sugar)» است (سینتکسی که برای خواندن سادهتر طراحی شده است، اما چیز جدیدی معرفی نمیکند)، زیرا در واقع میتوانیم همان را بدون کلمه کلیدی class
تعریف کنیم:
// بازنویسی کلاس کاربر در توابع خالص
// 1. ایجاد تابع سازنده
function User(name) {
this.name = name;
}
// یک پروتوتایپ تابع به طور پیشفرض دارای ویژگی "سازنده" است،
// پس ما نیاز نداریم که آن را ایجاد کنیم
// 2. اضافه کردن متد به پروتوتایپ
User.prototype.sayHi = function() {
alert(this.name);
};
// Usage:
let user = new User("John");
user.sayHi();
نتیجه این تعریف تقریباً یکسان است. بنابراین، در واقع دلایلی وجود دارد که میتوان class
را یک سینتکس برای زیباکردن(syntactic sugar) برای تعریف سازنده همراه با روشهای نمونه اولیه آن در نظر گرفت.
با این حال، تفاوتهای مهمی وجود دارد.
-
ابتدا، یک تابع ایجاد شده توسط
class
توسط یک ویژگی داخلی ویژه[[IsClassConstructor]]: true
برچسب گذاری میشود:. بنابراین کاملاً مشابه ایجاد آن به صورت دستی نیست.زبان آن ویژگی را در مکانهای مختلف بررسی میکند. به عنوان مثال، بر خلاف یک تابع معمولی، باید با
new
فراخوانی شود:class User { constructor() {} } alert(typeof User); // function User(); // خطا: سازنده کلاس کاربر را نمیتوان بدون 'new' فراخوانی کرد
همچنین، نمایش رشتهای از سازنده کلاس در اکثر موتورهای جاوااسکریپت با “کلاس…” شروع میشود.
class User { constructor() {} } alert(User); // class User { ... }
تفاوتهای دیگری نیز وجود دارد، به زودی آنها را خواهیم دید.
-
روشهای کلاس غیرقابل شمارش هستند. یک تعریف کلاس، پرچم
enumerable
را برای همه متدهای"prototype"
رویfalse
تنظیم میکند.این خوب است، زیرا اگر
for..in
را روی یک شیء قرار دهیم، معمولاً متدهای کلاس آن را نمیخواهیم. -
کلاسها همیشه از
use strict
استفاده میکنند. تمام کدهای داخل ساختار کلاس به طور خودکار در حالت سخت قرار میگیرند.
علاوه بر این، سینتکس class
بسیاری از ویژگیهای دیگر را به همراه دارد که بعداً بررسی خواهیم کرد.
بیان کلاس
درست مانند توابع، کلاسها را میتوان در یک عبارت دیگر تعریف کرد، ارسال کرد، برگرداند، تخصیص داد و غیره.
در اینجا نمونهای از عبارت کلاس آورده شده است:
let User = class {
sayHi() {
alert("سلام");
}
};
شبیه به عبارتهای تابع نامگذاری شده، عبارات کلاس ممکن است یک نام داشته باشند.
اگر یک عبارت کلاس دارای نام باشد، فقط در داخل کلاس قابل مشاهده است:
// «عبارت کلاس نامگذاری شده»
// (چنین اصطلاحی در مشخصات وجود ندارد، اما شبیه به عبارت تابع نامگذاری شده است)
let User = class MyClass {
sayHi() {
alert(MyClass); // فقط در داخل کلاس قابل مشاهده است MyClass نام
}
};
new User().sayHi(); // را نشان میدهد MyClass کار می کند، تعریف
alert(MyClass); // خارج از کلاس قابل مشاهده نیست MyClass خطا، نام
ما حتی میتوانیم کلاسها را به صورت پویا “بر اساس تقاضا” بسازیم، مانند این:
function makeClass(phrase) {
// یک کلاس تعریف کنید و آن را برگردانید
return class {
sayHi() {
alert(phrase);
}
};
}
// Create a new class
let User = makeClass("سلام");
new User().sayHi(); // سلام
گیرنده/ تنظیم کننده
درست مانند اشیاء تحت اللفظی، کلاسها ممکن است شامل گیرندهها/تنظیم کنندهها، خصوصیات محاسبه شده و غیره باشند.
در اینجا یک مثال برای user.name
است که با استفاده از get/set
پیادهسازی شده است:
class User {
constructor(name) {
// تنظیم کننده را فراخوانی میکند
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("نام بسیار کوتاه است.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // .نام بسیار کوتاه است
از نظر فنی، چنین فراخوانی کلاس با ایجاد گیرنده و تنظیم کننده در User.prototype
کار میکند.
نامهای محاسبه شده […]
در اینجا یک مثال با نام متد محاسبه شده با استفاده از براکت [...]
آورده شده است:
class User {
['say' + 'Hi']() {
alert("سلام");
}
}
new User().sayHi();
به خاطر سپردن چنین ویژگیهایی آسان است، زیرا آنها شبیه به اشیاء تحت اللفظی هستند.
فیلدهای کلاس
فیلدهای کلاس اخیرا به زبان اضافه شده هستند.
قبلا، کلاسهای ما فقط متد داشتند.
“فیلدهای کلاس” سینتکسی است که اجازه میدهد هر ویژگی را اضافه کنید.
برای مثال، اجازه دهید ویژگی name
را به class User
اضافه کنیم:
class User {
name = "John";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
بنابراین، ما فقط “
تفاوت مهم فیلدهای کلاس در این است که آنها بر روی اشیاء جداگانه تنظیم میشوند، نه User.prototype
:
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
همچنین میتوانیم با استفاده از عبارات و فراخوانیهای توابع پیچیدهتر، مقادیری را اختصاص دهیم:
class User {
name = prompt("نام، لطفا؟", "John");
}
let user = new User();
alert(user.name); // John
ساخت متدهای محدود با فیلدهای کلاس
همانطور که در فصل نشان داده شد توابع پیوند تابع در جاوااسکریپت دارای this
پویا هستند. بستگی به زمینه فراخوانی دارد.
بنابراین، اگر یک متد شیء منتقل شود و در زمینه دیگری فراخوانی شود، this
دیگر ارجاعی به شیء آن نخواهد بود.
به عنوان مثال، این کد undefined
را نشان میدهد:
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("سلام");
setTimeout(button.click, 1000); // undefined
مشکل “از دست دادن this
” نامیده میشود.
همانطور که در فصل پیوند تابع مورد بحث قرار گرفت، دو روش برای رفع آن وجود دارد:
- یک تابع دربرگیرنده مانند
setTimeout(() => button.click(), 1000)
را عبور دهید. - متد را به شیء متصل کنید، به عنوان مثال. در سازنده
فیلدهای کلاس، سینتکس بسیار ظریف دیگری را ارائه میدهند:
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("سلام");
setTimeout(button.click, 1000); // سلام
فیلد کلاس click = () => {...}
بر اساس هر شیء ایجاد میشود، یک تابع جداگانه برای هر شیء Button
وجود دارد که this
در داخل آن به آن شیء ارجاع میدهد. ما میتوانیم button.click
را به هرجایی منتقل کنیم، و مقدار this
همیشه درست خواهد بود.
این به ویژه در محیط مرورگر، برای شنوندگان رویداد مفید است.
خلاصه
سینتکس کلاس پایه به شکل زیر است:
class MyClass {
prop = value; // ویژگی
constructor(...) { // سازنده
// ...
}
method(...) {} // متد
get something(...) {} // متد گیرنده
set something(...) {} // متد تنظیم کننده
[Symbol.iterator]() {} // (Symbol در اینجا) متد با نام محاسبه شده
// ...
}
MyClass
از نظر فنی یک تابع است (توابعی که ما به عنوان constructor
ارائه میکنیم)، در حالی که متدها، دریافت کنندهها و تنظیم کنندهها در MyClass.prototype
نوشته میشوند.
در فصلهای بعدی درباره کلاسها، از جمله وراثت و سایر ویژگیها بیشتر خواهیم آموخت.