هرچیزی که درمورد bind و this در JavaScript و React باید بدونیم!

مفهوم bind، از اون مواردیه که تازه‌کارها رو اذیت میکنه. تا حدی که ممکنه دست از یاد گرفتنِ مفهوم اصلیش بردارن و فقط به استفاده با آزمون و خطا اکتفا کنند. توی این مقاله قراره با زبان خیلی خیلی ساده، این مفهوم رو خوب متوجه بشیم و بعد از اون اسامی مختلف bind رو یاد بگیریم و در آخر، سراغ علت استفادش توی class componentهای ری‌اکت بریم :))

با این تیکه کدِ خیلی ساده شروع می‌کنیم.

First Snippet: Implicit binding
First Snippet: Implicit binding

کاملا مشخصه که خروجی این تیکه کد، 27 میشه. تا اینجا همه چی خوب پیش میره و مشکلی وجود نداره. اما مشکل از جایی شروع میشه که این function رو توی یه متغیر بریزیم. درمورد خروجی این تیکه کد خوب فکر کنین و تا قبل اون، مقاله رو ادامه ندین:

Second Snippet: Default Binding
Second Snippet: Default Binding

این تیکه کد، بهمون undefined برمیگردونه! تنها تفاوتش هم با حالت قبلی اینه که فانکشنمون رو توی یه variable ریختیم.فرض کنین با ریختن این فانکشن توی متغیر age، این اتفاق میفته:

Third Snippet: Default Binding
Third Snippet: Default Binding

میدونین this توی کد بالا داره به کجا اشاره میکنه؟ برای فهمیدن این موضوع من توی فانکشن showAge، از لاگ، this.age رو به this تبدیل میکنم. یعنی:

showAge: function () {
     console.log(this);
}

با این تغییر، اگر توی Node.js باشیم، global و اگر توی محیط browser باشیم، window بهمون برگردونده میشه.

خب، تا اینجا منطقی شد که چرا undefined می‌خوریم. توی global و window چیزی به اسم age وجود نداره و this وقتی که توی variable ریخته میشه، دیگه به object خودش اشاره نمیکنه و میره سراغ global و window. اینجاست که bind به کمکمون میاد که به این variable بفهمونیم this باید به کجا اشاره کنه و وصلش کنیم به هرجا که میخوایم! پس کد نهایی ما به این شکل میشه:

Fourth Snippet: Explicit Hard Binding
Fourth Snippet: Explicit Hard Binding

اما میخوایم عمیق‌تر بشیم :) فرض کنین به جای یک آبجکت userData، دو تا آبجکت داشته باشیم، و میخوایم هر موقع که دوست داشته باشیم متغیرمون رو به یکی از این آبجکت‌ها bind کنیم. این تیکه کد رو خوب نگاه کنین:

Fifth Snippet: Explicit Hard Binding
Fifth Snippet: Explicit Hard Binding

پس توی اینجا می‌بینید که به راحتی می‌تونیم به هرکجا که دوست داریم، bind کنیم.

و اما میخوایم حالتای بالا رو دسته‌بندی کنیم. اگر دقت کنین زیر همه عکسا یه سری اسم نوشتم. بریم ببینیم معنی هرکدوم چیه :)

1. Default Binding

همونجور که از اسمش مشخصه، حالتیه که ما bind نمیکنیم و this به همون حالت default اشاره میکنه. یه نکته خیلی خیلی مهم که جلوتر هم ازش استفاده میکنیم!

اگر توی strict mode باشیم، default binding برای this، مقدار undefined داره.

حالا اینجا میتونیم علت این رو بفهمیم که چرا اگه توی class componentهای React، یه فانکشنی رو صدا می‌زنیم، بعضی موقعا داره بهمون undefined میده! شاید براتون سوال باشه که خب من از strict mode استفاده نمی‌کنم. پس چرا توی class componentای که نوشتم، this داره undefined برمیگردونه؟!

نکته باحال‌تری که وجود داره اینه:

بدنه‌ی کلاس‌های JS، خود به خود توی strict mode اجرا میشن!

علت این موضوعم برمیگرده به یه مشکلی که تا ECMAscript 262 edition 5 وجود داشت که واردش نمیشیم.

پس به طور خلاصه:

Default Binding in strict mode => undefined

Otherwise => global or window

به اضافه این که کلاس‌ها به صورت دیفالت strict mode هستند. برای واضح‌تر شدن هم به تیکه کد دوم و سوم همین مقاله مراجعه کنین (زیر عکس‌ها عدد و نوع binding وجود داره حتما بررسیش کنید). بریم برا حالت بعدی :)

2. Implicit binding

همین حالتی که بحثش رو اول مقاله کردیم. یعنی وقتی که this خود به خود به جای دیگه غیر از default binding اشاره کنه. حالا با این اسم‌ها خیلی راحت‌تر میتونیم این نکته رو بگیم و بحثای اول مقاله رو جمع‌بندی کنیم:

اگر یک فانکشن از داخل object رو توی variable بریزیم، از حالت Implicit binding به Default Binding تبدیل میشه. و برای همین this.age توی عکسای بالا، undefined میخوره. چون توی global و window چیزی به اسم age وجود نداره.

برای این که دقیق‌تر متوجه نکته‌ی بالا بشین، پیشنهاد میکنم یه بار دیگه اولین تیکه کد مقاله رو ببینین.

3. Explicit Hard Binding

ترکیب مورد 1 و 2! یعنی وقتی که مثل نکته بالا، Implicit binding به Default Binding تبدیل بشه و ما بخوایم به همون آبجکت برگردونیمش. و همون اتفاقایی که بالا دیدیم که به هرجا که دوست داشتیم bind میکردیم. مثالشم میشه تیکه کد چهارم و پنجم همین مقاله :)

