اکثر مواقع، prototype در JavaScript افرادی که به تازگی یادگیری آن را شروع کردهاند را گیج میکند؛ به خصوص اگر این افراد پیشزمینهای در C++ یا Java داشته باشند.
در JavaScript، وراثت (inheritance) در مقایسه با C++ یا Java کمی متفاوت کار میکند. وراثت JavaScript بیشتر تحت عنوان «وراثت prototypeای» (prototypical inheritance) شناخته میشود.
وقتی که شما همچنین با class هم در JavaScript مواجه میشوید، درک همه چیز سختتر میشود. سینتکس class جدید ظاهری مشابه به C++ یا Java دارد، اما در واقعیت به طور متفاوتی کار میکند.
در این مقاله، ما تلاش خواهیم کرد تا وراثت prototypeای را در JavaScript درک کنیم. ما همچنین به سینتکس بر پایه class جدید نگاهی داشته، و سعی خواهیم کرد تا درک کنیم که ماهیت آن چیست. پس بیایید شروع کنیم.
اول، با تابع و prototype قدیمی در JavaScript شروع میکنیم.
درک نیاز به Prototype
اگر با آرایهها، آبجکتها یا رشتههای JavaScript کار کرده باشید، متوجه شدهاید که برخی متدها وجود داشته، و این متدها به طور پیشفرض در دسترس هستند.
برای مثال:
var arr = [1,2,3,4];
arr.reverse(); // returns [4,3,2,1]
var obj = {id: 1, value: "Some value"};
obj.hasOwnProperty('id'); // returns true
var str = "Hello World";
str.indexOf('W'); // returns 6
آیا تا به حال برایتان سوال شده است که این متدها از کجا میآیند؟ شما خودتان این متدها را تعریف نکردهاید.
آیا میتوانید متدهای مختص خود به مانند این موارد را تعریف کنید؟ میتوان گفت که به این صورت میتوانید:
var arr = [1,2,3,4];
arr.test = function() {
return 'Hi';
}
arr.test(); // will return 'Hi'
این کد کار خواهد کرد، اما فقط برای این متغیر که arr نام دارد. فرض کنید یک متغیر دیگر به نام arr2 داریم. در این صورت، arr2.tes() یک خطای «TypeError: arr2.test is not a function.» را بروز میدهد.
پس این متدها چگونه در دسترس تمام نمونههای یک آرایه / آبجکت / رشته قرار میگیرند؟ آیا شما هم میتوانید متدهای مختص خود را با رفتاری مشابه بسازید؟ پاسخ «بله» است. شما باید این کار را به روش صحیح انجام دهید. Prototype در JavaScript برای کمک در همین مورد وارد میشود.
اول بیایید ببینیم که این توابع از کجا میآیند. قطعه کد زیر را در نظر داشته باشید:
var arr1 = [1,2,3,4];
var arr2 = Array(1,2,3,4);
ما دو آرایه را به دو روش مختلف ساختهایم: arr1 با استفاده از ادبیات آرایه و arr2 با استفاده از تابع سازنده Array. هر دو معادل یکدیگر هستند، اما برخی تفاوتها را با هم دارند که این موضوع در این مقاله اهمیتی ندارند.
حال به تابع سازنده Array میرسیم. این یک تابع سازنده از پیش تعریف شده در JavaScript است. اگر ابزار Chrome Developer را باز کنید، به کنسول بروید، console.log(Array.prototype) را تایپ کنید و کلید Enter را بفشارید، چیزی به مانند این تصویر خواهید دید:
در اینجا تمام متدهایی که برایمان سوال هستند را مشاهده مینمایید. پس حال ما میدانیم که آن توابع از کجا میآیند. شما میتوانید این کار را با String.prototype و Object.prototype هم امتحان کنید.
بیایید تابع سازنده ساده و مختص خود را بسازیم:
var foo = function(name) {
this.myName = name;
this.tellMyName = function() {
console.log(this.myName);
}
}
var fooObj1 = new foo('James');
fooObj1.tellMyName(); // will print James
var fooObj2 = new foo('Mike');
fooObj2.tellMyName(); // will print Mike
آیا میتوانید مشکل اساسی کد بالا را تشخیص دهید؟ مشکل این است که ما در رویکرد بالا، در حال هدر دادن حافظه هستیم. دقت کنید که متد tellMyName برای تمام نمونههای foo مشابه است. هر زمان که ما یک نمونه foo را میسازیم، متد tellMyName فضایی را در حافظه سیستم اشغال میکند. اگر tellMyName برای تمام نمونهها مشابه است، بهتر است که آن را در فقط یک جا نگه داریم و تمام نمونههای خود را مجبور کنیم که از آنجا ارجاع کنند. بیایید نحوه انجام این کار را ببینیم:
var foo = function(name) {
this.myName = name;
}
foo.prototype.tellMyName = function() {
console.log(this.myName);
}
var fooObj1 = new foo('James');
fooObj1.tellMyName(); // will print James
var fooObj2 = new foo('Mike');
fooObj2.tellMyName(); // will print Mike
بیایید تفاوت رویکرد بالا و رویکرد قبلی را بررسی کنیم. در رویکرد بالا اگر شما نمونه مورد نظر را console.dir() کنید، چیزی به مانند این تصویر را خواهید دید:
دقت کنید که ما myname را به عنوان یک ویژگی از نمونه مورد نظر داریم. tellMyName تحت __proto__ تعریف شده است. من کمی بعد به سراغ این __proto__ باز خواهم گشت. مهمتر این که مقایسه کردن tellMyName هر دو نمونه، به نتیجه true ارزیابی میشود. مقایسه تابع در JavaScript، فقط اگر ارجاعهای آنها یکی باشد به نتیجه true ارزیابی خواهد شد. این ثابت میکند که tellMyName حافظه اضافیای را برای چندین نمونه مصرف نمیکند.
بیایید همین مسئله را با رویکرد قبلی ببینیم:
دقت کنید که این بار tellMyName به عنوان ویژگیای برای نمونهها تعریف شده است. tellMyName دیگر تحت __proto__ نیست. همچنین دقت کنید که این بار مقایسه کردن تابع به نتیجه false ارزیابی میشود. علت آن این است که دو مکان حافظه متفاوت وجود دارند، و ارجاعهای آنها متفاوت هستند.
امیدوارم تا به اینجا ضرورت prototype را درک کرده باشید.
حال بیایید به برخی جزئیات بیشتر درباره prototype نگاه داشته باشیم.
تمام توابع JavaScript یک ویژگی prototype خواهند داشت که از نوع آبجکت است. شما میتوانید ویژگیهای مختص خود را تحت prototype تعریف کنید. وقتی که شما از تابع مورد نظر به عنوان یک تابع سازنده استفاده میکنید، تمام نمونههای آن ویژگیهایی را از آبجکت prototype به ارث خواهند برد.
حال بیایید به آن ویژگی __proto__ که در بالا دیدیم برسیم. __proto__ به سادگی فقط ارجاعی به آبجکت prototype است که نمونهها از آن به ارث برده میشوند. آیا این موضوع پیچیده به نظر میرسد؟ در واقع آنچنان هم پیچیده نیست. بیایید این را با یک مثال بصریسازی کنیم.
کد زیر را در نظر داشته باشید. ما از پیش میدانیم که ساختن یک آرایه با ادبیات آرایه، ویژگیهایی را از Array.prototype به ارث خواهد برد.
var arr = [1, 2, 3, 4];
من در بالا گفتم: «__proto__ به سادگی فقط ارجاعی به آبجکت prototype است که نمونهها از آن به ارث برده میشوند.» پس arr.__proto__ باید مشابه Array.prototype باشد. بیایید این مسئله را تایید کنیم.
حال ما نباید با استفاده از __proto__ به آبجکت prototype دسترسی داشته باشیم. طبق گفته MDN، استفاده از __proto__ اصلا پیشنهاد نمیشود و شاید در تمام مرورگرها پشتیبانی نشود. راه صحیح انجام این کار:
var arr = [1, 2, 3, 4];
var prototypeOfArr = Object.getPrototypeOf(arr);
prototypeOfArr === Array.prototype;
prototypeOfArr === arr.__proto__;
آخرین خط کد بالا، نشان میدهد که __proto__ و Object.getPrototypeOf چیز مشابهی را بر میگردانند.
حال وقت استراحت است. یک لیوان چایی بخورید و مثالهای بالا را خودتان آزمایش کنید. پس از این که آماده شدید، به این مقاله برگردید تا بتوانیم ادامه دهیم.
زنجیرهبندی و وراثت Prototype
آیا دقت کردید که یک __proto__ دیگر داخل اولین آبجکت __proto__ قرار دارد؟ اگر نه، کمی به بالا اسکرول کنید. نگاهی به آن داشته باشید و به اینجا بازگردید. حال ما ماهیت این موضوع را مورد بحث قرار خواهیم داد. این موضوع تحت عنوان زنجیرهبندی prototype شناخته میشود.
در JavaScript ما با کمک زنجیرهبندی به وراثت میرسیم.
این مثال را در نظر داشته باشید: همه ما عبارت «وسیله نقلیه» را درک میکنیم. یک اتوبوس را میتوان یک وسیله نقلیه دانست. یک خودرو را میتوان یک وسلیه نقلیه دانست. یک موتور را میتوان یک وسیله نقلیه دانست. اتوبوس، خودرو و موتور برخی ویژگیهای مشتری را دارند؛ به همین علت است که وسیله نقلیه نامیده میشوند. برای مثال، آنها میتوانند از یک مکان به مکانی دیگر منتقل شوند. آنها چرخ، بوق و... دارند.
باز هم اتوبوس، خودرو و موتور میتوانند از انواع مختلفی مانند مرسدس، BMW، هوندا و... باشند.
در ترسیم بالا، اتوبوس ویژگیهای مشابه وسیله نقلیه را به ارث میبرد، و اتوبوس مرسدس بنز هم برخی ویژگیهای اتوبوس را به ارث میبرد. به طور مشابه، همین مسئله درباره خودرو و موتور هم صدق میکند.
بیایید این رابطه را در JavaScript تاسیس کنیم.
اول، بیایید در جهت سادگی چند نکته را در نظر بگیریم:
- تمام اتوبوسها ۶ چرخ دارند.
- روندهای شتابگیری و ترمز کردن در میان اتوبوسها، خودروها و موتورها متفاوت هستند، اما در میان تمام اتوبوسها، تمام خودروها و تمام موتورها یکی هستند.
- تمام وسیلههای نقلیه بوق دارند.
در قسمت بعدی این مقاله، با یک قطعه کد شروع کرده، و این مبحث را ادامه خواهیم داد. با ما همراه باشید...
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید