در این جلسه به یکی از عجیب ترین و گیج کننده ترین قسمت های جاوا اسکریپت برای افراد مبتدی می رسیم: 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__
نیز قابل مشاهده است:
همانطور که می بینید این همان Person.prototype ما است. هر چیزی که درون Constructor خود بگذاریم در این قسمت دیده خواهد شد. اگر دقت کنید داخل همین __proto__
یک __proto__
دیگر داریم که همان Object.prototype ما است و 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 متصل شده است:
برای استفاده از این متد نیز مانند قبل عمل می کنیم:
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 نام او را عوض کرده ایم. نتیجه در کنسول مرورگر به شکل زیر خواهد بود:
بنابراین متدها قدرت فراوانی برای تغییر داده ها و پویاسازی برنامه ی شما دارند و نهایتا این موضوع بر عهده ی شما است که از آن ها به صورت بهینه و دقیق استفاده کنید. قبل از پایان این بحث باید به نکته ی دیگری اشاره کنم: اگر یادتان باشد گفتم که ما می توانیم به 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 را تکمیل کنیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.