Sam Aghapour
Sam Aghapour
خواندن ۱۱ دقیقه·۳ سال پیش

صفر تا صد oop در جاوااسکریپت! قسمت اول: prototype

سلام به جی اس کارای عزیز ?


وقتی جاوااسکریپت یاد میگرفتم همیشه برام سوال بود که وقتی ما یه استرینج یا فانکشن یا ... هرچی تعریف میکنیم یه سری متد خاص برا خودشون دارن برای مثال وقتی یه متغیر با تایپ استرینج تعریف میکنیم میتونیم روش از متد search یا length و.. استفاده بکنیم ولی خب چطورر؟؟ وقتی دلیل این اتفاقا رو تو پشت پرده میبینیم تازه کم کم بخش بزرگی از مفاهیم oop در جاوااسکریپت هم برامون جا میفته و تازه میفهمیم چه خبره.

پس من توی دو بخش جدا اول به پروتوتایپ ها و بعد به چهار مفهوم مهم توی oop با کلی مثال از دنیای کد و دنیای واقعی میپردازم تا یه بار برای همیشه یادشون بگیریم و از اونجایی که این مباحث هم توی مصاحبه ها هم توی پروژه ها و موقعیت های مختلف خیلی مهمن دیگه نگرانی بابتشون نداشته باشیم.


مفهوم __proto__ و prototype

جاوااسکریپت یه زبان بر پایه آبجکت و پروتوتایپ هستش و برخلاف خیلی از زبان ها بر پایه کلاس نیستش، همین قضیه باعث شده یکم بحث چگونگی ساخته شدن سلسله آبجکت ها و وراثت از هم دیگه یکم پیچیده و عجیب غریب به نظر بیاد. خب اما اصلا پروتوتایپ چی هست؟؟! بزارید با یه مثال شروع کنیم

توی عکس بالا ما یه آبجکت ساده ساختیم و سه تا از پراپرتی هاشو صدا زدیم، امااا ما فقط name و age رو تعریف کردیم! پس متدِ hasOwnProperty از کجا اومد؟ (به پراپرتی آبجکت که تایپ فانکشن هستش میگن متد) ما که همچین چیزی تعریف نکردیم ? هوممم.. چطوره بیایم و کل آبجکت رو کنسول لاگ بگیریم ببینیم چیا داره داخلش داره.

console.log(myobject)
console.log(myobject)


خب خب اینجاس که میفهمیم جاوااسکریپت یه چیزیو خودش به صورت پیش فرض هر دفعه که یه آبجکت میسازیم میزاره توش و اون چیزی نیست جز __proto__ خب حالا بیاید ایندفعه این پرارپتی پنهان رو لاگ بگیریم و بازش کنیم ببینیم چیا داره؟

console.log(myobject.__proto__)
console.log(myobject.__proto__)


اینجاس که متد hasOwnProperty و همینطور کلی متد جالب دیگه پیدا میکنیم و جالبیش اینجاس که اگه ما هزار تا هم آبجکت بسازیم قراره همشون این متدا و به بیان دیگه این آبجکت پروتو که global object prototype هم گفته میشه و شامل کلی متد هستش رو داشته باشن و جالب ترش اینه که قرار نیست برای هر ابجکت یه دونه از این ابجکتا ساخته شه و پدر حافظه رو در بیاره! بلکه همشون از یه آبجکت که این متدا رو داره به ارث میبرن و همشون از یه ابجکت استفاده میکنن و اخرین نکته جالبی که تو عکس بالا میبینیم اینه که حتی این آبجکت که خودش پپروتو هستش هم یه پراپرتی پروتو دیگه داره که به بن بست رسیده و مقدارش null هستش ولی اصل مسله همینه که هرابجکتی یه پروتو داره حتی خود ابجکت پروتوتایپ. کلا جالبیت از سر و روی جاوااسکریپت میباره :)
بریم به چند روش مختلف موشکافی کنیم ای پروتو خان رو :

با استفاده از Object literal و متد های object.create و object.setPrototypeOf


1.استفاده از متد object.create**

object.create()
object.create()

خب توی مثال بالا ما یه آبجکت car با سه تا پراپرتی با تایپ فانکشن یا متد داریم و نه هیچ چیز دیگه

حالا وقتی میایم از object.create استفاده میکنیم چه اتفاقی میفته؟ ما پارامتر اول رو برابر با car قرار دادیم و پارامتر دوم یه ابجکت که پراپرتی name داره و مقدارش tesla هست ( داخل این متد به پراپرتی ابجکت به همین شکل مقدار داده میشه در غیر اینصورت نیازی نبود پراپرتی برابر با یه ابجکت دیگه که مقدار value داره باشه خلاصه زیاد درگیر اون قسمت نشید) پس متغیر tesla برابر با چی میشه ؟ بیاید ببینیم

