برنامهنویسی شیءگرا یک روش برنامه نویسی است که در نگاه اول شاید یک مبحث سخت و پیچیده به نظر برسه ولی اگه مباحث پایهای برنامهنویسی شیءگرا رو بلد باشیم، این روش خیلی ساده میشه برامون و میتونیم از اون در ساخت برنامههامون استفاده. با جاوااسکریپت هم میتونیم به صورت شیءگرا برنامه نویسی کنیم. به شرطی که مباحث سادهی پایهای رو به درستی درک کنیم. یکی از اون مباحث پایهای، مفهوم «function constructor» است که تو این پست قراره درباهش بحث کنیم.
قبل از هر چیزی بهتره اول از خود آبجکتها شروع کنیم. وقتی یه سری اطلاعات مرتبط به هم رو داریم، بهترین راه برای ذخیره کردن اونها، استفاده از آبجکته. برای مثال ما یه سری از اطلاعات شخصی (نام و سال تولد) یه بازیگر سینمایی رو داریم و میخواهیم اونها رو تو یه آبجکت ذخیره کنیم. همون طور که میدونین آبجکت در جاوااسکریپت به صورت زیر تعریف میشه.
const actor = { fullName: "Leonardo DiCaprio", yearOfBorn: 1974, calcAge() { let date, currentYear; date = new Date(); currentYear = date.getFullYear(); return currentYear - this.yearOfBorn; } };
تو این آبجکت ساده، نام و سال تولد بازیگر ذخیره شده. همچنین این آبجکت یه متد هم داره که سن بازیگر رو با استفاده از سال تولدش محاسبه میکنه.
حالا اگه قصد داشته باشیم اطلاعات یه بازیگر دیگه رو تو یه آبجکت ذخیره کنیم، چه کار باید بکنیم؟
خب معلومه، یه آبجکت دیگه به اسم actor2 درست میکنیم. برای اینکه تو زمان هم صرفهجویی کنیم، آبجکت اول رو کپی و بعد پیست میکنیم و فقط نام و سن اون رو تغیر میدیم و با خودمون فکر میکنیم چقدر خفنیم ما!
const actor2 = { fullName: "Tom Hanks", yearOfBorn: 1956, calcAge() { let date, currentYear; date = new Date(); currentYear = date.getFullYear(); return currentYear - this.yearOfBorn; } };
ولی اگه نیاز داشته باشیم اطلاعات سه بازیگر رو ذخیره کنیم چی؟
قبل از اینکه دربارهی جواب سوال بالا فکر کنیم، این نکته رو گوشهی ذهنمون داشته باشیم که شاید لازم باشه اطلاعات 1000 بازیگر رو به صورت آبجکت ذخیره کنیم. با فکر کردن به این نکته، نتیجه میگیریم که روش کپی پیست کردن آبجکتها آنچنان هم عاقلانه نیست. چون برنامه نویسی یعنی انجام کار کمتر با حذف کارهای تکراری. درضمن باید به این نکته هم توجه کنیم که در مثال فعلی با آبجکتهایی که فقط یک متد چند خطی که برای محاسبهی سن بازیگر کاربرد دارن سروکار داریم. اگه در شرایطی قرار بگیریم که هر آبجکت چندین متد چند صد خطی داشته باشه، با کپی پیست آبجکتها، چند صد خط تکراری به برنامهی خودمون اضافه میکنیم که حجم کدمون رو زیاد و حافظهی دستگاه رو اشغال میکنن.
درست در همین لحظه است که «function constructor» یا «تابع سازنده» در مقام یک قهرمان وارد بازی میشه و کاری میکنه که ما باز هم احساس خفن بودن بکنیم!
وقتی که ناامیدانه نشستیم و منتظریم قهوهمون سرد بشه، تابع سازنده مثل یه دوست قدیمی میاد و میزنه رو شونهمون و میگه: «آخه دختر/ پسر خوب اگه یکم بیشتر دقت کنی میبینی که همهی آبجکتها تو ویژگیهاشون مشترکن و چیزی که اونها رو متمایز میکنه، مقدار ویژگیهاشونه. هر دو آبجکت قبلی پراپرتیهای fullName و yearOfBorn رو دارن ولی مقدراشون با هم فرق میکنه.»
پس برای برای ساخت یه آبجکت، به جای اینکه کلا اونا رو از اول بنویسیم، یه قالب اولیه (prototype) با کمک تابع سازنده درست میکنیم و بعد از اون فقط مقادیر رو عوض میکنیم تا یه آبجکت جدید ساخته بشه. (دقیقا مثل قالب کیک)
تابع سازنده چیزی نیست جز یه فانکشن ساده که قراره قالب اولیهی (prototype) آبجکتهایی باشه که از این به بعد بسازیم:
function Actor (){ };
برای اینکه تابع سازنده از فانکشنهای دیگه متمایز باشه، حرف اول اون رو به صورت بزرگ مینویسیم.
با توجه به مثال قبلی آبجکتهایی که قراره ساخته بشن فقط دو تا ویژگی به عنوان اطلاعات اولیه دارن؛ یکی اسمشون (fullName) و اون یکی تاریخ تولدشون (yearOfBorn). پس فقط دو تا پارامتر ورودی به تابع سازنده اضافه میکنیم.
function Actor (fullName, yearOfBorn){ };
در قدم بعدی از this keyword استفاده میکنیم. اگه دوست دارین دربارهی this بیشتر بدونین، پست زیر رو حتما بخونین.
با کمک this میتونیم مقادیر مختلفی رو به هر یک از آبجکتهایی که قراره از روی نمونهی اولیه (prototype) یا همون تابع سازنده خودمون ساخته بشن، اعمال کنیم.
function Actor(fullName, yearOfBorn) { this.fullName = fullName; this.yearOfBorn = yearOfBorn; this.calcAge = function (){ let date, currentYear; date = new Date(); currentYear = date.getFullYear(); return currentYear - this.yearOfBorn; } };
برای آخرین مرحله از ساخت آبجکت به وسیله تابع سازنده، فقط کافیه با استفاده از new keyword تابع سازنده رو به همراه آرگومانهای ورودی فراخوانی و اون رو تو یه متغیر ذخیره کنیم.
const leonardo = new Actor("Leonardo DiCaprio", 1974);
تو این مرحله new keyword سه کار انجام میده:
1- یک آبجکت جدید و خالی({}) میسازه.
2- دومین کاری که میکنه اینکه با استفاده از فانکشن فراخوانی شدهی بعد خودش، thisها رو بر روی آبجکت جدیدی که در مرحلهی اول ساخته، ست میکنه.
مقدار آرگومان اول تابع Actor برابره با «Leonardo DiCaprio». این مقدار به عنوان اولین پارامتر ورودی (fullName) به تابع سازنده پاس داده میده و داخل تابع، this.fullName برابر با مقدار اولین پارامتر ورودی (Leonardo DiCaprio) میشه.
یعنی this.fullName بعد از ست شدن، به عنوان یکی از ویژگیهای (پراپرتی) آبجکت جدید به حساب میاد و مقداری هم که جلوی this.fullName قرار گرفته، مقدار اون ویژگی قرار میگیره.
همچنین این روال برای متغیرها و فانکشنهای دیگه هم تکرار میشه و به این ترتیب this.fullName و this.yearOfBorn به عنوان پراپرتیها و this.calcAge هم به عنوان متد آبجکت جدید به حساب میان.
3- آبجکت جدیدی رو که ساخته و پراپرتیها و متدها رو براشون اعمال کرده، برگشت (return) میده.
بعد اینکه new کارش رو تموم کرد، مقدار برگشت داده شده داخل متغیر leonardo ذخیره میکنیم تا هر وقت خواستیم از اون استفاده کنیم.
البته برای محاسبهی سن هر آبجکت باید متد اون رو فراخوانی کنیم.
let leonardoAge = leonardo.calcAge() console.log(leonardoAge);
الان میتونیم به راحتی آبجکتهای جدید دیگهای رو بدون کپی کردن قسمتهای تکراری کدمون درست کنیم.
Const tom = new Person("Tom Hanks", 1956); const ralph = new Actor("Ralph Fiennes", 1962); Const johnny = new Person("Johnny Depp", 1963); Const brad = new Person("Brad Pitt", 1963); Const cillian = new Person("Cillian Murphy", 1976);
در جاوااسکریپت هر آبجکت چندین پراپرتی و متد پیشفرض داره. constructor یکی از اون پراپرتیها است. این پراپرتی، تابع سازندهی آبجکت رو مشخص میکنه.
function Actor(fullName, yearOfBorn) { this.fullName = fullName; this.yearOfBorn = yearOfBorn; this.calcAge = function() { let date, currentYear; date = new Date(); currentYear = date.getFullYear(); return currentYear - this.yearOfBorn; } }; const leonardo = new Actor("Leonardo DiCaprio", 1974); console.log(ralph.constructor); // return Actor() const ralph = new Actor("Ralph Fiennes", 1962); console.log(ralph.constructor); // return Actor() Const tom = new Person("Tom Hanks", 1956); console.log(tom.constructor); // return Actor()
همون طور که میبینیم، obj.constructor برای همهی آبجکتهایی که از روی تابع سازنده Actor ساخته شدن، مقدار ()Actor رو برگشت میده.
بیاین بحثمون رو با یه مثال سادهی دیگه ادامه بدیم. این بار قصد داریم اطلاعات یه ورزشکار رو در قالب آبجکت ذخیره کنیم. البته این بار، آبجکت رو با همون روش اولیه ایجاد میکنیم و نیازی به تابع سازنده نیست.
const athlete= { fullName: "Roger Federer", yearOfBorn: 1981 };
با پراپرتی constructor که چند خط بالاتر آشنا شدیم. الان قصد داریم دوباره از این پراپرتی استفاده کنیم و از کنسول مرورگر کروم کمک بگیریم تا ببینیم که آیا این آبجکتی که با روش معمول درست کردیم، تابع سازنده یا نمونهی اولیه داره یا نه؟
const athlete= { fullName: "Roger Federer", yearOfBorn: 1981 }; athlete.constructor // return Object()
همون طور که میبینیم یه فانکشن به عنوان تابع سازنده (prototype) آبجکت athlete برگشت داده شده. ولی ما هیچ تابع سازندهای به اسم Object تو کدی که نوشته بودیم، نداشتیم. پس اون از کجا اومده؟
اگه دقت کنین، توی اون تابع سازنده نوشته شده: «native code». یعنی ()Object به صورت خودکار در ساختار زبان جاوااسکریپت وجود داره. یعنی وقتی که ما آبجکت Athlete رو به شکل {} = obj تعریف میکنیم، موتور جاوااسکریپت موقع خوندن کدمون اون رو به صورت زیر تغیر میده تا قابل تفسیر باشه:
const athlete = new Object({ fullName: "Roger Federer", yearOfBorn: 1981 });
پس وقتی در ظاهر یک آبجکت رو به سادگی به صورت {} = obj تعریف میکنیم در باطن به صورت کد بالا تفسیر میشه.
البته توابع سازندهای که در ساختار جاوااسکریپت وجود دارن، محدود به ()Object نیست.
برای مثال یه متغیر به صورت string تعرف میکنیم.
const language = "javascript"
پراپرتی constructor رو علاوه بر آبجکتها، برای دیتا تایپهای دیگه هم میتونیم بهکار ببریم. پس بیاین امتحان کنیم تا ببینیم به چی میرسیم.
const language = "javascript" language.constructor;
این بار برای متغیر language یه تابع سازنده به نام ()String برگشت داده. پس مثل آبجکت، string هم با استفاده از یه تابع سازندهی نیتیو ساخته میشه. درواقع کد بالا هنگام تفسیر به شکل زیر تغیر میکنه.
const language = new String("javascript");
البته دیتا تایپهای از نوع array ،boolean ،number و function هم با این روش ساخته میشن و هر کدوم از این متغیرها، به صورت پیشفرض توابع سازندهی خودشون رو دارن.
const number = 1; // const number = new Number(1); const arrNums = [1, 2, 3]; // const arrNums = new Array([1, 2, 3]); const isAlive = true; // const isAlive = new Boolean(true); const sayHello = function(message) {console.log(message);}; //const sayHello = new Function("message", "console.log(message);");
پس تا اینجا فهمیدیم که علاوه بر آبجکتها، دیتا تایپهای ذکر شده هم برای خودشون توابع سازندهی مخصوصی دارن.
حالا دیگه وقتش رسیده قدم آخر رو برداریم تا با یک واقعیت روبه رو بشیم. اون واقعیت هم یک جمله است که گفته میشه: «همه چی تو جاوااسکریپت یه آبجکته».
این بار قراره یه آرایهی خالی بسازیم.
const arr = [];
متغیر arr رو تو کنسول مرورگر مینویسیم تا تابع سازندهی اون رو پیدا کنیم.
در مرحلهی اول به تابع سازندهی ()Array میرسیم که قبلا دربارهی اون بحث کردیم.
اگه روی ()Array کلیک کنیم، یه سری پراپرتی و متد میبینیم که مخصوص آرایهها هستن. اگه اسکرول بکنیم و به پایین اون لیست برسیم، میبینیم که ()Object رو به عنوان تابع سازندهی ()Array معرفی کرده. یعنی خود ()Array که به عنوان تابع سازندهی آرایهها شناخته میشه از یک تابع سازندهی دیگه به اسم ()Object ساخته شده.
بقیهی توابع سازنده هم مثل ()Array، از یک تابع سازندهی بزرگتر به اسم ()Object ساخته شدن که معمولا در اصطلاح به اون آبجکت ریشه (root Object) گفته میشه.
پس عکس قبلی رو بهتره به صورت زیر تغیر بدیم.
چون همهی توابع سازنده در نهایت به root Object ختم میشن، به همین دلیله که گفته میشه: «همه چی در جاوااسکریپت یه آبجکته.»
و ما الان دلیل این جمله رو میدونیم.
الان فقط یه نکته مونده که اون رو هم بررسی میکنیم و بحثمون تموم میشه.
برگردیم به آبجکتهایی که حاوی اطلاعات بازیگران بودن. یه آبجکتی داشتیم که تو متغیر leonardo ذخیره شده بود. اون آبجکت به کمک یه تابع سازنده به اسم ()Actor ساخته شده بود. قبلا گفتیم که تابع سازندهای که ساختیم فقط یه فانکشن ساده است. این نکته رو هم گفتیم که فانکشنها با استفاده از تابع سازندهی پیشفرض ()Function که در ساختار زبان جاوااسکریپت وجود داره، ساخته میشن و خود اون تابع سازنده با کمک root Object ساخته میشه. پس وقتی آبجکت leonardo رو میسازیم به ترتیب عکس زیر یه سری کارها انجام میشه.
آبجکت ریشه و تابع سازندهی ()Function که به صورت پیشفرض وجود دارن. تابع سازندهی ()Actor با کمک نمونهی اولیهی خودش که تابع سازندهی ()Function است ساخته میشه و آبجکت leonardo از روی تابع سازندهی ()Actor ساخته میشه.
پس دو نوع تابع سازنده داریم:
1- توابع سازندهی پیشفرض جاوااسکریپت. (مثل ()Function و ()Array و ...)
2- توابع سازندهای که خودمون میتونیم درست کنیم. (مثل ()Actor)