34- آموزش جاوااسکریپت | آموزش Prototype (پروتوتایپ)
در این آموزش با مفهوم پروتوتایپ (Prototype) در جاوااسکریپت، زنجیره پروتوتایپ (Prototype Chain) و نحوه انتقال متدها به پروتوتایپ برای بهینهسازی مصرف حافظه آشنا میشویم. یاد میگیریم چطور متدهایی بسازیم که دادهها را بخوانند یا تغییر دهند
یکی از مباحثی که در ابتدا برای یادگیری جاوااسکریپت میتواند کمی گیجکننده باشد، مفهوم پروتوتایپ (Prototype) است. در زبانهای برنامهنویسی مبتنی بر کلاس (مانند جاوا، سیشارپ یا پیاچپی)، ساختار اشیاء مستقیماً از کلاسها برمیآید. اما در جاوااسکریپت ES5، سیستم کلاس سنتی وجود ندارد و همهچیز بر پایه پروتوتایپها مدیریت میشود.
هر شیء در جاوااسکریپت یک پروتوتایپ دارد که خود یک شیء دیگر است. تمام اشیاء، ویژگیها و متدهای خود را از پروتوتایپ خود به ارث میبرند.
انواع پروتوتایپها و زنجیره پروتوتایپ (Prototype Chain)
- وقتی یک شیء را بهصورت Object Literal (مانند
{}) ایجاد میکنید، این شیء از پروتوتایپ اصلی به نامObject.prototypeارثبری میکند. - وقتی شیء را با استفاده از یک تابع سازنده (Constructor) ایجاد میکنید، پروتوتایپ آن از ساختاری مانند
Person.prototypeمنشأ میگیرد.
به این رابطه و وراثت چندلایه، زنجیره پروتوتایپ (Prototype Chain) میگویند. در نهایت، همهچیز در بالاترین نقطه زنجیره به Object.prototype ختم میشود.
راهاندازی تابع سازنده Person
برای بررسی عملی این مفاهیم، تابع سازنده Person را ایجاد میکنیم. این بار ویژگیهای نام کوچک، نام خانوادگی و تاریخ تولد را برای آن تعریف میکنیم:
function Person(firstName, lastName, dob) {
this.firstName = firstName;
this.lastName = lastName;
this.birthday = new Date(dob);
}
اکنون دو نمونه از روی این سازنده ایجاد میکنیم:
const john = new Person('John', 'Doe', '8-12-1990');
const mary = new Person('Mary', 'Johnson', 'March 20 1978');
چرا باید متدها را به Prototype منتقل کنیم؟
اگر متدی مانند محاسبه سن را مستقیماً درون تابع سازنده تعریف کنیم، با ساخت هر نمونه جدید از شیء، یک نسخه مجزا از آن تابع در حافظه سیستم ذخیره میشود. از آنجا که منطق محاسبه سن برای تمام اشخاص کاملاً یکسان است، نیازی نیست این تابع برای هر شیء کپی شود.
بهترین کار این است که متدهای مشترک را به پروتوتایپ اضافه کنیم تا همه اشیاء از یک نسخه مشترک استفاده کنند و حافظه سیستم بیهوده اشغال نشود.
۱. تعریف متد calculateAge در پروتوتایپ
برای اضافه کردن متد محاسبه سن به پروتوتایپ Person از ساختار زیر استفاده میکنیم:
Person.prototype.calculateAge = function() {
const diff = Date.now() - this.birthday.getTime();
const ageDate = new Date(diff);
return Math.abs(ageDate.getUTCFullYear() - 1970);
};
حالا اگر شیء mary را در مرورگر چاپ کنید، متد calculateAge را در ویژگی داخلی __proto__ مشاهده خواهید کرد.
نحوه مشاهده پروتوتایپ در مرورگر (Developer Tools)** برای اینکه ببینید این متد دقیقاً کجا ذخیره شده است، مراحل زیر را در کنسول مرورگر انجام دهید:
- کلید F12 یا ترکیب Ctrl + Shift + I را بزنید تا Developer Tools باز شود.
- در تب Console، نام شیء خود را تایپ کرده و اینتر بزنید (مثلاً
console.log(mary);). - روی فلش کوچک کنار نام شیء کلیک کنید تا باز شود.
- به دنبال ویژگی
[[Prototype]](در نسخههای قدیمیتر__proto__) بگردید و آن را باز کنید. - در اینجا لیست تمام متدهایی که به
Person.prototypeاضافه کردهاید (مانندcalculateAge) را مشاهده خواهید کرد.
- تغییر نام در کنسول: در نسخههای جدید کروم، کلمه
__proto__با نام[[Prototype]]نمایش داده میشود. - ** تفاوت رنگ:** در کنسول مرورگر، معمولاً متدهایی که مستقیماً روی خود شیء هستند پررنگتر و متدهای پروتوتایپ کمی شفافتر (Light) نمایش داده میشوند که نشاندهنده "ارثی بودن" آنهاست.
برای اجرای آن نیز درست مانند قبل عمل میکنیم:
console.log(john.calculateAge()); // خروجی: سن جان را برمیگرداند
۲. تعریف متد getFullName در پروتوتایپ
میتوانیم متدی برای دریافت نام کامل شخص نیز به پروتوتایپ اضافه کنیم:
Person.prototype.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
};
console.log(mary.getFullName()); // خروجی: Mary Johnson
۳. تغییر دادهها با استفاده از متدهای پروتوتایپ
متدهای پروتوتایپ نهتنها میتوانند دادهها را بخوانند، بلکه قادر به ویرایش اطلاعات شیء نیز هستند. برای نمونه، متدی مینویسیم که در صورت ازدواج شخص، نام خانوادگی او را تغییر دهد:
Person.prototype.getsMarried = function(newLastName) {
this.lastName = newLastName;
};
// نمایش نام کامل اولیه
console.log(mary.getFullName()); // Mary Johnson
// اجرای متد تغییر نام خانوادگی
mary.getsMarried('Smith');
// نمایش نام کامل جدید
console.log(mary.getFullName()); // Mary Smith
استفاده از متدهای Object.prototype
از آنجا که تمام اشیاء در انتهای زنجیره پروتوتایپ به Object.prototype متصل هستند، به متدهای سراسری آن دسترسی دارند. یکی از این متدها hasOwnProperty است که بررسی میکند آیا یک ویژگی مستقیماً متعلق به خود شیء است یا در پروتوتایپ آن قرار دارد.
// ویژگی firstName مستقیماً روی شیء تعریف شده است
console.log(mary.hasOwnProperty('firstName')); // true
// متد getFullName در پروتوتایپ قرار دارد و ویژگی مستقیم خود شیء نیست
console.log(mary.hasOwnProperty('getFullName')); // false
جمعبندی
استفاده از پروتوتایپها یکی از اصول بهینهسازی کدهای شیگرا در جاوااسکریپت است. با قرار دادن متدها در پروتوتایپ بهجای تابع سازنده، از اشغال فضای اضافی در حافظه جلوگیری میکنیم و کدی تمیزتر، سریعتر و مقیاسپذیرتر خواهید داشت.