console.log(tesla)
console.log(tesla)

خب پس با توجه به عکس بالا میبینیم که با object.create ما میایم با استفاده از پارامتر دوم ابجکتی که میخوایم رو با پراپرتی های اختصاصی خودش میسازیم و پروتو ی این ابجکت رو با استفاده از پارامتر اول از ابجکت دیگه ای ( که توی این مثالcar هستش) کپی میکنه و اینطوری میشه که پروتو برابر میشه با پراپرتی های داخل ابجکت car و یه جورایی به ارث میبره اون پراپرتی هارو و اما این ابجکت به ارث برده شده هم خودش همونطور که بالاتر گفتیم یه پروتو داره و این پروتو به global object protoype میرسه.

زنجیره پروتوتایپ یا object prototype chaining

کلا یادتون باشه این پروتوتایپ ها مثل زنجیره به هم وصل هستن و هر چقدر هم این پروتو متصل باشه به یه ابجکت دیگه در آخر میخوره به global object prototype و بعد از اخرین پروتو میخوره به null به این قضیه میگن prototype chaining یا زنجیره پروتوتایپ.

خیله خب حالا ما دوتا متغیر دیگه هم داریم که یکی یکی بررسی میکنیم، متغیر detail رو اول چک میکنیم که برابر با یه ابجکت با پراپرتی اختصاصی speed هستش با مقدار 3001 و پروتوی این ابجکت رفرنس داده شه به متغیر tesla و تسلا هم همونطور که بالاتر دیدیم پروتوش رفرنس داده شده به car پس فک کنم میدونیم قراره چی باشه نتیجه :

console.log(details)
console.log(details)

و بله به این میگن زنجیره پروتوتایپ: متغیر detail یه پراپرتی داره اما از طریق پروتوش پراپرتی name از تسلا و از طریق پروتوی تسلا به متدای sayname , start , stop از ابجکت car و از طریق پروتوی car به تموم متدای ابجکت گلوبال دسترسی داره.

حالا Prototypal delegation چیه؟

اگه دقت کنید توی مثالای بالا پروتوتایپ ها از پایین به بالا وصلن ، ینی چی ؟ ینی اینکه متغیر detail به پراپرتی name توی تسلا دسترسی داره چون پروتوش به تسلا وصله اما تسلا به پراپرتی speed توی detail دسترسی نداره چون پروتوش به detail وصل نیست و به یه چی دیگه وصله ،به این قضیه میگن Prototypal delegation. پس ینی اگه الان مثلا لاگ بگیریم console.log(tesla.speed) مقدار رو اول توی خود ابجکت میگرده و وقتی پیداش نکنه میره سراغ پروتوتایپش که car هستش و وقتی اونجا پیداش نکنه میره سراغ پروتوتایپ car که همون global object prototype هست و اونجا هم چیزی به اسم speed پیدا نمیکنه و میره سراغ پروتوی بعدی و میبینه که اوپس این پروتو دیگه بن بسته و null هستش و در نتیجه undefined برمیگردونه

دلیل ارور خیلی رو مخی به اسم is not a function

با توجه به چیزی که بالاتر گفتم، جاوااسکریپت وقتی یه پراپرتی رو توی زنجیره پروتو تایپ پیدا نکنه اندیفایند برمیگردونه حالا اگه speed بجای استرینج یه فانکشن یا همون متد بود در صورت پیدا نکردن متد بهمون ارور میداد که tesla.speed is not a function

پس الان میدونیم که چی میشه به همچین خطایی میخوریم مثلا بعضی وقتا شده روی آرایه یا هر تایپ دیگه ای یه متدی استفاده کنیم که به این ارور بخوریم اون بخاطر اینه که همچین متدی توی زنجیره پروتوتایپیه اون تایپ متغیر وجود نداره


بریم سراغ سومین متغیر که volvo باشه ، خب این متغیر دقیقا مثل متغیر تسلا نوشته شده و قراره پراپرتی اختصاصی خودش با مقدار اختصاصیش رو داشته باشه و پروتوش به car رفرنس داده بشه و وقتی لاگ بگیریم نتیجه اینطوریه:

console.log(volvo)
console.log(volvo)


2.استفاده از متد object.setPrototypeOf **

سه تا متغیری که بالا تر ساختیم رو میتونیم با متد object.setPrototypeOf به شکل زیر هم بنویسیم:

object.setPrototypeOf()
object.setPrototypeOf()

توی عکس بالا ما هر متغیر رو برابر با ابجکت با پراپرتی های اختصاصی خودش و مقدار اختصاصی خودش قرار میدیم و پایینش با استفاده از متد object.setPrototypeOf میایم از طریق پارامتر اول ابجکتی که میخوایم بهش پروتوتایپ خاصی بدیم رو انتخاب میکنیم و از طریق پارامتر دوم پروتوی اون ابجکت رو رفرنس میدیم به یه ابجکت دیگه، نتایج رو اگه لاگ بگیریم دقیقا همون نتایجیه که بالاتر دیدیم.


