منابع آموزشی و پژوهشی، برای دانشگاه و مدرسه
راهنمای انتخابمیز نویسنده
آموزش javascriptدرس ۹ از ۲۰رایگان

this، Prototype و Class در جاوااسکریپت با مثال کاربردی

برای فهم شیءگرایی در جاوااسکریپت، باید رابطهٔ فراخوانی تابع با this و نقش prototype را بشناسید. این درس، کلاس و فیلد خصوصی را با یک مدل پیشرفت دوره توضیح می‌دهد و خطای جداکردن متد از شیء را نشان می‌دهد.

فضای تمرینبه‌روزرسانی: ۱۴۰۵/۸/۵
ساختارهای زبان
در این درس می‌خوانید

گاهی چند شیء رفتار مشترک دارند؛ مثلاً چند یادگیرنده باید بتوانند پیشرفت خود را ثبت کنند. JavaScript رفتار مشترک شیءها را از راه prototype مدل می‌کند و class یک نحو روشن برای بسیاری از این الگوها فراهم می‌آورد. پیش از ساختن کلاس، مطمئن شوید مسئله واقعاً به داده و رفتار مرتبط نیاز دارد؛ هر تابع ساده به کلاس احتیاج ندارد. مثال‌های این درس نهم مستقل‌اند و در مرورگر به‌روز یا Node.js جدید اجرا می‌شوند.

this به شیوهٔ فراخوانی وابسته است

در یک تابع معمولی، this را صرفاً از محل نوشته‌شدن تابع حدس نزنید؛ روش فراخوانی مهم است. وقتی تابع را به شکل object.method() صدا می‌زنیم، در این نمونه this همان object است. جداکردن متد و فراخوانی آن به‌تنهایی، این ارتباط را حفظ نمی‌کند. تابع پیکانی this تازه نمی‌سازد و this محیط پیرامون را می‌گیرد؛ بنابراین برای یک متد وابسته به دریافت‌کننده، جایگزینی خودکار با arrow می‌تواند اشتباه باشد.

javascript
const progress = {
  completed: 2,
  next() {
    this.completed += 1;
    return this.completed;
  }
};
console.log(progress.next()); // 3
const nextForProgress = progress.next.bind(progress);
console.log(nextForProgress()); // 4

bind یک تابع با this مشخص می‌سازد و خود تابع اصلی را اجرا نمی‌کند. این روش هنگام دادن متد به‌عنوان callback می‌تواند مفید باشد. راه دیگر، استفاده از wrapper مانند () => progress.next() است. این دو انتخاب در همهٔ جزئیات یکسان نیستند، اما هر دو در این سناریو دریافت‌کننده را روشن می‌کنند. از اتکا به رفتار this سراسری پرهیز کنید؛ strict mode، ماژول و محیط اجرا می‌توانند نتیجه را تغییر دهند.

Prototype؛ رفتار مشترک از کجا می‌آید؟

هنگام خواندن یک ویژگی، اگر آن ویژگی روی خود شیء نباشد، موتور می‌تواند در زنجیرهٔ prototype جست‌وجو کند. این به معنای کپی‌کردن همهٔ متدها در هر نمونه نیست. زنجیره سرانجام به null می‌رسد و شیءهای ساخته‌شده با prototype برابر null هم ممکن‌اند. prototype یک سازوکار زبان است، نه همان رابطهٔ والد و فرزند در DOM؛ این دو مفهوم را از هم جدا نگه دارید.

javascript
class LessonCounter {
  constructor() { this.count = 0; }
  increment() { this.count += 1; return this.count; }
}
const counter = new LessonCounter();
console.log(counter.increment()); // 1
console.log(Object.hasOwn(counter, "count")); // true
console.log(Object.hasOwn(counter, "increment")); // false
console.log(Object.getPrototypeOf(counter) === LessonCounter.prototype); // true

یک کلاس با وضعیت خصوصی

فیلد خصوصی با # تعریف می‌شود و دسترسی مستقیم بیرون کلاس به آن مجاز نیست. در مثال بعد، completed را فقط از راه completeOne تغییر می‌دهیم و از بیشترشدن آن از تعداد کل جلوگیری می‌کنیم. این پنهان‌سازی به منظم‌کردن قرارداد برنامه کمک می‌کند؛ جایگزین امنیت سمت سرور نیست. اگر نتیجهٔ دوره برای پرداخت یا صدور مدرک مهم باشد، محاسبهٔ مرورگر به‌تنهایی مرجع اعتماد نخواهد بود.

