آشنایی با مفهوم Prototype ها

25 آبان 1398
جاوا اسکریپت OOP: آشنایی با مفهوم Prototype ها

جاوا اسکریپت OOP: مفهوم Prototype در جاوا اسکریپت

در این جلسه به یکی از عجیب ترین و گیج کننده ترین قسمت های جاوا اسکریپت برای افراد مبتدی می رسیم: prototype ها! معمولا زبان های برنامه نویسی مشهور مانند Java و PHP و #C و امثالهم در زمینه ی شیء گرایی از کلاس ها استفاده می کنند. به عبارت دیگر شیء گرایی این زبان ها بر محوریت کلاس ها  تعریف شده است اما در نسخه ی ES5 زبان جاوا اسکریپت چنین چیزی را نداریم! اینجاست که اکثر افراد با مفهوم prototype آشنا می شوند که حسابی آن ها را گیج می کند.

به زبان ساده هر شیء در زبان جاوا اسکریپت یک prototype دارد و خود prototype نیز یک شیء است. تمام اشیاء جاوا اسکریپتی خصوصیات و متدهایشان را از prototype هایشان به ارث می برند. مثلا زمانی که در حال نوشتن Object literal هستید (یعنی به صورت دستی خودتان یک شیء را بنویسید) prototype شما object.prototype نام دارد. هنگامی که در حال ساخت یک شیء از طریق Constructor ها هستید (کلیدواژه ی new) مسئله متفاوت است. مثلا در جلسه ی قبل یک constructor به نام Person ساختیم بنابراین prototype ما Person.prototype است اما می توانید یک مرحله بالاتر بروید و به object.prototype دسترسی داشته باشید.

بگذارید در قالب یک مثال توضیح بدهم. از جلسه ی قبل constructor خودمان را کپی می کنیم:

// Person constructor
function Person(firstName, lastName, dob) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthday = new Date(dob);
    this.calculateAge = function () {
        const diff = Date.now() - this.birthday.getTime();
        const ageDate = new Date(diff);
        return Math.abs(ageDate.getUTCFullYear() - 1970);
    }
}

من یک مورد (lastName) را نیز به این constructor اضافه کرده ام. حالا دو شیء جدید (نمونه یا instance) می سازم:

const john = new Person('John', 'Doe', '8-12-90');
const mary = new Person('Mary', 'Johnson', 'March 20 1978');

console.log(mary);

با اجرای کد بالا Mary را در کنسول مرورگر می بینیم. اگر به دقت نگاه کرده باشید یک خصوصیت به نام __Proto__ نیز قابل مشاهده است:

خصوصیت Proto
خصوصیت Proto

همانطور که می بینید این همان Person.prototype ما است. هر چیزی که درون Constructor خود بگذاریم در این قسمت دیده خواهد شد. اگر دقت کنید داخل همین __proto__ یک __proto__ دیگر داریم که همان Object.prototype ما است و prototype اصلی اشیاء در جاوا اسکریپت است:

proto دوم نشان دهنده ی prototype شیء اصلی در جاوا اسکریپت است
proto دوم نشان دهنده ی prototype شیء اصلی در جاوا اسکریپت است

می بینید که انواع متدها درون این prototype موجود است. Prototype در لغت به معنی «طرح اولیه» یا «پیش‌نمونه» یا «نخستین الگو» است بنابراین یک prototype در واقع یک طرح کلی و اولیه برای دسته ای از اشیاء است و می توان گفت به نوعی معادل کلاس ها می باشد.

 حالا به constructor خود (Person) برگردید. همانطور که می بینید هر شیء ای که از Person ساخته می شود باید نام، نام خانوادگی و تاریخ تولد را داشته باشد اما calculateAge برای همه ی این اشیاء یک تابع ثابت است و نیازی به تکرار آن نیست. بنابراین باید آن را داخل prototype بگذاریم نه خود شیء! برای اضافه کردن آن به prototype ابتدا آن را از داخل خود شیء کامنت کنید:

// Person constructor
function Person(firstName, lastName, dob) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.birthday = new Date(dob);
  // this.calculateAge = function(){
  //   const diff =  Date.now() - this.birthday.getTime();
  //   const ageDate = new Date(diff);
  //   return Math.abs(ageDate.getUTCFullYear() - 1970);
  // }
}

حالا به شکل زیر تابع calculateAge را به prototype خود اضافه می کنیم:

