در این جا قصد دارم مفهوم this رو بیان کنم تا کسانی که هنوز کمی در استفاده از this مشکل دارن درک بهتری از اون پیدا کنن.
خب به طور کلی مشکل اصلی با this همیشه اینه که this به چی اشاره داره؟ یا به عبارتی this به چی برمیگرده یا ارجاع پیدا میکنه؟
خب this همیشه به آبجکت ای برمیگرده که در اون آبجکت یا در متد اون آبجکت در حال اجرا شدنه. یا به عبارتی میشه:
The Object That Is Executing The Current Function
برای اینکه بفهمیم کدوم آبجکت هم هست باید بدونیم اون تابعی که this در اون در حال اجرا شدنه از کجا و چگونه فراخوانی شده.
خب اگر بیایم و در یک مرورگر console رو باز کنیم و فورا داخلش تایپ کنیم this و Enter بزنیم متوجه میشیم که به Window Object اشاره میکنه.
حالا اگر کد زیر رو تایپ کنیم چی ؟
function () { console.log(this) }
در این صورت باز هم به window object اشاره خواهد کرد. چون تمام توابع و متغییر ها زیر مجموعه این آبجکت سراسری هستند.
نکته : اگر Strict Mode فعال باشه مقدار winodw object به undefined بر میگرده و بنابراین در قسمت هایی که this به window object بر میگشت الان دیگه به undefined بر میگرده.
خب حالا اگر کد زیر رو بزنیم نظرتون چیه ؟
const myObj = { init: function () { console.log(this); }, print: function(STP) { console.log(STP) } } myObj.init(); // ----> myObj myObj.print(this) // -----> window
همونطور که گفتم چون this در متد init داخل function ای هست که اون function متعلق به myObj هست. پس به myObj بر میگرده ولی در متد print مشاهده میکنیم که مقدار window رو برامون چاپ کرده دلیلش هم اینه که مقدار this داره وارد این متد میشه و خارج از myObj مقدار دهی شده. this در بیرون این object به window اشاره میکرده پس الانم وقتی بیاد داخل این متد باز هم به window اشاره خواهد کرد.
حالا یه مثال دیگه از jQuery
حتما شده که با استفاده از jQuery بخواین یه Click Listener برای یه button بنویسید. به مثال زیر دقت کنید. هر وقت که روی button پرینت کلیک کنید. مقدار this نمایش داده میشود.
var printObj = { init: function () { const elem = $('button#print'); elem.on('click', function (){ console.log(this); }); } } printObj.init();
که در اینجا this مقدار button با آی دی print رو به ما نمایش میده ولی چرا ؟ چرا مقدار printObj رو به ما نشون نمیده چون اگر elem رو کنسول کنید متوجه میشید که elem یه Object هست و تابع کلیک ما برای elem یک متد به حساب میاد و در واقع طبق تعریف اول ما this برای آبجکت elem هست نه برای printObj حالا اگر بخوایم که درون تابع click به printObj دسترسی پیدا کنیم میتونیم از تکنیک cache استفاده کنیم. به شکل زیر:
var printObj = { init: function () { const self = this; const elem = $('button#print'); elem.on('click', function (){ console.log(self); }) } } printObj.init();
در اینجا self به ما printObj رو نشون میده.
خب تا اینجا متوجه شدیم که اگر this درون متدی از یک آبجت باشه به همون آبجکت برمیگرده و اگر خارج از اون آبجکت باشه به در حقیقت درون یک function باشه که این function هم متد هیچ آبجکتی نباشه پس به window برمیگرده یا اگر NodeJs باشه به global برمیگرده.
function amir (name) { console.log(this) // ---> window obj this.name = name } const Modal = { init: function () { console.log(this) // ----> Modal Obj }, }
یه آبجکت تعریف میکنم که پروپرتی store داره و یک لیست از کالا به همراه تابعی که لیست این کالا ها رو چاپ میکنه. اگر متد print رو فراخوانی کنم میشه به شکل زیر:
const Basket = { store: "Hasan Agha", list: ["Apple", "Pen", "Apple pen"], print: function () { this.list.forEach(function (item) { console.log(item); }) } }; Basket.print(); /* ------------------ */ Apple Pen Apple pen
تا اینجا که مشکلی نیست ...
خب حالا چی میشه اگر بخوام اسم مغازه هم کنار این کالا ها قرار بدم حتما میگین خب کاری نداره this رو که داریم میریم اونجا که داره لوپ میزنه تا چاپ کنه this.store رو اضافه میکنیم اینجوری ..
this.list.forEach(function (item) { console.log( this.store -- item); }); /* ------------------ */ undefind -- Apple undefind -- Pen undefind -- Apple pen
خب this.store که باید "Hasan Agha" رو نشون میداد داره undefind نشون میده اذیت میکنه.
خب پنیک نزنید داستان اینه forEach رو شما تعریف نکردید درسته ؟ از کجا اومده از متغییر سراسری window و باید this اش به window اشاره کنه دیگه. دروغ میگم بگو دروغ میگی دعوا که نداریم. حالا دوست داری دقیق تر بدونی جریان چیه ادامه بده این پاراگراف رو ...
و علت اینکه من می تونم ازش استفاده کنم بر میگرده به خاصیت chaining زبان جاوا اسکریپت. یکم با من باشید تا بگم: بینید js میگه این متدی که فراخونی کردی در این آبجکتی که توش هست وجود داره یا نه اگر داشت استفاده میکنه اگر نداشت میری آبجکت پدرش و دوباره همین داستان رو چک میکنه داشت داشت نداشت پدرش تا میره میره میرسه به window پس این توابعی که در آبجکت window هستند در واقع همه جا قابل استفاده هستند. setTimeout هم جز همین داستانه (این بابا اصلا تو زبان js هم نیست موتور مرورگرتون داره اضافش میکنه) حالا نکته چیه نکته اینه که js درواقع یه سری توابع خاص برای هر دسته از تایپ های خودش تعریف کرده (البته js تایپ safe نیست که نمیخوام بحثش الان باز بشه) داره برای مثال برای تایپ آرایه forEach داره یا .length داره پس هر چیزی که به عنوان آرایه تعریف میشه تمام خصوصیتها یا به عبارتی تمام متد های تایپ آرایه رو در خودش ارث بری میکنه و بهشون دست رسی داره بنابراین من اگر this.List رو به عنوان آرایه تعریف کنم در نتیجش به forEach دست رسی دارم. و چون forEach برای window هستش و this.store منم داخل forEach هستش این میشه که this به window اشاره میکنه نه به Basket و با خاصیت chaining میره تا به window میرسه.
اگر this درون یک Constructor Function باشه چی ؟
function Amir() { this.name = "Amir" this.printName = { console.log(this.name) } }; Amir.printName(); // ---> Uncaught TypeError: Hasan.printName is not a function const A1 = new Amir(); A1.printName(); // ----> Amir
حتما میپرسید که مگه نگفتی اگر فانکشن من داخل متد نبود به window بر میگرده پس چرا الان باز به Amir برگشت؟
جواب اینه که وقتی که از new استفاده میکنیم. پشت صحنه javascript یک آبجکت خالی درست میکنه. و this رو به اون آبجکت خالی برمیگردونه.
بنابراین printName حکم method و name حکم property رو برای A1 ایفا میکنه. البته فقط وقتی که از کلمه رزرو new استفاده میکنم. در غیر این صورت همونطور که دیدید Uncaught TypeError برخوردیم. که بایدم میخوردیم اون Amir که نوشیتم تا new نکنیم به فانکش معمولیه پس this داره به window obj بر میگرده پس printName برای window obj ست شده نه برای Amir .
بنابراین
window.printName(); // ---> Amir Amir.printName() // --> Uncaught TypeError: Hasan.printName is not a function
ولی درست بعد از استفاده از new و ذخیره در A1 متوجه شدیم که داره به A1 برمیگرده.
برای همینه که میگم باید دید this در کجا و چگونه داره فراخوانی میشه.
(اینجا فعلا همینو نگه دارین و بدونین چون بخوام روی این قسمت ریز بشم باید prototype and inheritance هم بگم که هدفم اینه در مقالات بعدی بگم )
خلاصه اینها میشه این:
زبان جاواسکریپت Run Time Compile هست یعنی همون لحظه که داره کامپایل میشه اجرا هم میشه در ضمن محیط یا اسکوپی که خط در حال اجرا در اون قرار داره بهش "Execution Context"' گفته میشه. و در ران تایم javascript یک پشته از این Execution Context ها داریم اون تکه کدی که در بالای این پشته قرار داره در حال اجرا شدنه و this همیشه در حال ارجاع دادن به اون تکه کده و هر بار که این تکه کد تغییر میکنه مقدار آدرس this هم باهاش تغییر میکنه.
خب تا اینجا متوجه شدیم که چرا this همیشه در جای های مختلف مقادیر مختلفی رو به ما نشون میده.
راههایی هم هست برای اینکه مشخصا ذکر کنیم this در تابع یا متد مورد نظر ما دقیقا چه مقداری به خودش اختصاص بده که اونم با استفاده از متد های call - apply - bind هست که در پست بعدی قصد دارم توضیح بدم.
امیدوارم به دردتون خورده باشه.
و حتما این مثال هایی که زدم رو کار کنین. اگر از لحاظ نگارشی هم مشکل داشتین منو ببخشید سعی میکنم بهترش کنم ولی دوست داشتم خیلی معمولی و به زبان ساده توضیح داده باشم.