و اما اتفاقی که توی class componentهای React میفته

می‌رسیم به جای جالب ماجرا!

برای خیلیامون سواله که وقتی داریم توی یه ایونتی، از یه فانکشن استفاده می‌کنیم، کِی باید از bind استفاده کنیم. اونایی که مفهوم رو یاد نگرفتن میبینن که بعضی موقعا بدون bind داره کار میکنه و بعضی موقعا ارور میده! توی این قسمت همه این موارد رو بررسی میکنیم?

با این تیکه کد شروع میکنیم. برای خیلیامون این تیکه آشناست. اما میدونین چرا توی این حالت undefined میخوریم؟

توی تیکه کد بالا میخوایم یه اسمی که از propهای component اومده رو alert کنیم. و مشخصاً به thisای احتیاج داریم که Implicitly Bound یا Explicitly Hard Bound باشه نه default bound (سعی کنین اسما رو برا خودتون تکرار کنین که حالتای مختلف bind رو بدونین.

و اما نکته مهم ماجرا که چرا undefined میخوریم و thisامون default bound هست؟

وقتی داریم از eventها استفاده میکنیم، فانکشن event handler که توی تیکه کد بالا alertNameعه، Implicit Binding خودش رو از دست میده و به Default Binding برمیگرده.

ممکنه بازم بگین خب اوکی :)) Implicit binding چرا تبدیل به default binding میشه؟! باید در جواب بگم که شما کل ماجرا رو اینجور در نظر بگیرین.

let click = this.alertName;
click();

اگه بخوایم فنی‌تر به قضیه نگاه کنیم، کلیت حرفم اینه که ما یه event handler (که توی اینجا فانکشن alertName هست) رو می‌خوایم به ایونت بدیم. دقیقا این اتفاق نمیفته ولی شبیه‌سازی از همون موضوعه. پس رسیدیم به حرف اول مقاله که یه چیزِ thisدار رو ریختیم توی یه variable و از حالت implicit بردیمش به default. و اکثر موقعا توی constructor میایم کار bind رو انجام میدیم یا بعضی موقعا توی همونجا که داریم event handler رو صدا میزنیم. و اما سوال دیگه‌ای که پیش میاد:

چرا توی constructor این کار رو انجام میدیم؟

میخوایم مثال بالا رو ادامه بدیم تا از مقدار undefined برای this، خارج بشیم. این تیکه کد برای هممون آشناست:

constructor(props) {
    super(props);
    this.alertName = this.alertName.bind(this);
}

اما بیاید جدا جدا این خط رو با شناخت الانمون از bind انجام بدیم. اول از همه توی constructor از this لاگ میگیریم:

constructor(props) {
    super(props);
    console.log(this);
}

چیزی که تو کنسول بهمون نشون میده، کل کامپوننتمونه که طبق مثال اسم کامپوننتمون Login هست. حالا اگر this.alertName رو لاگ بگیریم، خود فانکشن رو بهمون نشون میده. یعنی چیزی که توی کنسول میبینیم میشه:

ƒ alertName() {
    alert(this.props.name);
}

خب، پس تا الان خود فانکشنمون رو سمت چپ این خط کد داریم:

    this.alertName = this.alertName.bind(this)

و دقیقا داریم Explicit Hard Binding رو اینجا انجام میدیم! یعنی this(که قبل‌تر لاگ گرفتیم و بهمون خود کامپوننت رو داد) رو به فانکشنمون که this.alertName هست، bind میکنیم که thisهای توی فانکشن، به جای اشاره به undefined، به خود کلاس Login اشاره کنند و اینجاست که میتونیم از this.props استفاده کنیم :)

خیلی سخت بهش نگاه میکردیم نه؟! به همین سادگی این موضوع که برای هممون توی React سوال بوده، واضح میشه.

پایان سختی‌ها با Arrow Functions!

اینجا والت در نقش arrow functionعه و اسکایلر اونور خط، bindعه?
اینجا والت در نقش arrow functionعه و اسکایلر اونور خط، bindعه?

و اما اسم‌گذاری آخر برای bind! تا حالا سه نوع Default Binding و Implicit binding و Explicit Hard Binding رو گفتیم. میمونه نوع آخر که قراره تقدیم arrow functionهای عزیز کنیم.

4. Lexical binding

اگر از Arrow Functionها استفاده کنیم، دیگه نیاز به هیچ نوع bindای نداریم. چون به صورت اتوماتیک خودشون this رو به اسکوپی که تعریف میشن bind میکنن :)))

خیلی کوتاه و جامع و مفید: Arrow Functionها کلا از این مسخره‌بازیای bind خوششون نمیاد!

پس حالا میتونیم علت اون "بعضی موقعا کار میکنه" توی ری‌اکت رو بفهمیم.

مثلا می‌تونیم همونجا که فانکشن alertName رو صدا می‌زنیم، از arrow function استفاده کنیم:

یا این که میتونیم این تیکه رو مثل قبل نگه داریم، ولی بالا رو به صورت Arrow function بنویسیم. یعنی:

یکی از تفاوتای function با arrow function که توی سوالات مصاحبه زیاد پرسیده میشه هم اینجا مشخص میشه. قابلیت Lexical binding!

سخت نبود نه؟ این فقط یه مثال بود از همه چیزایی که ممکنه پیگیریشون نکرده باشیم. و ممکنه یکی از همین موارد توی پروژه‌های بزرگ، مشکلات بزرگ ایجاد کنند و توی بدترین مواقعِ ممکن باعث دردسر بشن! توی این چند مقاله اخیر سعی کردم چندتا از این مثال‌‌های skipشده رو بیارم. مثل asynchronous و package-lock و ... . امیدوارم که براتون مفید بوده باشه :)

موفق و سربلند باشید❤Reza Hasani