مقدمه
برای درک این ۳ متد کاربردی در JavaScript باید پیش از هر چیزی معنی this در موقعیت های مختلف رو بدونیم (واسه مطالعه بیشتر درباره this به این مقاله مراجعه کن). با استفاده از این ۳ متد میتونیم از متد های Object های دیگه روی Object های خودمون استفاده کنیم. بهترین روش واسه فهم این ۳ متد مثاله که در ادامه مقاله بهش می پردازیم.
همون طوری که میدونیم متد (Method) یعنی function ای که به یک Object تعلق داره. جنس متد ها و function ها در JavaScript برابر با function عه. یعنی typeof شون میشه function.
// a simple function const my_function = function() { console.log("This is my function."); } console.log(typeof my_function); // 'function' // a simple object const my_object = { name: 'bizhan', family: 'hejazi', fullName: function() { console.log(this.name + ' ' + this.family); } } console.log(my_object.fullName); // 'function'
اما function ها هم در حقیقت object هستن. در JavaScript تقریبا هر چیزی یک object عه و حتی اگر مستقیما هم object نیست میشه مثل object باهاش رفتار کرد. چون که function ها هم prototype دارن پس در واقع object هایی هستن که از روی prototype شون نوشته شدن. اگه شک داری می تونی کد زیر رو اجرا کنی:
Object.getPrototypeOf(my_function); // {}
همون طوری که دیدی پروتوتایپ (واسه درک مفهوم پروتوتایپ ها در JavaScript می تونی این مقاله رو بخونی) تابعی که در بالا تعریف کردیم یک object خالی عه.
خوب حالا که متوجه شدیم هر متدی خودش یک object عه که از روی پروتوتایپ اش درست شده، پس بیراه نیست اگر توقع داشته باشیم که یک سری متد هم از پروتوتایپ اش به ارث برده باشه. یکی از متد هایی که function ها به ارث میبرن call هست. این متد از ما یک object دریافت میکنه و این object رو به متدی که از روش call رو صدا زدیم تزریق میکنه. متوجه نشدی؟! طبیعیه. بذار مثال زیر رو بررسی کنیم. به کد زیر دقت کن:
const user = { name: 'bizhan', family: 'hejazi', fullName: function() { return this.name + ' ' + this.family; } } console.log(user.fullName);
در کد بالا object ای رو داریم که با استفاده از متد fullName اش نام کامل user رو تولید میکنه. داخل این متد کلیدواژه this رو داریم که به خود این object اشاره میکنه. حالا فرض کن که یک object دیگه میسازیم به صورت زیر:
const admin = { name: 'Hazhir', family: 'Dehestani' }
خوب در کد بالا object ای به نام admin درست کردیم که property های name و family رو داره، ولی مثل user در بالا متد fullName رو نداره. می تونیم متد fullName رو برای این یکی هم تعریف کنیم، ولی چرا دوباره نویسی کنیم؟!
یکی از اصول خیلی مهم برنامه نویسی اصل DRY عه که یعنی: Don't Repeat Yourself. یعنی همش کد های خودت رو تکرار نکن. یک جوری کد بنویس که یک بار بنویسی و هزار بار استفاده کنی. در برابر اصل DRY، روش خطایی هست به نام WET که مخفف Write Everything Twice عه. یعنی طوری کد بنویسی که همش یک چیزی رو دوباره نویسی کنی.
در مثال بالا اگه برای admin هم بخایم دوباره متد fullName رو تعریف کنیم WET کردیم. متد call به ما این توانایی رو میده که متد fullName عه user رو برای admin صدا بزنیم! دیگه چی بهتر از این میخای؟! این طوری:
console.log(user.fullName.call(admin)); // Hazhir Dehestani
بذار به زبان خیلی ساده تری برای متد call رو توضیح بدم.
متد call مصداق this در تعریف fullName رو عوض میکنه.
توی تعریف fullName منظور از this خود object ای بود که fullName روش تعریف شده. ولی متد call باعث میشه که این this تبدیل به object ای بشه که به call دادیم.
خوب حالا اومدیم و متدی که میخایم call رو روش اعمال کنیم parameter هم داشت. مثلا به کد زیر دقت کن:
const user = { name: 'Bizhan', family: 'Hejazi', intro: function(age, city) { return this.name + ' ' + this.family + ' is ' + age + ' from ' + city; } } console.log(user.fullName(26, 'Shiraz')); // Bizhan Hejazi is 26 from Shiraz;
اگه بخایم متد call رو روی متد intro صدا بزنیم و object دیگری رو بهش بدیم تکلیف age و city چی میشه؟! هیچی! خیلی راحت age و city رو هم بعد از admin و به ترتیب به متد call میدیم! این طوری:
const admin = { name: 'Behrad', family: 'Azizi' } console.log(user.intro.call(admin, 28, 'Tabriz')); // Behrad Azizi is 28 from Tabriz
متد apply هم شبیه به متد call عه با این تفاوت که پارامتر های اضافی رو به صورت یک آرایه میگیره. اگه برای همین مثال بالا میخایم به جای call از apply استفاده کنیم باید این طوری می گفتیم:
console.log(user.intro.apply(admin, [28, 'Tabriz']));
متد apply به خصوص در ترکیب با آبجکت Math به کار میاد. فرض کن آرایه ای از اعداد داریم و میخایم max و min این اعداد رو حساب کنیم. روی آبجکت Math در JavaScript متد های min و max هست. اگه فقط متد call رو داشتیم باید تک تک عنصر های آرایه مون رو به صورت دستی به متد call می دادیم (که عملا غیر ممکنه) ولی با استفاده از متد apply خیلی راحت خود آرایه رو میدیم. به کد زیر دقت کن:
const nums = [1,5,12,98, 234, -1, 0, 54.2]; console.log(Math.min.apply({}, nums));
در قسمت bold شده کد بالا اولین پارامتری که به متد apply دادیم یک آبجکت خالی عه. ولی چه نیازی هست یک object خالی بدیم؟ چون متد min اصلا با this کاری نداره و هر کاری میکنه با پارامتر هایی هست که میگیره اما برای این که متد apply قاطی نکنه یک آبجکت خالی دادیم. میشد null یا undefined هم بدیم به جای object خالی. دومین پارامتر هم که آرایه اعدادمونه. اگه میخاستیم همین کارو با متد call بکنیم باید می گفتیم:
console.log(Math.min.call({}, 1,5,12,98,234,-1,0,54.2));
که خوب طبیعتا متد apply خیلی راحت تره.
همون طوری که در ابتدای این مقاله گفتیم هر function ای در JavaScript در حقیقت یک object عه. تفاوت اش با بقیه object ها در اینه که callable هست. یعنی اگه جلوش () قرار بدیم، صداش کردیم تا رسالتش رو انجام بده. اگر هم جلوش () قرار ندیم بدون صدا زدن ازش استفاده کردیم. یکی از مواقعی که بدون صدا زدن یک function ازش استفاده میکنیم، موقعی هست که یک function رو به عنوان callback به function دیگری میدیم. callback ها function هایی هستند که بدون () به function های دیگه میدیمشون تا وقتی که function دریافت کننده callback کارش تموم شد، جلوی callback پرانتز بذاره و صداش بزنه. به کد زیر دقت کن:
const user = { name: 'Bizhan', family: 'Hejazi', fullName: function() { console.log(this.name + ' ' + this.family); } } setTimeout(user.fullName, 3000); // undefined undefined
در کد بالا متد fullName از user رو به تابع setTimeout دادیم تا ۳ ثانیه بعد از اجرای کد این متد رو صدا بزنه. در این جا user.fullName رو به عنوان یک callback به تابع setTimeout دادیم. ولی مشاهده میکنیم که بعد از ۳ ثانیه به جای Bizhan Hejazi برای ما undefined undefined پرینت میگیره! چرا؟!
دلیلش اینه که متد fullName رو به صورت منفرد و جدا از object اش به setTimeout دادیم. در تعریف fullName از کلیدواژه this استفاده شده که منظور ازش user هست ولی وقتی که متد رو به صورت انفرادی به عنوان یک callback میدیم به setTimeout دیگه object ای نداره! انگاری یتیم شده باشه. تکلیف چیه؟!
تکلیف اینه که با استفاده از متد bind بیایم و user رو به متد fullName بچسبونیم تا هر جایی که به عنوان callback ازش استفاده کردیم، object اش رو هم به خودش چسبونده باشه. این طوری:
user.fullName = user.fullName.bind(user);
حالا دیگه خیلی سفت و سخت مقدار this رو برای متد fullName مشخص کردیم. این طوری هر جایی که به صورت انفرادی هم بره this اش رو فراموش نمیکنه. حالا اگه مجددا این متد رو به عنوان callback به setTimeout بدیم بعد از ۳ ثانیه برامون Bizhan Hejazi پرینت میکنه.
اگه متد های call و apply و bind رو درک کنی، میشه گفت که سواد JavaScript ات در حد بالایی قرار داره چرا که پیش نیاز درک این ۳ متد، درک Prototyping در JavaScript، درک Callback ها و شئ گرایی (OOP) هست که این ۳ تا هم از مباحث پیشرفته حساب میشن. این ۳ متد در کنار هم دیگه انعطاف پذیری بی مثالی به JavaScript میدن که بدون شک یکی از رمز های ماندگاری این زبانه. وقتی که JavaScript به بازار عرضه شد شاید کسی تصورش رو هم نمی کرد که تا این حد محبوب بشه ولی در عمل دیدیم که تبدیل به یکی از محبوب ترین و پرکاربرد ترین زبان های جهان شده و هر روز هم داره قوی تر میشه.