راهنمایی برای this در JavaScript
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 10 دقیقه

راهنمایی برای this در JavaScript

کلیدواژه this یکی از مورد استفاده‌ترین و به اشتباه درک شده‌ترین مفاهیم در JavaScript است. من امروز سعی خواهم کرد این که مسئله را تغییر دهم.

قبل از شروع به خواندن این مقاله، بهتر است دوره آموزش JavaScript را بر روی راکت بگذرانید تا درک بهتری از آن داشته باشید، و سپس بیایید به روزهای خوبی باز گردیم که درباره ضمایرها یاد می‌گرفتیم.

«فلپس سریع شنا می‌کند، زیرا او می‌خواهد در مسابقه برنده شود.»

به استفاده از ضمیر «او» دقت کنید. ما در اینجا مستقیما به فلپس اشاره نمی‌کنیم، اما از ضمیر «او» برای اشاره به فلپس استفاده می‌کنیم. به طور مشابه، JavaScript از کلیدواژه this به عنوان یک مرجع برای اشاره به آبجکت مورد نظر استفاده می‌کند. مثلا the subject.

مثال:

var car= {
make: "Lamborghini",
model: "Huracán",
fullName: function () {
console.log(this.make+" " +this.model);
console.log(car.make+ " " +car.model);
}
}
car.fullName();

در کد بالا، ما یک آبجکت به نام car داریم که ویژگی‌های make، model و fullName‌ را دارد. مقدار fullName، یک تابع است که نام کامل ماشین را با استفاده از دو سینتکس متفاوت چاپ می‌کند.

  • this در هنگام استفاده از this => this.make+” “+this.model، به آبجکت مورد نظر اشاره می‌کند که car‌ می‌باشد. پس this.make در واقع car.make بوده، و this.model هم همینطور می‌باشد.
  • در هنگام استفاده از نشانه‌گذاری نقطه‌ای، ما می‌توانیم به ویژگی‌های آبجکت، یعنی car.make و car.model دسترسی داشته باشیم.

همین!

حال که ما this و پایه‌ترین استفاده از آن را درک کردیم، بیایید برخی قوانین اساسی را بسازیم، تا بتوانیم همیشه آن را به یاد داشته باشیم.

کلیدواژه this در JS‌ به آبجکتی که به آن تعلق دارد، اشاره می‌کند

var car={
make:'....'
func:()=>{console.log(this.make)}}

this در مثال بالا، به آبجکت car تعلق دارد.

این کلیدواژه مقادیر متفاوتی را بر حسب نحوه استفاده از آن می‌پذیرد

۱. داخل یک متد

۲. داخل یک تابع

۳. به تنهایی

۴. در یک رویداد

۵. call() و apply()

داخل یک متد

وقتی که this داخل یک متد استفاده می‌شود، به آبجکت صاحب اشاره دارد.

توابعی که داخل یک آبجکت تعریف شده‌اند، متد نام دارند. بیایید باز هم مثال car خود را در نظر بگیریم.

var car= {
make: "Lamborghini",
model: "Huracán",
fullName: function () {
console.log(this.make+" " +this.model);
console.log(car.make+ " " +car.model);
}
}
car.fullName();

در اینجا fullName() یک متد است. this داخل این متد، به car تعلق دارد.

داخل یک تابع

this داخل یک تابع، کمی پیچیده است. اولین چیزی که باید درک کنید، این است که همانطور که تمام آبجکت‌ها ویژگی‌هایی را دارند، توابع هم ویژگی‌هایی را دارند. هر زمان که یک تابع اجرا می‌شود، ویژگی this‌ را به دست می‌آورد، که در واقع یک متغیر با مقدار آبجکتی است که آن را فراخوانی می‌کند.

این در واقع میان‌بری به «آبجکت پیشین»، یا آبجکت فراخوانی کننده است.

اگر تابع توسط یک آبجکت فراخوانی نشده است، this داخل تابع مورد نظر به آبجکت global تعلق دارد، که widnow نام دارد. در این مورد، this‌ به مقادیر تعریف شده در محدوده global‌ اشاره خواهد داشت. بیایید مثالی را برای درک بهتر ببینیم:

var make= "Mclaren";
var model= "720s"
function fullName(){
console.log(this.make+ " " + this.model);
}

var car = {
    make:"Lamborghini",
    model:"Huracán",
    fullName:function () {
    console.log (this.make + " " + this.model);
    }
}
    car.fullName(); // Lmborghini Huracán
    window.fullName(); // Mclaren 720S
    fullName(); // Mclaren 720S

در اینجا make، model و fullName به طور global تعریف می‌شوند، درحالیکه آبجکت car هم یک پیاده‌سازی fullName دارد. this وقتی که توسط آبجکت car فراخوانی شود، به ویژگی‌های تعریف شده داخل آبجکت اشاره دارد. در سمت دیگر، دو فراخوانی تابع دیگر مشابه هستند و ویژگی‌های تعریف شده به صورت global را بر می‌گردانند.

به تنهایی

