در این فصل از دوره آموزش جامع تایپ اسکریپت سری به کلاس های جاوا اسکریپت می زنیم تا با آن ها در محیط تایپ اسکریپت کار کنیم. ترکیب شیء گرایی پیش فرض جاوا اسکریپت و زبان تایپ اسکریپت ابزار قدرتمندی را برایمان فراهم می کند که یادگیری آن بسیار مهم خواهد بود. من سعی می کنم در این فصل مباحث شیء گرایی جاوا اسکریپت و تایپ اسکریپت را به صورت مرحله به مرحله توضیح بدهم تا مباحث ساده آن را نیز مرور کنیم اما اگر با مباحث شیء گرایی جاوا اسکریپت هیچ آشنایی ندارید، به شما پیشنهاد می کنم به مجموعه مقالاتی که برای مقدمه شیء گرایی جاوا اسکریپت نوشته ام مراجعه کنید.
برای شروع کار یک پروژه خالی و از قبل تنظیم شده را برای شما قرار می دهم که باید آن را دانلود کرده و در سیستم خود باز کنید (دانلود پروژه). درون این پروژه ترمینال را باز کرده و npm install
را اجرا کنید. پس از نصب پکیج های وابسته می توانید دستور npm start
را اجرا کنید تا بتوانیم کار را به صورت خودکار در مرورگر مشاهده کنیم. همچنین در یک ترمینال جدید دستور tsc -w
را اجرا کنید تا با ذخیره فایل ها، فرآیند کامپایل شدن به صورت خودکار انجام بگیرد.
حالا فایل app.ts را باز کنید تا اولین کلاس خود را بنویسیم. همانطور که می دانید ابتدا باید کلیدواژه کلاس و سپس نام کلاس مورد نظر را ذکر کنیم. فرض کنید ما بخواهیم برنامه ای بنویسیم که مسئول مدیریت کردن دپارتمان ها یا بخش های مختلف کمپانی ما باشد. بر این اساس نام اولین کلاس را department قرار می دهیم:
class Department { }
از قراردادهای کدنویسی این است که نام کلاس ها با حرف بزرگ شروع شود اما اجباری نیست. حالا برای تعریف خصوصیت یا property می توانیم نام آن را به همراه مقدارش ذکر کنیم:
class Department { name: string; }
من در اینجا هیچ مقداری برای این خصوصیت تعیین نکرده ام و تنها نوع آن را «رشته ای» قرار داده ام. در مرحله بعد می توانیم تابع یا متد خاصی به نام constructor را اضافه کنیم. Constructor از نام های رزرو شده در جاوا اسکریپت است بنابراین نمی توانید از آن برای خصوصیات یا متدهای خودتان استفاده کنید. این تابع یا متد به کلاس Department متصل می شود و هر زمانی که بخواهید یک نمونه از کلاس خود را بسازید (فرآیند ساخت instance یا instantiation) به صورت خودکار اجرا خواهد شد:
class Department { name: string; constructor(n: string) { this.name = n; } }
همانطور که می بینید در اینجا از ویژگی تعیین تایپ در تایپ اسکریپت استفاده کرده ایم تا بگوییم پارامتر ورودی constructor از نوع رشته ای است. در کد بالا constructor ما می گوید خصوصیت name باید برابر با پارامتر ورودی باشد. همچنین کلیدواژه this (به معنی «این») به شیء فعلی اشاره می کند، یعنی هر شیء ای که در حال ساختن آن باشیم (در ادامه در مورد آن صحبت می کنیم).
همانطور که گفتم متد constructor یک متد خاص است و به کلاس خودش می چسبد بنابراین اگر بخواهیم یک نمونه از کلاس بالا را بسازیم باید حتما پارامتر ورودی n را به آن پاس بدهیم. حتما می دانید که فرآیند ساختن یک نمونه از یک کلاس با کلیدواژه new است:
new Department();
در حالت عادی کد بالا کار می کرد اما constructor ما دارای پارامتر ورودی است بنابراین باید پارامتر ورودی را به کد بالا پاس بدهیم:
const accounting = new Department('Accounting');
در کد بالا accounting یک نمونه از کلاس accounting است. اگر با دستور tsc فایل را کامپایل کنیم و به محتویات فایل جاوا اسکریپت ایجاد شده نگاهی کنیم چنین چیزی می بینیم:
همانطور که می بینید در جاوا اسکریپت es6 نمی توانیم خصوصیات را به صورت تایپ اسکریپت ایجاد کنیم بلکه باید آن ها را درون constructor تعریف کنیم، آن هم با مقدار دهی مستقیم! جالب تر این است که می توانید به فایل tsconfig.json بروید و target را از روی es6 روی es5 قرار دهید، همچنین تمام قسمت lib را کامنت و غیر فعال کنید. حالا فایل app.ts را دوباره کامپایل کنید تا نتیجه را ببینید:
همانطور که می بینید کلاسی در این فایل جاوا اسکریپت و کامپایل شده وجود ندارد. چرا؟ به دلیل اینکه کلاس ها در es6 معرفی شدند و در es5 چیزی به نام constructor function داریم که در بالا می بینید. همانطور که مشاهده می کنید این کد واقعا بهم ریخته است ولی جاوا اسکریپت es5 و قدیمی تر به همین شیوه کار می کند. اینجاست که تایپ اسکریپت باز هم به کمک ما آمده است. بله! ما می توانیم کد های جاوا اسکرپیت را در همان جاوا اسکریپت و به صورت کلاس بنویسیم اما دیگر در مرورگرهای قدیمی اجرا نخواهد شد. تایپ اسکریپت به ما اجازه می دهد که از ویژگی های بسیار جدید استفاده کنیم که در عین حال در مرورگرهای قدیمی نیز کار می کنند.
با روش ساخت متد constructor آشنا شدیم اما اگر بخواهیم متدهای خودمان را بنویسیم چطور؟ ایجاد یک متد دقیقا مانند ساخت یک تابع عادی در جاوا اسکریپت است با این تفاوت که کلیدواژه Function را ذکر نمی کنیم:
class Department { name: string; constructor(n: string) { this.name = n; } describe() { console.log('Department: ' + name); } }
متد describe که در بالا می بینید دارای خطا است و اجرا نمی شود. آیا می دانید چرا؟ اگر یادتان باشد scope توابع، خاص خود آن توابع است بنابراین name در این تابع یعنی یک متغیر محلی که درون describe تعریف شده باشد و یا اینکه متغیری سراسری که به همین نام وجود داشته باشد. قطعا چنین چیزی وجود ندارد.
همیشه یادتان باشد: هر زمان که بخواهیم به یکی از خصوصیات یا متدهای یک کلاس، از درون همان کلاس، اشاره کنیم باید از کلیدواژه this استفاده کنیم. بنابراین شکل صحیح آن بدین صورت است:
describe() { console.log('Department: ' + this.name); }
حالا می توانیم از این متد استفاده کنیم:
class Department { name: string; constructor(n: string) { this.name = n; } describe() { console.log('Department: ' + this.name); } } const accounting = new Department('Accounting'); accounting.describe();
همانطور که گفتم accounting یک نمونه ساخته شده از کلاس Department بود بنابراین می تواند از متدهای درون آن استفاده کند. حالا اگر به کنسول مرورگر نگاه کنید مقدار زیر را می بینید:
Department: Accounting
اگر با جاوا اسکریپت کار کرده باشید می دانید که کلیدواژه this معمولا باعث بروز مشکلاتی می شود. بگذارید برایتان مثالی بزنم. اگر کد زیر را بنویسیم چه اتفاقی می افتد؟
const accounting = new Department('Accounting'); accounting.describe(); const accountingCopy = { describe: accounting.describe }; accountingCopy.describe();
در کد بالا یک شیء جدید ساخته ام (accountingCopy) که به متد describe اشاره می کند و نهایتا آن را صدا زده ایم. اگر به مرورگر برویم با مقدار زیر روبرو می شویم:
همانطور که می بینید undefined برگردانده شده است. چرا؟ در اینجا accountingCopy بر اساس کلاس ساخته نشده است بلکه بر اساس یک شیء ساخته شده است بنابراین خصوصیت describe آن به متد describe در شیء accounting اشاره می کند، نه کلاس Department. تا اینجا مشکلی نیست. مشکل اینجاست که در این حالت کلیدواژه this دیگر به شیء accounting اشاره نمی کند، بلکه به چیزی اشاره می کند که متد را صدا زده است (یعنی accountingCopy). در واقع this در اکثر مواقع به چیزی اشاره می کند که متد مورد نظر را صدا زده است. از آنجایی که accountingCopy بر اساس کلاس Department ساخته نشده است، بنابراین خصوصیت name ای نیز ندارد و مقدار undefined برایمان برگردانده می شود.
این مشکل هم در تایپ اسکریپت و هم در جاوا اسکریپت وجود دارد. برای حل آن می توانیم پارامتر مخصوصی به describe بدهیم که this نام دارد:
class Department { name: string; constructor(n: string) { this.name = n; } describe(this: Department) { console.log('Department: ' + this.name); } }
این پارامتر ویژه در کد بالا مشخص است. با پاس دادن this و سپس مشخص کردن تایپ از نوع Department برای آن به تایپ اسکریپت می گوییم که this درونِ describe باید همیشه به نمونه ای از کلاس Department اشاره کند. این قابلیت مخصوص تایپ اسکریپت است که البته یک پارامتر خاص است و واقعا نیازی به پاس دادن چیزی به describe در هنگام فراخوانی آن نداریم. به محض انجام این کار، خطایی دریافت می کنیم که می گوید صدا زدن describe روی accountingCopy باعث مشکل می شود.
برای حل آن باید برای accountingCopy یک خصوصیت به نام name ایجاد کنیم تا به آن اشاره کند:
class Department { name: string; constructor(n: string) { this.name = n; } describe(this: Department) { console.log('Department: ' + this.name); } } const accounting = new Department('Accounting'); accounting.describe(); const accountingCopy = { name: 'DUMMY', describe: accounting.describe }; accountingCopy.describe();
حالا کد ما صحیح کار می کند و در کنسول مرورگر مقدار صحیح را می بینیم:
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.