من میدانم که هیچ نمیدانم.
بررسی جامع کلاسها (Class) در جاوااسکریپت
با کلاسها میتونیم آبجکتهایی داشته باشیم که همگی شامل یکسری پراپرتی و متدهای مشترک هستن
درود دوستان. کلاسها (Classes) از ES6 به جاوااسکریپت اومدن تا جایگزینی برای توابع Constructor باشن که یک روش سنتی برای ساختن آبجکتها از یک طرح و الگوی اولیه به حساب میومد. این ویژگی کاربرد زیادی توی فریمورکها و کتابخونههایی مثل ریاکت و انگولار داره.
موارد زیر رو توی این قسمت یاد میگیریم:
- مشکل کجاست
- کلاس چیه
- ساختن آبجکت از کلاسها
- تعریف کردن متدها
- متد constrcutor
- تعریف کردن پراپرتیها
- دسترسی به فیلدها از متدها
- کلاسهای والد، کلاسهای فرزند
- دسترسی به اعضای کلاس والد
- فرزند و زیرفرزند
- بررسی کردن نوع آبجکت
مشکل کجاست؟
فرض کنیم آبجکتی داریم به صورت زیر:
const emily = {
name: "Emily",
age: 4,
run() {
alert(`${this.name} is running`);
}
}
همونطور که میبینیم ۲ پراپرتی داریم و یک متد. حالا میخوایم چند آبجکت دیگه مشابه همین آبجکت داشته باشیم که فقط توی مقدار پراپرتیها با هم تفاوت دارن. شاید راهی که به ذهنمون برسه این باشه که هر آبجکتی رو دقیقاً مثل همون آبجکت بالا و به صورت دستی بسازیم:
const mario = {
name: "Mario",
age: 5,
run() {
alert(`${this.name} is running`);
}
}
const john = {
name: "John",
age: 3,
run() {
alert(`${this.name} is running`);
}
}
// ...
این کار چند مشکل داره:
۱. داریم کدهای تکراری مینویسیم. متد run
بدون تغییر توی همه آبجکتها تکرار شده. کار زمانی سختتر میشه که تعداد پراپرتیها و متدهای ما زیاد باشن.
۲. برای اضافه کردن یک پراپرتی به آبجکت، باید سراغ همه آبجکتها بریم و به صورت دستی پراپرتیها رو اضافه کنیم.
کلاسها برای حل چنین مشکلهایی به کارمون میان ?
کلاس چیه؟
کلاس، یک طرح و الگو برای ساختن آبجکتهاست. توی دنیای واقعی، واحدهای یک ساختمون همگی ساختار منظم و یکشکل دارن. به این دلیل که از روی یک طرح و نقشه اولیه ساخته میشن. توی برنامهنویسی، به این طرح و نقشهٔ اولیه میگیم کلاس. با کلاسها حجم کدهای ما پایین میاد و سرعت کدنویسی بالا میره.
توی جاوااسکریپت، یک کلاس با استفاده از کلمهکلیدی class
ساخته میشه:
class User {
}
توی این کد، User
اسم کلاس ما هست و هر چیزی که داخل براکتها بنویسیم، بدنهٔ این کلاس به حساب میاد. توی این بدنه ما پراپرتیها و متدهایی رو تعریف میکنیم تا توی آبجکتها در دسترس باشن!
ساختن آبجکت از کلاسها
ما با استفاده از کلمهکلیدی new
که درست قبل از اسم کلاس قرار میگیره میتونیم آبجکت بسازیم:
class User { }
const emily = new User();
const mario = new User();
به آبجکتهایی که از یک کلاس ساخته میشه به اصطلاح میگیم نمونه یا Instance. الان متغیرهای emily
و mario
نمونههایی هستن که هر دو از کلاس User
ساخته شدن و دارای ویژگیهایی مشترکی هستن. اما هنوز هیچ ویژگی (پراپرتی و متد) تعریف نکردیم که توی این نمونهها در دسترس باشن.
تعریف کردن متدها
متدها رو میتونیم به صورت زیر توی کلاس تعریف کنیم تا توی همه نمونهها در دسترس باشن:
class User {
welcome() {
alert("Hello!");
}
login() {
alert("I'm logging in");
}
}
const emily = new User();
const mario = new User();
emily.welcome(); // Hello!
mario.login(); // I'm logging in
همونطور که توی دو خط آخر میبینیم، متدهایی که توی کلاس تعریف کردیم توی نمونهها در دسترس و قابل استفاده هستن.
متد constrcutor
همه کلاسها میتونن یک متد اختصاصی داشته باشن به اسم constructor
که به صورت زیر تعریف میشه:
class User {
constructor() {
// ...
}
}
این متد کاربرد جالبی داره: وقتی با کلمهکلیدی new
میخوایم نمونه بسازیم، این متد به طور خودکار اجرا میشه:
class User {
constructor() {
alert("Bravo");
}
}
const mario = new User(); // Bravo
همونطور که میبینیم، برعکس متدهای دیگه لازم نیست این متد رو صدا بزنیم و خود به خود صدا زده شد. استفاده از اون برای زمانی خوبه که میخوایم نمونهها رو موقع ساختهشدن شخصیسازی کنیم. همونطور که از اسم اون بر میاد: Constructor یعنی سازنده.
شخصیسازی یک نمونه یعنی اینکه کاری کنیم پراپرتیهایی که توی نمونهها وجود دارن بتونن مقدارهای متفاوتی از بقیه نمونهها داشته باشن. مثلاً همه نمونهها یک پراپرتی داشته باشن به اسم name
اما مقدار اون توی هر آبجکت فرق کنه. توی ادامه یاد میگیریم که چطوری نمونهها رو شخصیسازی کنیم.
تعریف کردن پراپرتیها
برای اینکه بخوایم پراپرتیهایی داشته باشیم که توی همه نمونهها در دسترس باشن از متد constructor
و this
کمک میگیریم:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const mario = new User("Mario", 5);
const emily = new User("Emily", 4);
alert(mario.name); // Mario
alert(mario.age); // 5
alert(emily.name); // Emily
توی خط ۲ برای constructor
دو پارامتر تعریف کردیم و که اونها رو موقع ساختن نمونه (خطهای ۸ و ۹) باید پاس بدیم. توی constructor
با استفاده از کلمهکلیدی this
تونستیم ۲ پراپرتی بسازیم و مقدار اونها رو برابر با ورودیهای این متد قرار بدیم. از حالا نمونههایی که ساخته میشن شامل این ۲ پراپرتی با مقدارهای اختصاصی خواهند بود و مثل خطهای آخر قابل دسترس هستن ?
پراپرتی؟ فیلد؟ ?
شاید واژه فیلد (Field) هم به گوشتون خورده باشه که با واژه پراپرتی بجای هم مورد استفاده قرار میگیرن. توی کد بالا و توی خط ۳ و ۴ ما در واقع فیلد تعریف کردیم. این فیلد با مقدارش زمانی که توی نمونهها قرار میگیرن، با عنوان پراپرتی شناخته میشن. پس پراپرتی چیزی هست که توی نمونهها و فیلد چیزی هست که توی کلاسها داریم. توی خطهای آخر کد بالا داریم پراپرتیها رو فراخونی میکنیم.
دسترسی به فیلدها توی متدها
با استفاده از کلمهکلیدی this
میتونیم به فیلدها دسترسی داشته باشیم:
class User {
constructor(name) {
this.name = name;
}
welcome() {
return `Welcome ${this.name}!`;
}
}
const mario = new User("Mario");
alert(mario.welcome()); // Welcome Mario!
کلاسهای والد؟ کلاسهای فرزند؟ ?
یک کلاس میتونه ویژگیهای یک کلاس دیگه رو به ارث ببره. این کار که به اصطلاح به اون گفته میشه ارثبری، برای زمانی خوبه که میخوایم یک کلاس موجود رو توسعه (Extend) بدیم.
ابتدا فرض کنیم کلاسی داریم به صورت زیر:
class User {
login() { }
logout() { }
}
توی این کلاس که برای کاربرها هست ۲ تا متد قرار دادیم. حالا میخوایم برای کاربرها قابلیتی رو اضافه کنیم که بتونن پست ارسال کنن:
class User {
login() { }
logout() { }
writePost() { }
deletePost(id) { }
}
دو متد writePost
و deletePost
رو اضافه کردیم. اما قابلیت ارسال پست رو قصد نداریم در اختیار همه کاربرها قرار بدیم. چون کاربرها ممکنه نقشهای متفاوتی مثل نقشها مدیرتی داشته باشن که در این صورت این دو متد اضافه شده برای اونها بیمعنی هست. همچنین اگه بخوایم برای نقشهای مدیریتی متدهایی رو اضافه کنیم، این متدها برای کاربرهای نویسنده بیمعنی هست.
یک راه بهتر اینه که برای کاربرهای نویسنده یا مدیر یک کلاس اختصاصی برای همون نقش درست کنیم و قابلیتهای اختصاصی رو توی اون کلاس جدا تعریف کنیم. برای این کار، ارثبری به کار ما میاد.
برای اینکه یک کلاس رو توسعه بدیم، یک کلاس جدید میسازیم و از کلمهکلیدی extends
به صورت زیر استفاده میکنیم:
class User { }
class WriterUser extends User {
}
توی این مثال، کلاس User
به عنوان کلاس والد (Parent) و کلاس WriterUser
به عنوان کلاس فرزند (Child) در نظر شناخته میشه. با این کار، کلاس فرزند همه ویژگیهای کلاس والد رو در اختیار داره:
class User {
login() {
alert("Logging in...");
}
}
class WriterUser extends User {
}
const david = new WriterUser();
david.login(); // Logging in...
حالا میتونیم کلاس WriterUser
رو به صورت اختصاصی توسعه بدیم:
class User {
login() { }
logout() { }
}
class WriterUser extends User {
writePost() { }
deletePost(id) { }
}
const david = new WriterUser();
david.login();
david.writePost();
david.logout();
با این کار کلاس User
از ویژگیهایی که برای اون بیمعنی هست خالی میشه و برنامهٔ ما معنادارتر و توسعهٔ اون راحتتر میشه ?
نکته: توی جاوااسکریپت یک کلاس حداکثر میتونه یک کلاس والد داشته باشه. همچنین کلاس والد نمیتونه به اعضای کلاس فرزند دسترسی داشته باشه.
دسترسی به اعضای کلاس والد
با استفاده از کلمهکلیدی super
توی یک کلاس میتونیم به اعضای کلاس والد دسترسی داشته باشیم:
class Parent {
parentMethod() {
alert("I'm a method in the parent");
}
}
class Child extends Parent {
childMethod() {
super.parentMethod();
}
}
const obj = new Child();
obj.childMethod(); // I'm a method in the parent
اگه هم کلاس والد و هم کلاس فرزند constructor
دارن، کلاس فرزند توی constructor
خودش، باید constructor
والد رو صدا بزنه. این کار با صدا زدن تابعی به اسم ()super
اتفاق میوفته:
class Parent {
constructor() {
alert("I'm a method in the parent");
}
}
class Child extends Parent {
constructor() {
super();
}
}
const obj = new Child(); // I'm a method in the parent
به این صورت این تابع میتونه یک ورودی رو به constructor
والد پاس بده:
class User {
constructor(name) {
this.name = name;
}
}
class WriterUser extends User {
constructor(name) {
super(name);
}
writePost() { }
deletePost(id) { }
}
const david = new WriterUser("David");
alert(david.name); // David
اگه super
رو صدا نزنیم خطا میگیریم. توی بعضی از زبانهای برنامهنویسی ممکنه خطا نگیریم، اما این کار باعث میشه constructor
والد اجرا نشه.
اما کدی که نوشتیم یک کار اضافی هست. چون توی constructor
فرزند هیچ کار دیگهای غیر از صدا زدن constructor
والد انجام ندادیم. توی چنین شرایطی بهتره از نوشتن constructor
فرزند صرف نظر کنیم:
class User {
constructor(name) {
this.name = name;
}
}
class WriterUser extends User {
writePost() { }
deletePost(id) { }
}
const david = new WriterUser("David");
alert(david.name); // David
با ساخته شدن نمونه از کلاس فرزند، constructor
والد به طور خودکار اجرا میشه.
فرزند و زیرفرزند
یک کلاس که نقش فرزند رو بازی میکنه، میتونه نقش والد برای یک کلاس دیگه رو هم ایفا کنه:
class A1 {
constructor() {
alert("I'm A1");
}
}
class A2 extends A1 {
constructor() {
super();
alert("I'm A2");
}
}
class A3 extends A2 {
constructor() {
super();
alert("I'm A3");
}
}
new A3;
بررسی کردن نوع آبجکت
برای اینکه ببینیم یک آبجکت از نوع یک کلاس خاص هست یا نه، از عملگر instanceof
استفاده میکنیم. این عملگر به ما true
یا false
برمیگردونه:
class User { }
const emily = new User();
alert(emily instanceof User); // true
alert(emily instanceof Array); // false
خب دوستان با کلاسها و ویژگیهای اونها توی ES6 جاوااسکریپت آشنا شدیم و دیدیم که با اونها ساختن آبجکتهایی که ویژگیهای مشابه دارن چقدر راحت میشه!
Resources:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
https://dmitripavlutin.com/javascript-classes-complete-guide
مطلبی دیگر از این انتشارات
JulSwap چیست و چگونه کار میکند + ارز JULD
مطلبی دیگر از این انتشارات
هفت کاربرد بلاکچین در صنعت لجستیک
مطلبی دیگر از این انتشارات
همه چیز را درباره ارز انجین کوین بدانید!