Prototype چیست؟

گردآوری و تالیف : ارسطو عباسی
تاریخ انتشار : 04 آبان 1397
دسته بندی ها : جاوا اسکریپت

توسعه‌دهندگان مبتدی جاوااسکریپت معمولا به صورت اشتباه کلمه Prototype را به دو مفهوم متفاوت ربط می‌دهند. این دو مفهوم object prototype و prototype property در یک تابع است. اما دقیقا تفاوت این دو مورد چیست؟ در این مطلب قصد داریم در این رابطه بیشتر صحبت کنیم.

قبلا فکر می‌کردم که به خوبی قضیه Prototypeها و ارث‌بری Prototype را در جاوااسکریپت درک کرده‌ام. اما بعد از مدتی وقتی که به Prototype در کدها و مستندات اشاره می‌شد دچار سردرگمی می‌شدم. 

بسیاری از درگیری‌های من از آنجایی شروع شد که مردم وقتی راجع به Prototype صحبت می‌کردند دو مفهوم کاملا متمایز از همدیگر را به یک صورت بیان می‌کردند و آن‌ها را یکی می‌دانستند.

۱. Object Prototype: یک الگوی object است که متدها و خاصیت‌های دیگر در جاوااسکریپت می‌توانند براساس آن نمونه‌هایی را بسازند.

۲. non-enumerable prototype property: یک راه‌حل ساده برای پیاده‌سازی الگوهای طراحی.

استفاده از این مورد تا زمانی که نتوان یک ارث بری درست را از یک تابع (تابع سازنده یا تابع Factory) داشت، معنا و مفهوم بسیار دقیقی ندارند. اگرچه تمام توابع در جاوااسکریپت یک خاصیت را به صورت پیشفرض دارند. اما یک خاصیت constructor نیز به صورت پیشفرض وجود دارد که به تابع اصلی اشاره می‌کند.

برای مدت طولانی بود که من با تعریف اول هیچ مشکلی نداشتم اما تعریف دوم کمی برای من سردرگم کننده و عجیب بود.

این تمایز چه اهمیتی دارد؟

قبل از اینکه تفاوت دقیق Object Prototype و non-enumerable prototype property را درک کنم، با عبارت زیر برخورد کردم و بیشتر از پیش دچار درگیری شدم:

Array.prototype.slice.call([1, 2], 0, 1);

برای درک این موضوع می‌توانید این لینک را دنبال کنید. 

در برخورد با این عبارت یک سوال در ذهنم شکل گرفت که در آن زمان قادر به پاسخگویی آن نبودم:

چرا ما باید برای استفاده از slice در یک آرایه به prototype سازنده آن اشاره کنیم؟ آیا نباید خود Array چنین متدی داشته باشد؟

این سوال من را کاملا در رابطه با الگوی طراحی که در Prototypeها به کار می‌بریم راهنمایی کرد.

۳ قدم برای درک درست خاصیت Prototype در توابع جاوااسکریپت

در جهت درک خاصیت Prototype در توابع جاوااسکریپت نیاز است که درک خوبی از الگوهای طراحی اولیه‌ای که ما را قادر به استفاده از این خاصیت می‌کنند داشته باشیم. برای درک این الگو من یک مثال را با شما حل می‌کنم.

پیاده‌سازی ۱: الگوی کلاس Functional

تصور کنید که ما قصد ساخت بازی را داریم که در آن با یک دسته از سگ کار می‌کند. ما قصد داریم به سرعت تعداد زیادی سگ ایجاد کنیم که به ویژگی‌های اصلی آن‌ها مانند pet و giveTreat دسترسی داشته باشیم.

می‌توانیم بازی‌مان را براساس الگوی کلاس Functional به صورت زیر ایجاد کنیم:

var createDog = function(name) {

  var newDog = {};

  newDog.name = name;

  newDog.giveTreat = function() { console.log("bark bark bark") };

  newDog.pet = function() { console.log("*wags tail*") };

  return newDog;

}

var zeus = createDog("zeus");

var casey = createDog("casey");

zeus.giveTreat(); 

// "bark bark bark"

casey.giveTreat();

// "bark bark bark

بیایید با قرار دادن متدها در شئ‌ها برنامه را تمیز تر بنویسیم. بعد از آن می‌توانیم تابع کارخانه createDog را توسعه دهیم:

var methodsForShowingAffection = {

  giveTreat: function() { console.log("bark bark bark") },

  pet: function() { console.log("*wags tail*") }

}

var createDog = function(name) {

  var newDog = {};

// here we are manually doing what jQuery.extend() does

  for (var method in methodsForShowingAffection) {

    newDog[method] = methodsForShowingAffection[method];

  }

  

  newDog.name = name;

  return newDog;

}

var zeus = createDog("zeus");

var casey = createDog("casey");

zeus.giveTreat(); 

// "bark bark bark"

casey.giveTreat();