// Calculate age
Person.prototype.calculateAge = function(){
  const diff =  Date.now() - this.birthday.getTime();
  const ageDate = new Date(diff);
  return Math.abs(ageDate.getUTCFullYear() - 1970);
}

فایل خود را ذخیره کرده و به مرورگر بروید. مشاهده خواهید کرد که calculateAge به صورت یک متد به خود prototype متصل شده است:

متصل شدن calculateAge به prototype
متصل شدن calculateAge به prototype

برای استفاده از این متد نیز مانند قبل عمل می کنیم:

console.log(john.calculateAge());

خروجی این کد عدد 27 خواهد بود بنابراین هیچ مشکلی در اجرای آن نداریم. برای تمرین بیشتر دو متد دیگر به نام های getFullName و getsMarried را نیز به آن اضافه می کنیم:

// Get full name
Person.prototype.getFullName = function(){
  return `${this.firstName} ${this.lastName}`;
}

// Gets Married
Person.prototype.getsMarried = function(newLastName){
  this.lastName = newLastName;
}

اگر با دستور Console.log از این متد استفاده کنیم برای getFullName مقدار Mary Johnson یا نام کامل این خانم را می گیریم. حالا فرض کنید این خانم ازدواج کند (در آمریکا رسم است که با ازدواج، زن فامیل مرد را به عنوان نام خانوادگی خودش انتخاب می کند) و بخواهد نام خانوادگی اش را تغییر دهد. متد getsMarried می تواند نام شیء ساخته شده را تغییر دهد! به مثال زیر توجه کنید:

const mary = new Person('mary', 'Johnson', 'March 20 1978');

console.log(mary.getFullName());

mary.getsMarried('Smith');

console.log(mary.getFullName());

در این کد ابتدا شیء mary را ساخته ایم و سپس با تابع getsMarried نام او را عوض کرده ایم. نتیجه در کنسول مرورگر به شکل زیر خواهد بود:

تغییر نام Mary پس از اجرای متد getsMarried
تغییر نام Mary پس از اجرای متد getsMarried

بنابراین متدها قدرت فراوانی برای تغییر داده ها و پویاسازی برنامه ی شما دارند و نهایتا این موضوع بر عهده ی شما است که از آن ها به صورت بهینه و دقیق استفاده کنید. قبل از پایان این بحث باید به نکته ی دیگری اشاره کنم: اگر یادتان باشد گفتم که ما می توانیم به prototype اصلی اشیاء در جاوا اسکریپت نیز دسترسی داشته باشیم:

proto دوم نشان دهنده ی prototype شیء اصلی در جاوا اسکریپت است
proto دوم نشان دهنده ی prototype شیء اصلی در جاوا اسکریپت است

این prototype برخی از متدهای آماده را برای ما دارد که برای مثال می خواهم از یکی از آن ها استفاده کنم (hasOwnProperty). متد hasOwnProperty به شیء شما نگاه می کند تا ببیند آیا این شیء خصوصیت (property) خاصی دارد یا خیر. ما می دانیم که اشیاء ما دارای خصوصیاتی مثل firstname و lastname و dob هستند بنابراین می توانیم آن را به شکل زیر امتحان کنیم:

console.log(mary.hasOwnProperty('firstName'));

نتیجه ی این دستور true خواهد بود چرا که ما چنین خصوصیتی را در شیء mary داریم.

حالا برای شما مثالی دارم. به نظر شما نتیجه ی کد زیر چیست؟

console.log(mary.hasOwnProperty('getFullName'));

بله نتیجه False است چرا که 'getFullName' درون prototype است و بخشی از خود شیء mary نیست بنابراین به همین سادگی می توانیم بقیه ی موارد را نیز حساب کنیم.

امیدوارم با مفهوم prototype ها آشنا شده باشید. اگر دوست دارید با کلاس ها نیز آشنا شوید جای نگرانی نیست، در قسمت های بعدی به سراغ کلاس های معرفی شده در ES6 نیز خواهیم رفت اما فعلا باید مباحث ES5 را تکمیل کنیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری شی گرایی در جاوا اسکریپت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

Sajjad
16 فروردین 1399
لطفا در مورد Arrow Function ها و This در Arrow Function ها هم یه مقاله ایجاد کنید تا بهتر بتونیم درکشون کنیم. تشکر

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

امیر زوارمی
27 فروردین 1399
سلام دوست عزیز، در مقاله ی زیر این موضوع رو بررسی کردیم: https://www.roxo.ir/arrow-functions/

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.