سلام دوستان. پروتوتایپ (Prototype) یکی از مهمترین مفهومها توی جاوااسکریت هست و اگه اون رو خوب و کامل درک کنیم، یک قدم بزرگ توی فهم این زبان جذاب برداشتیم. حتماً شنیدیم که جاوااسکریپت ویژگیها و رفتارهایی داره که اگه اون رو با بقیه زبانها مقایسه کنیم عجیب به نظر میان. اما نباید انتظار داشته باشیم هر زبانی دقیقاً رفتاری رو ارائه بده که توی زبانهای دیگه وجود داره. اگه اینطور بود، دلیلی برای معرفی شدن زبانهای مختلف وجود نداشت. همونطور که مقایسه زبانهای محاورهای کار بیمعنی هست و هر زبانی گرامر و ساختار خودش رو داره و ما هیچ وقت نمیتونیم با ذهنیت قبلی از یک زبان، یک زبان دیگه رو یاد بگیریم.
توی این قسمت میخوایم با مفهوم پروتوتایپ توی جاوااسکریپت آشنا بشیم.
توی این قسمت یاد میگیریم که:
پروتوتایپ یعنی چی؟ 🤔
معنی لغوی پروتوتایپ یعنی نمونهی اولیه. درک همین جمله خیلی میتونه کمککننده باشه. هر مقداری که توی جاوااسکریپت تعریف میکنیم، یک سری از ویژگیهاش رو از یک نمونهی اولیه به ارث میبره. این ویژگیها شامل متدها و پراپرتیهای مفید هستن که ما فکر میکنیم توی مقداری که ساختیم وجود دارن. در صورتی که موقع ساخته شدن، از یک والد به ارث برده میشن. مثل پراپرتی length توی رشتهها و آرایهها. این موضوع رو با مثال بهتر میشه توضیح داد. کد زیر رو در نظر بگیرین:
const person = { name: "Mario" } alert(person.toString()); // [object Object]
توی خط آخر ما از یک متد به اسم toString طوری استفاده کردیم که انگار توی آبجکت person از قبل تعریف شده. اما ظاهراً چنین متدی توی این آبجکت دیده نمیشه. پس این متد چطور در دسترس هست؟ 🤔 جواب پروتوتایپ یا نمونه اولیه هست.
وقتی مثل خط اول یک آبجکت داره ساخته میشه، یک سری از ویژگیها از آبجکت والد به ارث برده میشه. آبجکت والد برای person، یک آبجکت درونی جاوااسکریپت به اسم Object هست.
متد toString از کجا قابل دسترسی هست؟
وقتی از خود person یک console.log بگیریم چیزی جز { name: 'Mario' } نمیبینیم. کد زیر رو اجرا کنید و کنسول مرورگر رو ببینید:
const person = { name: "Mario" } console.log(person);
اما باید بدونیم که توی جاوااسکریپت هر آبجکتی یک پراپرتی مخفی داره به اسم [[Prototype]]. این پراپرتی به عنوان یک لینک یا واسط برای دسترسی به نمونهی والد در نظر گرفته میشه. برای اینکه پروتوتایپ یا نمونه اولیه یا به زبان سادهتر، والد آبجکت person رو ببینیم، از کد زیر استفاده میکنیم:
Object.getPrototypeOf(person); // or person.__proto__;
اگه خروجی کد بالا رو ببینیم کلی متد دیگه به همراه متد toString رو خواهیم دید:

پس متد toString از والدِ آبجکت person خونده میشه.
زنجیرهی پروتوتایپ چیه؟ 🤔
همونطور که گفته شد، توی جاوااسکریپت هر آبجکتی دارای یک پراپرتی مخفی به اسم [[Prototype]] هست که به عنوان یک لینک به آبجکت بالاتر (والد) عمل میکنه. اون آبجکت هم خودش ممکنه شامل یک پراپرتی prototype باشه که به یک آبجکت بالاتر اشاره میکنه. به این زنجیره، زنجیرهی پروتوتایپ گفته میشه.
میخوایم کدی بنویسیم که آبجکت person به عنوان پروتوتایپ (والد) برای یک آبجکت دیگه در نظر گرفته بشه:
const parent = { name: "Mario" } const child = Object.create(parent);
با متد Object.create میتونیم یک آبجکت با یک پروتوتایپ دلخواه درست کنیم. پروتوتایپ مد نظرمون رو باید به عنوان آرگومان اول به این متد پاس بدیم. وقتی یک عضو (پراپرتی یا متد) رو از یک آبجکت رو صدا میزنیم، جاوااسکریپت دنبال این عضو توی همون آبجکت میگرده. اگه نتونست پیدا کنه، توی پروتوتایپ دنبالش میگرده. و باز هم اگه نتونست پیدا کنه، جستجو توی زنجیرهٔ پروتوتایپ ادامه داره تا جایی که هیچ پروتوتایپی وجود نداشته باشه:
const parent = { name: "Mario" } const child = Object.create(parent); console.log(child); // {} alert(child.name); // Mario
همونطور که میبینیم، به name دسترسی داریم با اینکه این پراپرتی توی child وجود نداره.
توی کد بالا، پروتوتایپ آبجکت child، دقیقاً آبجکت parent هست. این رو میشه با کد زیر متوجه شد:
child.__proto__ === parent; // true
توی جاوااسکریپت وقتی دو تا آبجکت رو با === مقایسه میکنیم، نتیجه زمانی true هست که هر دو آبجکت رفرنسهای یکسانی توی حافظه داشته باشن. پس پروتوتایپ child صرفاً به آدرس parent اشاره میکنه و مقدار کپی شده نیست. این موضوع رو با این کد هم میشه متوجه شد:
const parent = { lastname: "Doe" } const child = Object.create(parent); alert(child.lastname); // Doe >> parent.lastname = 'Becker'; alert(child.lastname); // Becker
چطور بررسی کنیم یک پراپرتی متعلق به خود آبجکت هست؟
توی مثال بالا، پراپرتی lastname متعلق به پروتوتایپی هست که child اون رو به ارث برده. پس متعلق به child نیست. اما چطور متوجه بشیم lastname متعلق به child نیست و اون رو به ارث برده؟ با استفاده از متد hasOwnProperty که خروجی اون بولین هست:
const parent = { lastname: "Doe" } const child = Object.create(parent); alert(parent.hasOwnProperty('lastname')); // true alert(child.hasOwnProperty('lastname')); // false
همونطور که میبینیم، خروجی متد hasOwnProperty برای آبجکت child برابر با false هست. چون متد hasOwnProperty فقط توی خود آبجکت مورد نظر جستجو میکنه و این جستجو توی پروتوتایپها صورت نمیگیره. اگه بخوایم جستجو توی پروتوتایپها هم انجام بشه، از کد زیر استفاده میکنیم:
const parent = { lastname: "Becker" } const child = Object.create(parent); alert('lastname' in child); // true alert('toString' in child); // true alert('hasOwnProperty' in child); // true
برای این موضوع پیشنهاد میکنم سوال ۶۳ سوالات مصاحبه جاوااسکریپت رو بخونین.
خب دوستان توی این قسمت به طور کلی با مفهوم پروتوتایپها آشنا شدیم. توی قسمت بعدی با جزئیات بیشتری دیگه از پروتوتایپ آشنا میشیم. روزتون خوش ✌️😉