this وقتی که به تنهایی استفاده شده است و داخل هیچ تابع یا آبجکتی قرار ندارد، به آبجکت global اشاره دارد.

در اینجا this به ویژگی نام global اشاره دارد.

داخل یک رویداد

رویدادها می‌توانند از هر نوعی باشند، اما در جهت سادگی و وضوح، بیایید یک رویداد کلیک را در نظر داشته باشیم.

هر زمان که بر روی یک دکمه کلیک می‌شود و یک رویداد بر می‌خیزد، این رویداد می‌تواند یک تابع دیگر را برای انجام یک کار بر پایه کلیک فراخوانی کند. اگر this داخل آن تابع استفاده شده باشد، به عنصری اشاره خواهد داشت که رویداد مورد نظر را برخیزید. در DOM، تمام عناصر به عنوان آبجکت ذخیره شده‌اند. به همین علت است که وقتی یک رویداد بر می‌خیزد، به آن عنصر اشاره دارد؛ زیرا عنصر صفحه وب در واقع یک آبجکت داخل DOM است.

مثال:

<button onclick="this.style.display='none'">
  Remove Me!
</button>

call()، apply() و bind()

  • bind: ما را قادر می‌سازد تا مقدار this را بر روی متدها تنظیم کنیم.
  • call و apply: ما را قادر می‌سازند تا توابع را قرض بگیریم و مقدار this را بر روی فراخوانی تابع تنظیم کنیم.

call، bind و apply خودشان موضوعی برای یک پست دیگر هستند. این موارد خیلی مهم هستند، و توضیح دادن آن‌ها در اینجا ممکن نیست؛ زیرا ما باید همه چیز را درباره this بدانیم، تا بتوانیم نحوه استفاده از این توابع را هم بدانیم. اما اگر به یادگیری آن‌ها علاقه دارید، می‌توانید نگاهی به مقاله «چگونه از متدهای apply، call و bind در JavaScript استفاده کنیم؟» داشته باشید.

پیچیده‌ترین بخش

this اگر به خوبی درک شده باشد، می‌تواند کار ما را آسان‌تر کند. اما برخی موارد وجود دارند که این کلیدواژه در آن‌ها به اشتباه درک می‌شود.

مثال ۱

var car = {
make:"Lamborghini",
model:"Huracán",
name:null,
fullName:function () {
this.name=this.make + " " + this.model;
console.log (this.name);
}
}

var anotherCar={
make:"Ferrari",
model:"Italia",
name:null}

    anotherCar.name= car.fullName();

در اینجا ما یک نتیجه غیر منتظره می‌گیریم. ما یک متد را قرض گرفتیم که this یک آبجکت دیگر را استفاده می‌کند، اما مشکل این است که متد مورد نظر فقط به تابع anotherCar اختصاص داده شده است، اما در واقع بر روی آبجکت car فراخوانی شده است. به همین علت است که ما نه نتیجه Ferrari، بلکه Lamborghini را دریافت می‌کنیم.

برای رفع این مشکل، ما از متد call() استفاده می‌کنیم.

در اینجا متد call()، تابع fullName() را بر روی آبجکت anotherCar فراخوانی می‌کند که در اصل تابع fullName را ندارد.

ما همچنین می‌توانیم ببینیم که وقتی car.name و anotherCar.name را لاگ می‌کنیم، نتیجه دومین مورد را به دست می‌آوریم، نه اولین مورد، که یعنی تابع در واقع بر روی anotherCar فراخوانی شده بود، نه car.

مثال ۲

var cars=[
{ make: "Mclaren", model: "720s"},{make: "Ferrari",model: "Italia"}]

var car = {cars:[{make:"Lamborghini", model:"Huracán"}],
fullName:function () {console.log(this.cars[0].make + " " + this.cars[0].model);}}
var vehicle=car.fullName;
vehicle()

در قطعه کد بالا ما یک آبجکت global‌ به نام cars داشته، و آبجکتی با نام مشابه را نیز داخل آبجکت car داریم. متد fullName() سپس به متغیر وسیله نقلیه (vehicle) اختصاص داده می‌شود که بعد از آن نیز فراخوانی می‌شود. متغیر مورد نظر به آبجکت global تعلق دارد؛ پس this به جای آبجکت cars، آبجکت global با نام cars را فراخوانی می‌کند.

برای رفع این مشکل، ما از تابع .bind() استفاده می‌کنیم.

bind کردن در تنظیم مقدار this و از این رو متغیر وسیله برابر با آبجکت car و نه آبجکت global، کمک می‌کند.

مثال ۳

var car = {
cars:[{make:"Lamborghini",model:"Huracán"},
{ make: "Mclaren", model: "720s"},
{make: "Ferrari",model: "Italia"}],
fullName:function(){this.cars.forEach(()=>{console.log (this.make + " " + this.model);
})}}
car.fullName();

در قطعه کد بالا، fullName() یک تابع را فراخوانی می‌کند که بر روی آرایه ماشین‌ها با استفاده از forEach تکرار می‌کند. در داخل forEach، یک تابع ناشناس وجود دارد که this‌ زمینه خود را از دست می‌دهد. یک تابع داخل یک تابع در JavaScript، نام closure را دارد. closureها بسیار مهم بوده و به طور گسترده‌ای در JavaScript مورد استفاده قرار می‌گیرند.