// "bark bark bark"

اگرچه پیاده‌سازی این برنامه بسیار ساده است و در دیگر زبان‌ها چنین قابلیتی برای شما نیز فراهم شده است، اما با این وجود هنوز یک مشکل دیگر وجود دارد و آن این است که: ما تعریف متدمان را برای هر شئ کپی کرده‌ایم و در تابع کارخانه‌ای createDog قرار داده‌ایم.

این کار باعث می‌شود که حافظه بسیار بیشتری صرف شود و قاعده DRY اجرا نشود. نظرتان چیست که بجای کپی کردن این تعداد، یک متد را ایجاد نکنیم؟

بازسازی ۱: پیاده‌سازی یک الگوی طراحی Prototypal Class 

ارث بری Prototypal دقیقا همان چیزی است که در آن به دنبال آن می‌گشتیم و باید از آن استفاده کنیم. این کار باعث می‌شود که ما بتوانیم متدهای‌مان را در یک شئ prototype قرار دهیم. 

به کد زیر دقت کنید:

var methodsForShowingAffection = {

  giveTreat: function() { console.log("bark bark bark") },

  pet: function() { console.log("*wags tail*") }

}

var createDog = function(name) {

  // make methodsForShowingAffection the prototype for newDog

  var newDog = Object.create(methodsForShowingAffection);

  newDog.name = name;

  return newDog;

}

var zeus = createDog("zeus");

var casey = createDog("casey");

zeus.giveTreat(); 

// "bark bark bark"

casey.giveTreat();

// "bark bark bark"

عالی‌ است! حال دیگر نیازی نیست برای عنوان‌های مختلف از کپی کردن‌های متعدد استفاده کنیم:

  

اما به نظر می‌رسد که اگر methodsForShowingAffection در تابع کارخانه createDog کپسوله‌سازی بشود، زیبا نخواهد بود. به همین دلیل بهتر است که این متد تنها با تابع تعامل داشته باشد. برای این کار می‌توانید پروژه را به صورت زیر تغییر دهید:

var createDog = function(name) {

  // make methodsForShowingAffection the prototype for newDog

  var newDog = Object.create(createDog.methodsForShowingAffection);

  newDog.name = name;

  return newDog;

}

createDog.methodsForShowingAffection = {

  giveTreat: function() { console.log("bark bark bark") },

  pet: function() { console.log("*wags tail*") }

}

var zeus = createDog("zeus");

var casey = createDog("casey");

zeus.giveTreat(); 

// "bark bark bark"

casey.giveTreat();

// "bark bark bark"

بازسازی ۲: ارث‌بری Prototypal 

عالی است! اما آيا استفاده از نام methodsForShowingAffection خیلی عجیب و سخت نیست؟ چرا چیزی طبیعی‌تر و قابل پیش بینی تر را امتحان نکنیم؟ می‌توانید به صورت زیر کدها را بهتر نمایید:

var createDog = function(name) {

  // make the "prototype" property on the factory function the       

  // prototype for newDog

  var newDog = Object.create(createDog.prototype);

  newDog.name = name;

  return newDog;

}

createDog.prototype.giveTreat = function() { console.log("bark bark bark") };

createDog.prototype.pet = function() { console.log("*wags tail*") };

var zeus = createDog("zeus");

var casey = createDog("casey");

zeus.giveTreat(); 

// "bark bark bark"

casey.giveTreat();

// "bark bark bark"

هیچ چیز خاص و عجیب و غریبی در رابطه با prototypeها وجود ندارد. همانطور که در بالا نشان دادیم همه چیز می‌تواند بسیار طبیعی پیش برود. 

برای پیدا کردن اطلاعات بیشتر راجع به این موضوع می‌توانید مستندات مربوط به موزیلا را مطالعه کنید. 

مقالات پیشنهادی

wireframe ، prototype ، mockup چه تفاوتی با هم دارند ؟

اصطلاحات wireframe و prototype و mockup اغلب به صورت مترادف در پروژه ها مورد استفاده قرار میگیرن در عين حال یکی نیستند . در اين پست توضيح میدیم ك...

Flat-File CMS چیست؟

سیستم های مدیریت محتوای مبتنی بر Flat-file امروزه به یکی از محبوب ترین سیستم ها برای اپلیکیشن های تحت وب با اندازه کوچک و متوسط تبدیل شده است. طراحان...

دیزاین پترن چیست؟

در برنامه‌نویسی معمولا یک سری مسئله‌ های خیلی ساده و شناخته شده‌ای داریم که بسیار پر رخداد و تکراری هستند. برای حل این مسئله‌ها هرکسی احتمالا یک راه‌ح...

UML - زبان مدل سازی یکنواخت چیست؟

UML یک زبان مدلسازی همه منظوره استاندارد  و از زیرمجموعه های مبحث مهندسی نرم‌افزار است که توسط Object management group ایجاد شده است.