javascript
class CourseProgress {
  #completed = 0;
  #total;
  constructor(total) {
    if (!Number.isInteger(total) || total < 1) {
      throw new RangeError("total must be a positive integer");
    }
    this.#total = total;
  }
  completeOne() {
    if (this.#completed < this.#total) this.#completed += 1;
    return this.percent;
  }
  get percent() {
    return Math.round(this.#completed / this.#total * 100);
  }
}
const jsCourse = new CourseProgress(20);
console.log(jsCourse.completeOne()); // 5
console.log(jsCourse.percent); // 5

getter شبیه یک ویژگی خوانده می‌شود؛ برای percent پرانتز نمی‌گذاریم. constructor هنگام new اجرا می‌شود و ورودی نامعتبر را رد می‌کند. متدهای class در strict mode اجرا می‌شوند؛ اگر completeOne را از نمونه جدا کنید و بدون دریافت‌کننده صدا بزنید، دسترسی به فیلد خصوصی معتبر نخواهد بود. برای callback از bind یا یک wrapper روشن استفاده کنید. فیلد خصوصی، همان ویژگی معمولی با نام رشته‌ایِ «#completed» نیست.

ارث‌بری یا ترکیب رفتار؟

extends و super برای ارث‌بری کلاس‌ها وجود دارند، اما لازم نیست از اولین پروژه زنجیرهٔ بلند کلاس بسازید. اگر هدف فقط استفادهٔ دوباره از یک محاسبه است، یک تابع مستقل می‌تواند ساده‌تر باشد. اگر چند رفتار مستقل کنار هم لازم‌اند، ترکیب شیءها یا تابع‌ها ممکن است وابستگی کمتری ایجاد کند. انتخاب را بر اساس نیاز، آزمون‌پذیری و تغییرهای احتمالی انجام دهید، نه برای شبیه‌کردن JavaScript به زبان دیگری.

تمرین و پاسخ پیشنهادی

کلاس کوچکی بسازید که عنوان درس را نگه دارد و بتواند وضعیت خوانده‌شدن را تغییر دهد. وضعیت اولیه false باشد و پس از markRead برابر true شود. سپس همین متد را به‌صورت callback مستقل اجرا کنید، به‌طوری که اتصال به نمونه حفظ شود. پاسخ زیر از وضعیت خصوصی و wrapper استفاده می‌کند؛ برای تمرین دوم می‌توانید wrapper را با bind جایگزین کنید.

javascript
class Lesson {
  #read = false;
  constructor(title) { this.title = title; }
  markRead() { this.#read = true; }
  get isRead() { return this.#read; }
}
const lesson = new Lesson("Prototype");
const finishLesson = () => lesson.markRead();
finishLesson();
console.log(lesson.isRead); // true

اشتباه‌های رایج

  • حدس‌زدن this فقط از محل تعریف تابع معمولی.
  • دادن متد جداشده به callback بدون توجه به دریافت‌کننده.
  • فرض اینکه تمام ویژگی‌های قابل خواندن، متعلق به خود نمونه‌اند.
  • استفاده از کلاس برای هر محاسبهٔ کوچک و مستقل.
  • درنظرگرفتن فیلد خصوصی مرورگر به‌عنوان کنترل امنیتی سرور.

منابع انگلیسی

متن و مثال‌های آموزشی برای این مجموعه تدوین شده‌اند. از درس بعد، منطق زبان را به ساختن و تغییر صفحهٔ وب با DOM وصل می‌کنیم.

پیش‌نیاز و محیط اجرای مثال‌ها

درس 9 از ۲۰؛ پیش‌نیاز: درس‌های قبلی مجموعه. کدهای مستقل این درس در Console تازهٔ مرورگر یا فایل JavaScript اجرا می‌شوند؛ نمونه‌ها را جداگانه آزمایش کنید.

پیشرفت روی همین مرورگر ذخیره می‌شود.

یکجا انتخاب کن، راحت‌تر دریافت کن

انتخاب‌های من ۰ منبع

مجموعهٔ مطالعه‌ات را بساز.

کنار مقاله‌ها «انتخاب» را بزنید تا چند فایل را با هم دریافت کنید.

انتخاب مقاله‌های رایگان