سلام دوستان. توی قسمت قبل با مفهوم پروتوتایپ آشنا شدیم و دیدیم که پروتوتایپ‌ها می‌تونن به صورت زنجیر به هم وصل باشن. توی این قسمت می‌خوایم پروتوتایپ‌ها رو یکم بیشتر موشکافی کنیم.

می‌دونیم که هر مقداری که ساخته میشه، یک سری ویژگی‌ها رو از یک آبجکت والد به ارث می‌بره و دیدیم که پروتوتایپ برای یک آبجکت معمولی، کلاس Object هست که این رو میشه با متد getPrototypeOf یا پراپرتی مخفی __proto__ متوجه شد:

const person = {}

person.__proto__; // Object

 

زنجیره پروتوتایپ جایی متوقف میشه که پروتوتایپ یک مقدار (مقدار پراپرتی [[prototype]]) برابر با null باشه:

const person = {}

person.__proto__; // Object
person.__proto__.__proto__; // null

طول زنجیره برای یک آبجکت معمولی 1 هست. اما بیاید طول زنجیره برای رشته‌ها، آرایه‌ها و اعداد رو بررسی کنیم:

const str = "Discover";

str.__proto__ // String
str.__proto__.__proto__; // Object
str.__proto__.__proto__.__proto__; // null
const arr = [];

arr.__proto__ // Array
arr.__proto__.__proto__; // Object
arr.__proto__.__proto__.__proto__; // null
const num = 429;

num.__proto__ // Number
num.__proto__.__proto__; // Object
num.__proto__.__proto__.__proto__; // null

جالب اینجاست که پروتوتایپ آبجکت‌های Array، String و Number، آبجکتِ Object هست و برای همین طول زنجیره برای این کلاس‌ها ۲ هست.

نکته: احتمالاً این رو شنیدیم که همه چیز توی جاوااسکریپت آبجکت هست و شاید با دیدن کدهای بالا به‌خصوص مثال رشته و اعداد، به این نتیجه برسیم که اعداد و رشته‌ها هم نوعی آبجکت هستن. اما اینطور نیست. رشته‌ها و اعداد جز نوع‌های داده‌ای Primitive هستن. یعنی نوع یک رشته واقعا رشته هست و نوع یک عدد واقعا عدد هست. اما چطور برای یک نوع Primitive مثلا رشته، پراپرتی length در دسترس هست؟

const message = "Meow";

message.length; //4

جواب، Wrapper Objects هست. توی مثال بالا وقتی می‌خوایم به یک پراپرتی از رشته (message) دسترسی پیدا کنیم، رشته توی یک آبجکت موقت قرار می‌گیره تا چیزی که نیاز داریم رو ارائه بده. به این آبجکت موقت می‌گن Wrapper. برای مثال آبجکت Wrapper برای یک رشته String و برای اعداد Number هست.

برای درک بهتر، رو ببینید.

برای مطالبی که توی ادامه میاد لازمه اطلاعاتی از Constructor Functions داشته باشیم. هر چند اونها رو اینجا بررسی کردیم، خوندن ادامه مطالب اختیاری هست.

 

توابع سازنده و پروتوتایپ اونها

توابع سازنده یا Constructor Functions به توابعی گفته میشه که وقتی اونها رو با کلمه‌کلیدی new استفاده می‌کنیم به ما یک آبجکت از نوع خود تابع می‌گردونن. این توابع بیشتر نقش کلاس‌ها که توی شی‌گرایی داریم رو ایفا می‌کنن و با new گرفتن از اونها می‌تونیم یک نمونه از کلاس داشته باشیم. توی کد زیر تابع Person یک تابع سازنده هست:

function Person(name) {
  this.name = name;

  this.run = function () {
    alert("I'm running!");
  }
}

const jack = new Person('Mario');

jack.name; // Mario
jack.run(); // I'm running

اگه از خود تابع Person یک console.log بگیریم:

می‌بینیم که این تابع یک پراپرتی داره به اسم prototype. آیا این همون پروتوتایپی هست که با __proto__ یا getPrototypeOf به اون دسترسی داشتیم؟ جواب false هست:

Person.prototype === Person.__proto__; // false

هرچند این نام‌گذاری‌ها ممکنه گیج کننده باشه، باید بدونیم که پراپرتی prototype توی توابع سازنده با قضیه پروتوتایپی که تا الان بررسی کردیم فرق می‌کنه و فقط یک تشابه اسمی هست.

وقتی از یک تابع سازنده new می‌گیریم، پروتوتایپی که برای آبجکت جدید در نظر گرفته میشه، برابر با پراپرتی prototype تابع سازنده هست. به بیان ساده‌تر، مقدار پراپرتی prototype تابع سازنده، در نظر گرفته میشه برای پروتوتایپ نمونه‌ها:

function Person(name) {
  this.name = name;
}

const emily = new Person('Emily');

alert(<<emily.__proto__ === Person.prototype>>); // true

می‌تونیم به پراپرتی prototype توابع سازنده عضو اضافه کنیم تا توی نمونه‌ها در دسترس باشن:

function Person (name) {
  this.name = name;
}

Person.prototype.role = 'admin';

const emily = new Person('Emily');

alert(emily.role); // admin

 

همونطور که گفته شد، محتویات پراپرتی prototype یک تابع سازنده، به عنوان پروتوتایپ درنظر گرفته میشه برای نمونه‌ها. بررسی کنیم که این محتویات چی هستن:

function Person (name) {
  this.name = name
}

console.log(Person.prototype) // { constructor: Person }

کد بالا رو اجرا کنین و کنسول رو ببینین. یک آبجکت با پراپرتی constructor می‌بینیم که مقدار اون خود تابع هست:

function Person (name) {
  this.name = name;
}

alert(Person.prototype.constructor === Person); // true

پس پراپرتی constructor توی نمونه‌ها هم در دسترس هست و مقدار اون، تابع سازنده‌ی نمونه هست:

function Person (name) {
  this.name = name;
}

const emily = new Person('Emily');

alert(emily.constructor === Person); // true

و چون به این صورت به تابع سازنده دسترسی داریم، می‌تونیم از اون دوباره نمونه بسازیم:

function Person (name) {
  this.name = name;
}

const emily = new Person('Emily');
const mario = <<new emily.constructor>>;

alert(mario.constructor === Person); // true

این برای زمانی خوبه که می‌خوایم یک نمونه دیگه بسازیم ولی نمی‌دونیم تابع سازنده‌ی اون نمونه چی هست.

نکته: باید بدونیم که برای دسترسی به پروتوتایپ یک متغیر یا تغییر دادن اون، استفاده از __proto__ منسوخ شده و باید از روش‌های جدید یعنی Object.getPrototypeOf یا Object.setPrototypeOf استفاده کنیم:

const obj1 = { name: "Mario" }
const obj2 = {}

// setting protoype for obj2
Object.setPrototypeOf(obj2, obj1);

alert(obj2.name); // Mario
alert(Object.getPrototypeOf(obj2) === obj1) // true

 

خب دوستان این قسمت هم به پایان رسید و امیدوارم این موضوع مهم رو به خوبی درک کرده باشین. امیدوارم مثل جاوااسکریپت تو اوج باشین :)) روزتون خوش 😉 🖐️

برای این قسمت از اطلاعات شخصی + این منبع استفاده کردم:

 

javascript.info/function-prototype