پروتوتایپ در زبان انگلیسی یعنی نمونه اولیه. مثلا وقتی که میخای یک محصولی رو به سرمایه گذار عرضه کنی میگن که یک پروتوتایپ ازش ارائه بده تا بررسی بشه. در برنامه نویسی هم Prototype مفهوم مشابهی داره ولی هر وقت گفتن prototype در jS چیه و چی نیست از این فرمول استفاده کن: prototype یک key رو Function object هاست. تمام! اگه این طوری به پروتوتایپ نگاه کنی درکش برات راحت تر میشه. در ادامه این مقاله به بررسی مفهومی پروتوتایپ می پردازیم.
در برنامه نویسی (فرقی هم نداره کدوم زبان) مفهوم ارث بری (inheritance) نمود های مختلفی داره. مثلا وقتی یک class رو از روی کلاس دیگری extend می کنیم، کلاس فرزند (Child class) ویژگی ها (property) و متد های کلاس والد (Parent) رو به ارث می بره. جاوا اسکریپت از Design Pattern ای استفاده می کنه (در ES6 به بعد jS کلاس ها رو هم اضافه کرد ولی استفاده از کلیدواژه class در jS هم در واقع راه دیگری برای پروتوتایپ کردن عه) به نام Prototyping که یعنی object ای رو از روی object دیگری clone کرد.
بیا یک بررسی دقیق تر داشته باشیم که object یعنی چی اصلا. در زبان انگلیسی object یعنی یک شئ واقعی که تو جهان فیزیکی وجود داره. مثلا ماشین، درخت، دیوار ... در برنامه نویسی هم تقریبا هر چیزی یک object حساب میشه. البته ممکنه این object ها مفاهیم انتزاعی باشن. مثلا یک Account هر چند که وجود فیزیکی نداره ولی توی برنامه نویسی می تونیم object اش رو داشته باشیم. jS از Prototyping برای درست کردن object ها استفاده میکنه. یعنی به جای درست کردن object ها از روی یک class، آبجکت ها از روی object دیگری درست میشن.
مجددا تاکید میکنم که از سال 2015 به بعد و با معرفی ES6 جاوا اسکریپت توانایی اعلان کردن class و درست کردن object از روی class رو به خودش اضافه کرد. ولی این راهکار هم در واقع مسیر دیگری هست برای پروتوتایپ کردن و در عمل چیزی فرق نکرده.
منظور از clone کردن درست کردن یک object از روی object دیگری هست. به عبارتی object ای که جدیدا ایجاد کردیم از روی prototype اش (که اونم یک object عه) درست بشه. به کد زیر دقت کن:
const user1 = { name: 'Bizhan', family: 'Hejazi', age: 26 }; // cloning user2 from user1 const user2 = Object.create(user1);
در مثال بالا اگه user2 رو console.log کنیم یک object با یک object خالی رو به رو میشیم:
console.log(user2);
اما اگر سعی کنیم که به ویژگی name روی user2 دسترسی پیدا کنیم با مقدار Bizhan رو به رو خواهیم شد !!! خوب چطوری ممکنه که یک object رو وقتی console.log می کنیم خالی باشه ولی درونش یک ویژگی هم موجود باشه که مقدار خاصی داشته باشه؟!
console.log(user2.name);
همون طوری که گفتیم user2 از روی user1 کلون شده. پس به عبارتی پروتوتایپ user2 برابر است با user1. این قضیه رو می تونیم با کد زیر متوجه بشیم:
console.log(Object.getPrototypeOf(user2)) // {name: 'Bizhan, family:'Hejazi', age: 26}
و همون طوری هم که گفتیم مفهوم inheritance در prototyping هم وجود داره. پس user2 هر چند که خودش property ای به نام name نداشت ولی چون که این property در prototype اش موجود بود بهش به ارث رسیده. در حقیقت زمانی که ما گفتیم user2.name رو برامون console.log کنه مفسر jS این گام ها رو پیموده تا به ما خروجی بده:
به مفهوم بالا میگن Prototype Chain که یعنی زنجیره ای از پروتوتایپ ها. دقیقا برای همینه که در jS می تونیم یک رشته رو به صورت primitive اعلان کنیم ولی از متد های پروتوتایپ String.prototype استفاده کنیم. به مثال زیر دقت کن:
const name = 'bizhan'; console.log(name.length);
در مثال بالا متغیر name یک متغیر عینی عه (به عبارتی literal عه). یعنی فقط مقدار داره. پس اگه literal عه چطوری مثل یک object باهاش برخورد کردیم و حتی property ای ازش به نام length رو پرینت کردیم؟! دلیلش اینه که وقتی جلوی یک literal نقطه میذاریم مفسر jS به صورت خودکار این مقدار ساده رو تبدیل به object ای میکنه که از روی prototype مرتبط درست شده باشه. مثلا در کد بالا name رو تبدیل به object ای میکنه که از روی پروتوتایپ String درست شده باشه و چون روی این پروتوتایپ ویژگی length هست پس مشکلی پیش نمیاد. اگر میخاستیم به صورت دستی از همون اول به جای literal string یک object string اعلان می کردیم باید این طوری می گفتیم:
const name = new String('bizhan');
ولی این عادت جالبی نیست و سرعت اجرای کد رو کاهش میده. از طرفی ممکنه باعث ایجاد خطا های ناخواسته باشه. مثلا دو تا متغیر که هر دو تا شون string literal هستن (یعنی به صورت ساده و با استفاده از " یا ' اعلان شدن) می تونن با هم دیگه مقایسه بشن ولی وقتی که دو تا object string داریم حتی اگه مقدارشون هم دقیقا برابر باشه مقایسه شون نتیجه false میده. چرا؟! چون object ها با هم قابل مقایسه نیستن.
// Literal strings const name1 = 'bizhan'; const name2 = 'bizhan'; console.log(name1 === name2); // true // Object strings const name3 = new String('bizhan'); const name4 = new String('bizhan'); console.log(name3 === name4); // false
حالا که با object string ها آشنا شدیم وقتشه با constructor function ها آشنا بشیم. به کد زیر دقت کن:
const name = new String('bizhan');
در کد بالا String دقیقا چیه؟! شاید فکر کنی که یک class عه. ولی همون طوری که میدونیم jS از prototype استفاده میکنه به جای class. شاید هم بگی که پس یک prototype عه که نشون میده نزدیک شدی ولی دقیق نگفتی هنوز. در کد بالا String دقیقا یک constructor function عه. یعنی تابعی که یک object رو میسازه. در jS به صورت پیش فرض Constructor function هایی داریم مثل: Number, Boolean, String, Array و ... ولی می تونیم constructor function خودمون رو هم اعلان کنیم تا باهاش object های مد نظرمون رو بسازیم. به روایت دیگه به جای این که یک object رو با استفاده از Object.create کلون کنیم با استفاده از تابع اختصاصی خودش درست کنیم. در کد زیر یک تابع سازنده رو اعلان کردیم:
function Person(name) { this.name = name; this.sayHello = function() { console.log('Hello'); } }
به عنوان یک عادت خوب اسم توابع سازنده رو با حروف بزرگ شروع کن. حالا در کد زیر یک object رو با استفاده از این تابع سازنده درست میکنیم:
const bizhan = new Person('bizhan'); console.log(bizhan.name); bizhan.sayHello();
از طرف دیگه میدونیم که یک function در jS خودش یک object عه! یادته که گفتم تقریبا هر چیزی توی jS یک object حساب میشه. اگه شک داری کد زیر رو اجرا کن:
console.log(Person instanceof Object); // true
حالا که تابع سازنده خودش یک object عه پس باید پروتوتایپ داشته باشه. برای رسیدن به پروتوتایپ تابع Person از کد زیر استفاده می کنیم:
Object.getPrototypeOf(bizhan); // {}
مشاهده میکنیم که یک object خالی به ما داده! یعنی پروتوتایپ تابع Person یک object خالی بوده! منطقی هم هست. چون با استفاده از Object.create تابع Person رو درست نکردیم بلکه به صورت مستقل اعلانش کردیم. حالا که prototype اش خالیه آیا نمیشه به prototype تابع Person چیزی اضافه کرد؟! چرا. میشه. این طوری:
Person.prototype.age = 26;
یادته که اول این مقاله گفتم prototype یک property از Function Object هاست؟! تو کد بالا دقیقا از این property استفاده کردیم. حالا که Prototype عه Person رو عوض کردیم آیا object هایی که با استفاده از Person درست شدن هم عوض شدن؟! بله. همه اون ها هم دیگه property ای به نام age بهشون اضافه شده. پس زمانی که prototype رو عوض کنیم object هایی که از روش نوشته شدن هم عوض میشن. البته برعکس این قضیه درست نیست. یعنی زمانی که object ای رو عوض میکنیم prototype اش کاری نمیشه. بذار با یک مثال درک این قضیه رو برات راحت کنم:
زمانی که قالب کیک رو عوض کنیم هر کیکی که درست کنیم با اون قالب هم عوض میشه. ولی وقتی که یک کیک که با هر قالبی درست کردیم رو رنگ کنیم، برش بزنیم، بخوریم ... در هر صورت به قالب آسیبی نمیرسه.
البته دستکاری prototype ها کار قشنگی نیست و توصیه میشه که به هیچ وجه prototype های داخلی jS رو دستکاری نکنین. مثلا Array.prototype رو عوض نکنین. از طرفی prototype های خودتون رو هم (یعنی اونایی که خودمون تابع سازنده اش رو اعلان کردیم مثلا همین Person) تا حد امکان دستکاری نکنین.
با استفاده از تابع سازنده Person اومدیم و Object ای به نام bizhan رو درست کردیم و مشاهده کردیم که prototype اش یک object خالی بود. از طرفی یک object هر چند که خالی باشه ولی چون یک object عه پس خودش هم یک prototype داره. پروتوتایپ یک object خالی میشه Object پس در jS هر شئ ای که درست کنیم در نهایت زنجیره پروتوتایپ هاش ختم میشه به Object! برای همینه که توی jS تقریبا هر چیزی یک object عه. این پروتوتایپ Object یک سری متد هایی داره که به همه فرزندانش به ارث میرسه. مثلا:
Object.hasOwnProperty() Object.valueOf() Object.toLocaleString() ...
و به همین دلیل متد هایی مثل valueOf و hasOwnProperty در هر object ای که توی jS اعلان کنیم وجود داره.
در سال 2015 و با منتشر شدن نسخه ES6 توانایی اعلان کردن class ها و ایجاد object از روی class به وجود اومد. اعلان کردن class در jS در ظاهر شبیه به زبان هایی مثل Java هست ولی در پشت صحنه مجددا همون prototyping عه پس در عمل چیزی فرق نکرده. همون طوری که در توی prototyping تابع سازنده داشتیم در class ها هم متد سازنده داریم. در هر صورت prototyping یکی از Design Pattern های معروفه که تسلط بهش باعث میشه دید بازتری به جهان برنامه نویسی و مهندسی نرم افزار داشته باشی.