یک مفهوم مهم دیگر که نقشی را بازی می‌کند، scope است. یک متغیر داخل یک تابع، نمی‌تواند به متغیرها و ویژگی‌های خارج از scope (محدوده) خود دسترسی داشته باشد. this داخل تابع anon، نمی‌تواند به this خارج از آن دسترسی داشته باشد. پس this جایی نمی‌تواند برود، جز این که به آبجکت global اشاره کند. اما در اینجا، هیچ ویژگی‌ای برای this تعریف نشده است که به آن دسترسی داشته باشد؛ پس مقدار undefined‌ چاپ می‌شود.

یک راه حل برای مشکل بالا، این است که مقدار this را به یک متغیر خارج از تابع ناشناس اختصاص دهیم و سپس از آن داخل تابع استفاده کنیم.

در اینجا، متغیر self شامل مقدار this است که با توابع درونی استفاده می‌شود و از این رو به ما خروجی می‌دهد.

مثال ۴

var car= {
make: "Lamborghini",
model: "Huracán",
fullName: function (cars) {
cars.forEach(function(vehicle){
console.log(vehicle +" "+ this.model);
})}}
car.fullName(['lambo','ferrari','porsche']);

این یک مثال بازبینی شده است، که this در آن مورد دسترسی قرار نگرفته بود؛ پس ما مقدار آن را با استفاده از یک متغیر به نام self حفظ کردیم. بیایید از تابع پیکانی برای برطرف کردن همین مشکل استفاده کنیم:

همانطور که می‌توانید ببینید، استفاده از یک تابع پیکانی در forEach() به طور خودکار این مشکل را رفع می‌کند و نیازی نیست که ما bind‌ کرده، یا این که مقدار برخی متغیرهای دیگر را به this بدهیم. علت آن این است که توابع پیکانی، زمینه خود را bind می‌کنند؛ پس this‌ در وقع به زمینه منشأ، یا آبجکت منشأ اشاره می‌کند.

مثال ۵

var car= {
make: "Lamborghini",
model: "Huracán",
fullName: function () {
console.log(this.make +" "+ this.model);
}}
var truck= {
make: "Tesla",
model: "Truck",
fullName: function (callback) {
console.log(this.make +" "+ this.model);
callback();
}}
truck.fullName(car.fullName);

کد بالا شامل دو آبجکت یکسان می‌باشد، که یکی از آن‌ها یک تابع callback را دارد. یک تابع callback، تابعی است که به عنوان آرگومان به یک تابع دیگر منتقل شده، و سپس داخل تابع بیرونی فراخوانی می‌شود تا نوعی روال معمولی را به پایان برساند.

در اینجا، متد fullName آبجکت truck شامل یک callback می‌باشد که همچنین داخل آن هم فراخوانی شده است. آبجکت car ما به مانند قبل است. وقتی که با متد fullName آبجکت truck را با callback (آرگومان) مورد نظر به عنوان متد fullName آبجکت car فراخوانی کردیم، خروجی‌های Tesla Truck و undefined undefined را دریافت کردیم.

پس از خواندن درباره this، برخی از شما شاید حدس زده باشید که car.fullName مدل و برند آبجکت truck را چاپ خواهد کرد، اما متاسفانه باز هم this ما را گول زد. در اینجا car.fullName به عنوان یک آرگومان منتقل شده است و در واقع در حال فراخوانی آبجکت truck نیست. Callback‌ مورد نظر متد آبجکت car را فراخوانی می‌کند، اما دقت کنید که ناحیه فراخوانی اصلی برای تابع، callbackای است که this را به آبجکت global متصل (bind) می‌کند. این موضوع کمی گیج کننده است، پس دوباره آن را بخوانید.

در اینجا ما برای این که کمی وضوح داشته باشیم، خود this را چاپ می‌کنیم. ما می‌توانیم ببینیم که یک محدود (scope) global به this‌ مربوط به یک callback داده شده است. پس برای دریافت یک نتیجه، ما ویژگی‌های global با نام make و model را می‌سازیم.

حال با اجرای کد مشابه به همراه ویژگی‌های global با نام make و model، ما در نهایت پاسخ را this‌ از نوع global می‌رسانیم. این ثابت می‌کند که this به آبجکت global‌ اشاره دارد.

برای دریافت نتیجه مطلوب خود، یعنی نتیجه car.fullName، ما باز هم از bind() برای اتصال آبجکت car به callback مورد نظر استفاده می‌کنیم، که همه چیز را درست خواهد کرد.

مشکل رفع شد!

شکی در این نیست که this خیلی کاربردی است، اما این کلیدواژه برخی تله‌های مختص خود را هم دارد. امیدوارم درک آن را برای شما آسان کرده باشم.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
4.5 از 2 رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

عرفان کاکایی

مقالات برگزیده

مقالات برگزیده را از این قسمت میتوانید ببینید

مشاهده همه مقالات