با استفاده از constructor function و کلمه کلیدی new

constructor function
constructor function


فانکشن هم functoin است هم object!

فانکشن ها توی جاوااسکریپت مثل آبجکت میتونند رفتار کنند همونطور که تو عکس بالا هم میبینید میتونن پراپرتی بگیرن و وقتی تعریف میشن جاوااسکریپت به صورت پیشفرض کلی متد و پراپرتی بهشون اضافه میکنه ، حالا بیاید عکس بالا رو موشکافی کنیم:

وقتی یه فانکشن داخلش از this.** استفاده شده و اسم این فانکشن هم به صورت capitalize (حرف اول بزرگ) است و اینطوری داریم میگیم که این فانکشن در اصل یه آبجکت هستش و میشه ازش نمونه سازی کرد و وسیله ای که باهاش نمونه سازی میکنیم هم همون کلمه کلیدی new هستش. درواقع داخل فانکشن وقتی از this استفاده میکنیم داریم میگیم که جاوااسکریپت عزیز هر وقت یه نمونه از این فانکشن ساخته شد پراپرتی فلان رو (که توی این مثال name) هستش بده به این نمونه و مقدارشم برابر با پارامتر فلان (تو این مثال name) بکن. پس وقتی یه متغیر tesla مینویسیم که که با new از Car نمونه میسازه درواقع انگار داریم یه ابجکت میسازیم که پراپرتی name برابر با tesla داره و برای volvo هم همینطور. به این نوع فانکشن ها ( که تو این مثال Car هستش) ، constructor function گفته میشه.

حالا یه چیزی که این وسط هست اون پروتوتایپ ها هستن! ما در واقع طبق عکس بالا به آبجکت Car یه سری متد ها به پروتوتایپش اضافه میکنیم و وقتی یه نمونه از این ابجکتمون رو مثلا با اسم تسلا میسازیم و تسلا رو لاگ میگیریم این میشه نتیجش:

console.log(tesla)
console.log(tesla)

خب ما از نتیجه میتونیم اینو بفهمیم که وقتی یه نمونه ساختیم در اصل یه آبجکت ساختیم با پراپرتی name که مقدارشم قرار دادیم با tesla حالا اون متدهایی که به پروتوتایپ ابجکتِ Car داده بودیم همشون جمع شدن و به طور یه ابجکت داده شدن به پروتوتایپه هر نمونه ای که از car ساخته میشه و پروتوی ابجکت متدهای اختصاصیمون هم رفرنس میخوره به global object prototype طبق معمول. برای volvo هم دقیقا همین پروسه انجام میشه.

کلمه new توی عکس بالا:

  1. یه ابجکت میسازه با پراپرتی هایی که توی Car هست و this رو رفرنس میده به ابجکت جدیدی که ساخته
  2. پروتوی این ابجکت رو رفرنس میده به پروتوتایپ Car

و حالا میرسیم به نکته جالبش که ما کاربرد اصلی پروتوتایپ رو هم نشون میده :

ما اگه بخوایم مثلا صدتا متغیر تعریف کنیم که همشون متدهای start , stop و... داشته باشن که عملکرد همه ی این متدها هم یکیه بنظرتون منطقیه که بیایم همه ی این آبجکت هارو یکی یکی تعریف کنیم و برای هرکدوم همه ی این فانکشن هارو یکی یکی جدا جدا تعریف کنیم؟ خب معلومه که نه!! اینطوری که خااارِ حافظه تار و مار میشه پس میایم مثل روش بالا یه constructor function مینویسیم و میگیم که هرکی خواست یه نمونه از این فانکشن بسازه نیازی نیست هزار بار پراپرتی اسم رو تعریف کنه من تعریفش میکنم و نمونه ها فقط مقدارشو بدن و علاوه بر اون نیازی نیست تک تک این متد هارو براشون بنویسی من توی پروتوتایپم تعریفشون میکنم و هر نمونه ای که ازم ساخته شد اینارو به ارث ببره و از همین جا استفاده کنه و این یعنی جلوگیری از نوشته شدن تکراریه هزاران متد و پراپرتی و نجاتِ حافظه و پرفورمنس حالا بیاید نتیجه چیزی که گفتیم رو تو عکس زیر ببینیم:

new Car('name')
new Car('name')

خب طبق عکس بالا teslaو volvo با اینکه هیچوقت متد های استارت و استاپ و .. براشون به صورت جدا جدا نوشته نشد هر دوشون دسترسی دارن به این متدها و یه چیزه دیگه اینکه اگه اخری رو ببینید اومدم noneFunction رو صدا زدم و اینو چون نه من توی پروتوتایپ تعریف کردم و نه توی global object prototype همچین چیزی هست هر ابجکتی رو توی زنجیره پروتوتایپ ها گشته پیدا نکرده و در آخر این ارور رو برگردونده.

