راهنمای استفاده از this در جاوا اسکریپت

عکس از Pankaj Patel قرارگرفته در Unsplash
عکس از Pankaj Patel قرارگرفته در Unsplash

در جاوا اسکریپت یکی از خواصی ( properties ) هست که به طور پیشفرض به هر فانکشن ( function ) پاس داده میشود. مقدار آن بستگی به Object داره که توسط فانکشن صدا زده میشه که عمدتا در زمان runtime جاوا اسکریپت تعریف میشه. اما عملکرد this به اون شکلی که تو ذهنتون هست ممکنه نباشه، پس با من همراه باشین تا بهتون بگم زمانی که میخواین مقدار this رو مشخص کنید چه حرکتی باید بزنین :))

یه چند دقیقه وقتتون رو بذارین و کد زیر رو چک کنین، فک میکنین که خروجی چه میشه؟ کد رو تو بخش console مرورگرتون ( رو صفحه کلیک راست کنید و گزینه Inspect Element در فایرفاکس و یا کلیک راست و زدن گزینه Inspect در گوگل کروم و انتخاب گزینه console در کادر باز شده) اجرا کنید و خروجی رو ببینین

let myObject = {
  name: "ali",
  age: 22,
  aboutMe: function(){
    return "My name is "+this.name+", I am "+this.age + " years old"
  }
}
console.log(myObject.aboutMe());

همونطور که متوجه شدین خروجی به شکل زیر خواهد بود:
My name is ali, I am 22 years old

ما از this برای ارجاع دادن به object داخل فانکشن استفاده کردیم. به نظر ساده اس درست میگم؟ حالا به قسمت پایین توجه کنید:

let myObject2 = {
  name: "sara",
  age: 24,
  aboutMe: function(){
    return "My name is "+this.name+", I am "+this.age + " years old"
  },
  child: {
    name: "soheila",
    age: 2,
    aboutMe: function(){
      return "My name is "+this.name+", I am "+this.age + " years old"
    }
  }
};
console.log(myObject2.aboutMe());
console.log(myObject2.child.aboutMe());

فک کنم انتظار داشتین خروجیتون چیزه دیگه ای باشه، اما متاسفم :)) دستور دوم ما با نمایش خروجی اطلاعات فرزند ( child ) رو در console نمایش میده و نه والد رو. و میتونیم بگیم که this به صورت پیشفرض اشاره میکنه به نزدیک ترین object ما. اما وقتی که ما فانکشن رو به صورت global تعریف کنیم قصه مون چطور میشه؟

آبجکت عمومی ( یا به قول فرنگیا Global Object)

هر فراخوانی یا تابعی که داخل یک object تعریف نشه وصل میشه به global object، همونطور که آبجکت Window داخل مرورگر هست. شما میتونین برای رسیدن به حرف من this; رو داخل console مرورگر وارد کنید و اینتر رو بزنین تا نتیجه رو ببینین. این عمل آبجکت Window رو به همراه خاصیت هاش ( properties ) و مقادیرش برمیگردونه.
شما حتی میتونین properties رو از آبجکت تغییر بدین. کد زیر رو داخل console کپی / پیست کنین:

console.log(this.color); //Undefined
this.color = "Green";
console.log(this.color); //Green

خط اول ما مقدار undefined رو بر میگردونه، بخاطر اینکه آبجکت Window ما هیچ پراپرتی با نام color رو در داخلش نداره. خط دوم یک پراپرتی با نام color ایجاد میکنه و مقدار "Green" بهش نسبت میده. و الان شما میبینید که تو خط سوم کدمون میتونیم به این پراپرتی دسترسی داشته باشیم.

تا الان ما، کل حرفه ما سره این بود که runtime جاوا اسکریپت به ما کمک میکنه تا مقدار this چی باشه. اما این موضوع برای همه ی مواقع خوب نیست و فقط بعضی وقتا به داد ما میرسه و بعضی جاها کار پیچیده تر میشه. برای نمونه توبع ناهمگام یا ( asynchronous function ) در نظر بگیرید. کد زیرو ببینید و حدس بزنین که خروجی چه خواهد بود؟ تو کنسول مرورگرتون تستش کنید:

let myObject3 = {
  firstName: "Michael",
  age: 20,
  aboutMe: function(){
    setTimeout(function(){
      console.log(this.firstName + " is " + this.age + " years old")
    },5000)
  }
}
myObject3.aboutMe();

متد aboutMe دارای یک فانکشن هست که فقط بعد از 5 ثانیه اجرا میشه، پس زمانی که اجرا میشه فانکشن رو برای بعدا نگه میداره. وقتی که در نهایت اجرا بشه اون در global scope نیست و خروجی بازگشتی ما به شکل زیر هست:

undefined is undefined years old.

