این مطلب را با یک مثال عجیب شروع میکنیم.
let pizza = {};
console.log(pizza.taste); // "pineapple"
خروجی کد زیر "pineapple" است به نظر شما چنین اتفاقی ممکن است؟
ما صرفا یک شیئ pizza ایجاد کردیم که هیچ پراپرتی ندارد و انتظار داریم خروجی مقدار برابر باشد ولی اینگونه نیست. با مفاهیمی که تا بحال آموختیم نمیتوانیم تصور کنیم که چه کدهایی قبل از این دو خط میتواند موجب این اتفاق شود پس مدل ذهنی ما کامل نیست.
برای حل این معما در این مطلب به شرح prototypes میپردازیم. و از همه مهمتر اینکه prototypes قلب چندین ویژگی مختلف جاوااسکریپت هستند. معمولا افراد به یادگیری این مفهوم کم توجهی میکنند زیرا این مفهوم از نظر آنها خیلی غیر عادی به نظر میرسد درصورتی که مفهومی که در آن نهفته است بسیار سادهاست.
Prototypes
مفهوم prototypes را به همراه مثالهای زیر و مدل ذهنی که تا بحال آموختیم شرح خواهیم داد.
به کد زیر و مدل ذهنی آن توجه کنید.
let human = {
teeth: 32
};
let gwen = {
age: 19
};
در مثال بالا شیئ که gwen به آن متصل است پراپرتی teeth را ندارد پس در نتیجه خروجی کد زیر undefined خواهد بود.
console.log(gwen.teeth); // undefined
فرض کنیم میخواهیم به جاوااسکریپت بگوییم در صورتی که یک پراپرتی در یک شیئ مثل پراپرتی teeth در شیئ gwen وجود نداشت به دنبال آن پراپرتی در یک شیئ دیگری مانند human بگردد و مقدار آن را برگرداند، این دقیقا همان مفهوم prototype است.
ما با تعریف یک پراپرتی به نام کلیدی __proto__ مشخص میکنیم که درصورت پیدا نشدن یک پراپرتی در یک شیئ جاوااسکریپت برای یافتن آن در کدام شیئ دیگری میتواند جستوجو کند. به مثال زیر توجه کنید.
let human = {
teeth: 32
};
let gwen = {
// We added this line:
// "Look for other properties here"
__proto__: human,
age: 19
};
مدل ذهنی قطعه کد بالا
در نتیجه پس از این تغییرات خروجی کد زیر برابر 32 میشود.
console.log(gwen.teeth); // 32
نگاه دقیقتر به فرایندی که شرح دادیم.
مفهوم The Prototype Chain
مفهوم زنجیره prototype به این اشاره میکند که جاوااسکریپت عملیات جستوجو در prototype را مشابه شئ اول را همیشه تکرار میکند و زمانی متوقف میشود که یا آن پراپرتی را پیدا کند و یا مقدار __proto__ یک شیئ تعریف نشده باشد.
مثال زیر نمونهای از این مفهوم زنجیره prototype است.
let mammal = {
brainy: true,
};
let human = {
__proto__: mammal,
teeth: 32
};
let gwen = {
__proto__: human,
age: 19
};
console.log(gwen.brainy); // true
پراپرتی اصلی یک آبجکت یا یک Prototype
برای تشخیص این مورد کافیست از متد hasOwnProperty استفاده کنیم در صورتی که پراپرتی در آبجکت موجود باشد مقدار ture در غیر اینصورت مقدار false را برمیگرداند.
console.log(gwen.hasOwnProperty('brainy')); // false
console.log(mammal.hasOwnProperty('brainy')); // true
مقدار دهی یا Assignment
به مثال زیر توجه کنید آیا به نظرتون مقدار پراپرتی human عوض میشود ویا پراپرتی جدیدی در gwen ایجاد میشود.
let human = {
teeth: 32
};
let gwen = {
__proto__: human,
// Note: no own teeth property
};
gwen.teeth = 31;//high
console.log(human.teeth); // ?
console.log(gwen.teeth); // ?
همانطور که قبلا اشاره کردیم مفهوم prototype صرفا به مفهوم جستوجوی جاوااسکریپت برای یافتن یک مقدار اشاره دارد و نه چیزی بیشتر پس با توجه به آنچه تا بحال آموختیم با عملیات انتصاب فقط پراپرتی جدید teeth در شیئ gwen ایجاد شده و به مقدار 31 متصل میشود.
The Object Prototype
به شیئ زیر توجه کنید، هیچ prototype ای برای آن تعریف نکردیم درسته؟
let obj = {};
حال قطعه کد زیر را در console مرورگر خود اجرا کنید.
let obj = {};
console.log(obj.__proto__); // Play with it!
با کمال تعجب مقدار __proto__ برابر undefined یا null نیست بلکه شامل مقادیر مختلفی مانند تابع toString و hasOwnProperty و ... است. با این اوصاف اونطور که ما تصور کردیم ما با استفاده از {} صرفا یک شیئ خالی ایجاد نکردیم بلکه جاوااسکریپت بصورت پیش فرض یک پراپرتی __proto__ تعریف کرده که به شیئ ای اشاره میکند که به آن The Object Prototype میگوییم.
برای ایجاد یک آبجکت بدون prototype پیش فرض زمان تعریف آن میتوان مقدار __proto__ را برابر null قرار داد.
let weirdo = {
__proto__: null
};
console.log(weirdo.hasOwnProperty); // undefined
console.log(weirdo.toString); // undefined
آلوده کردن prototype یا Polluting the Prototype
همانطور که تا الان متوجه شدیم تمام object ها بصورت پیش فرض شامل یک پراپرتی __proto__ هستن که به یک شیئ که نام آن را The Object Prototype گذاشتیم اشاره میکند.
به کد زیر توجه کنید.
let obj = {};
obj.__proto__.smell = 'banana';
با این کار ما تغییراتی در prototype پیش فرض ایجاد کردیم که prototype پیش فرض تمام object هایی بوده که تا بحال ساختهایم و که نتایج زیر را به دنبال دارد.
console.log(sherlock.smell); // "banana"
console.log(watson.smell); // "banana"
تغییر دادن prototype مشترک بین object های مختلف را آلوده کردن prototype یا Polluting the Prototype میگویند.
در گذشته برای افزودن ویژگیهای سفارشی جدید به جاوااسکریپت از این روش بسیار استفاده میشد ولی با گذشت سالها جامعه وب متوجه شدن که اینکار باعث ایجاد شکنندگی میشود و اضافه کردن قابلیتهای جدید زبان را دشوارتر میکند و ترجیح دادن از آن دوری کنند.
حالا شما میتونید معمای مثال اول را حل کرده و در console مرورگر نتایج را تست کنید.
__proto__ یا prototype
ممکن است شما عناوین صفحهی MDN را مشاهده کنید و برای شما سوال پیش بیاید که پراپرتی prototype چیست.
خبر بد: prototype تقریبا هیچ ربطی به مفهوم prototypes و __proto__ که تا بحال راجع به آن صحبت کردیم ندارد بلکه مرتبط با عملگر new در جاوااسکریپت است.
پس به یاد داشته باشید __proto__ همان نمونهی اولیه یا یک prototype از یک شیئ است.
در صورتی که پرارپتی prototype یک شیئ و عملگر new دو مفهوم کاملا مجزا هستند.