و بله جاوااسکریپت برای ابجکتای خودشم یه global object prototype ساخته تا همشون از همین به ارث ببرن وگرنه که دیگه میترکید بخواد برای هر ابجکتی که تو پروژه نوشته میشه کل اون متدهارو تعریف کنه!

فرق __proto__ و prototype ؟

خب شاید این سوال براتون یپش بیاد که فرق این دو چیه؟
وقتی یه فانکشن تعریف میکنیم پراپرتی prototype براش ساخته میشه تا وقتی میخوایم از روش چند تا ابجکت نمونه سازی کنیم این proto داخل ابجکت نمونه اشاره کنه به prototype فانکشن که یه ابجکته شامل متدایی که ما بهش اضافه کردیم (توی مثال بالا میشه ابجکتی که توش start , stop , sayName رو اضافه کردیم).

prototype vs __proto__
prototype vs __proto__

پس prototype فقط پراپرتیِ فانکشن هستش و توی ابجکت همچین اسمی برای پراپرتی وجود نداره و در عوض توی ابجکت __proto__ داریم.

با استفاده از class و کلمه کلیدی new

خب کم کم داریم وارد محوله oop میشیم دیگه...، ما توی زبونای برنامه نویسی مختلف مبحثی داریم به اسم oop که طراحی و پیاده سازی برنامه بر محور ابجکت ها و کلاس هاس که حالا واردش نمیشم و کلاس ها چیزی بود که جااوااسکریپت توی es6 معرفی کرد و کاری کرد که oop توی جاوااسکریپت هم ممکن بشه اما به شیوه خودش! ینی اینکه ظاهر رو با استفاده از کلاس مثل oop توی بقیه زبان های برنامه نویسی در آورد اما پشت پرده دقیقا همین پروسه ی prototype رو پیاده میکنه درحالی که زبونای دیگه اصلا پشت پردشون اینطوری نیست!

حالا پروسه هایی که با روش های constructor function و object literal رفتیم رو با این روش و سینتکس جدید پیاده میکنیم تا هم ببینیم ظاهر oop چجوریه هم ببینیم این پروسه ارث بری و پروتوتایپ با کلاس ها چه شکلیه.

ES6 class
ES6 class

توی عکس بالا کلاس car رو تعریف کردیم و constructor که داخلش نوشتیم و پراپرتی name رو هم مشخص کردیم این constructor دقیقا برابر با همون فانکشنیه که توی constructor function پیاده کردیم ینی این:

constructor function === class constructor function
constructor function === class constructor function

و متدهایی که داخل کلاس نوشتیم دقیقا مثل اینه که به پروتوتایپ constructor function متد بدیم ینی این:

prototype defining in class and constructor function
prototype defining in class and constructor function

حالا بیاید این قسمتارو توضیح بدیم:

 نمونه سازی instantiate
نمونه سازی instantiate

خب آشنا هستیم که new دقیقا چیکار میکنه :

میاد یه نمونه ابجکت از کلاس car میسازه با پراپرتی اسم و یه پروتو که رفرنس داده میشه به پروتوتایپی که داخلش سه تا متد start , stop , sayName هست و پروتوی درون اون هم به آبجکت پروتوتایپ...

بعدش اومدیم یه کلاس دیگه ساختیم به اسم Detail و پراپرتی اختصاصی خودش به اسم speed رو تعریف کردیم و با extends و super پروتوتایپش رفرنس داده شده به ابجکت Car با پراپرتی name و سه تا متد، و در آخر پروتوی این ابجکت هم رفرنس داده میشه به ابجکت پروتوتایپ گلوبال.

اگه متغیر detail رو که از Detail نمونه سازی کردیم لاگ بگیریم نشون میده این قضیه رو:

'console.log(detail)'
'console.log(detail)'


خب جاوااسکریپت اومده اینطوری oop رو ظاهر سازی کرده ولی پشت پرده مثل بقیه oop ها عمل نمیکنه و همون روش پروتوتایپ خودشو پیش میگیره :)


مبحث پروتوتایپ با انواع و اقسام روش ها رو یاد گرفتیم

توی قسمت دوم میریم که تموم مفاهیم oop رو یاد بگیریم و بتونیم ازش تو پروژه ها استفاده کنیم و تو مصاحبه ها از پس سوالاش بر بیایم و کلا یه وجب دیگه توی javascript و کلا برنامه نویسی عمیق تر شیم.

پس تا قسمت بعد خدانگهدار?

javascriptoopprototypereactclass
درباره ی تکنولوژی های حوزه ی فرانت اند مینویسم.
شاید از این پست‌ها خوشتان بیاید