دلیل این اتفاق خیلی ساده است و اون این هست که کلمه this در زمان اجرا به آبجکت Window اشاره میکنه و نه به myObject3 و از اونجایی که هیج کدوم از اون پراپرتی ها در آبجکت Window وجود ندارن مقدار undefined برگشت داده میشه. اما خب ما چطور مشکل رو حل کنیم؟ ما باید به نحوی به Async function مون بگیم که کلمه this چی هست. نگران نباشین جاوا اسکریپت 3 راه برای راحتی ما گذاشته که عبارتند از call, apply وbind

استفاده از متد Call 

همونطور که آبجکت ها دارای پراپرتی هایی از پیش تعریف شده است ( prototypes ) مثله toString ، فانکشن ها هم این شکلی هستن. یکی از اون متد ها call هست. متد call در بسیاری از موارد استفاده میشه اما در نهایت یک مشکل رو حل میکنه.

بیاین دوتا آبجکت جداگانه تعریف کنیم شبیه همونایی که قبلا داشتیم:

let user1 = {
  name: "touhid",
  sayHi: function(){
    return this.name + " says Hi"
  }
}
let user2 = {
  name: "mitra",
  sayHi: function(){
    return this.name + " says Hi"
  }
}
console.log(user1.sayHi()); //touhid says Hi
console.log(user2.sayHi()); //mitra says Hi

هر دو آبجکت فانکشن sayHi در درون خودشون دارند. الزامی نیست که ما کدهارو چند بار بنویسیم، ما باید تا جایی که میشه کد هامون رو باز نویسی کنیم تا به بهترین خروجی برسیم. ما میتونیم کد بالا رو به شکل زیر باز نویسی کنیم. ما میتونیم فانکشن رو یه بار بنویسیم و در آبجکت دوم با استفاده از متد call مقدار دهی کنیم:

let user1 = {
  name: "touhid ",
  sayHi: function(){
    return this.name + " says Hi"
  }
}
let user2 = {
  name: "mitra "
}
console.log(user1.sayHi());           //touhid says Hi
console.log(user1.sayHi.call(user2)); //mitra says Hi

در اینجا ما از فانکشن user1 که sayHi بود به همراه متد call برای مقدار دهی مجدد با استفاده از پاس دادن آرگومان user2 بود استفاده کردیم. اگر فانکشن ما باید پارامتر داشته باشه، ما میتونیم آرگومان هارو در متد call قرار بدیم

let user1 = {
  name: "touhid ",
  greet: function(msg, friend){
    return this.name + " says " +msg+ " to "+friend
  }
}
let user2 = {
  name: "mitra "
}
console.log(user1.greet("Hi", "jalal"));
//touhid says Hi to  jalal 
console.log(user1.greet.call(user2, "Hello", "habib"));
//mitra says Hello to habib

استفاده از متد Apply 

بسیار شبیه به متد call هست، متد apply دقیقا همینکارو با یک استثنا انجام میده، تنها یک آرگومان به رو میپذیره و زمانی که فانکشن ما بیشتر از یک پارامتر داشته باشه ما باید به صورت آرایه آرگومان هارو پاس بدیم.

console.log(user1.greet.apply(user2, ["Hello", "Habeeb"]));

هر دو متد call و apply با اجرای تابع بلافاصله فراخوانی میکنند. این مسئله یکی دیگه از مشکلاتی است که ما می خوایم از آنها در Asynchronous استفاده کنیم .

استفاده از متد Bind 

برخلاف call و apply ، متد bind فانکشنی رو بازگشت میده که صدا شده باشه اما در حال حاضر اجرا نشده باشه.

let myObject4 = {
  firstName: "mohammad",
  age: 32,
  aboutMe: function(){
    setTimeout(function(){
      console.log(this.firstName + " is " + this.age + " years old")
    }.bind(myObject4),5000)
  }
}
myObject4.aboutMe(); //mohammadis 32 years old

این بار، خروجی درست به نظر میاد. متد bind به درستی به رفرنس آبجکتمون اشاره کرده.

تو این مطلب ما با 3 روش آشنا شدیم که که میتونیم به this مقدار بدیم. اما راه چهارمی هم هست که همینکارو میکنه و اون استفاده از new هست اما کمی توضیحش در این مقاله نمیگنجه ( به قول قدیمیا هر سخن جایی و هر نکته مکانی دارد :)) ) و به صورت جداگانه بعدا توضیح میدم خدمتتون.

از اینکه در این مقاله هم همراه من بودین سپاسگذارم، شما هم تجربه تو این موضوع دارین؟ خوشحال میشم نظراتتون رو زیر این پست ببینم و بیشتر باهم در ارتباط باشیم



https://virgool.io/JavaScript8/%D9%81%DB%8C%D9%84%D8%AA%D8%B1-%D9%87%D8%A7-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-rkzpcnejj6dg