Head of Frontend Development at Dopely.top | Senior Frontend Engineer at Digikala.com
هرچیزی که درمورد bind و this در JavaScript و React باید بدونیم!
مفهوم bind، از اون مواردیه که تازهکارها رو اذیت میکنه. تا حدی که ممکنه دست از یاد گرفتنِ مفهوم اصلیش بردارن و فقط به استفاده با آزمون و خطا اکتفا کنند. توی این مقاله قراره با زبان خیلی خیلی ساده، این مفهوم رو خوب متوجه بشیم و بعد از اون اسامی مختلف bind رو یاد بگیریم و در آخر، سراغ علت استفادش توی class componentهای ریاکت بریم :))
با این تیکه کدِ خیلی ساده شروع میکنیم.
کاملا مشخصه که خروجی این تیکه کد، 27 میشه. تا اینجا همه چی خوب پیش میره و مشکلی وجود نداره. اما مشکل از جایی شروع میشه که این function رو توی یه متغیر بریزیم. درمورد خروجی این تیکه کد خوب فکر کنین و تا قبل اون، مقاله رو ادامه ندین:
این تیکه کد، بهمون undefined برمیگردونه! تنها تفاوتش هم با حالت قبلی اینه که فانکشنمون رو توی یه variable ریختیم.فرض کنین با ریختن این فانکشن توی متغیر age، این اتفاق میفته:
میدونین 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 باید به کجا اشاره کنه و وصلش کنیم به هرجا که میخوایم! پس کد نهایی ما به این شکل میشه:
اما میخوایم عمیقتر بشیم :) فرض کنین به جای یک آبجکت userData، دو تا آبجکت داشته باشیم، و میخوایم هر موقع که دوست داشته باشیم متغیرمون رو به یکی از این آبجکتها bind کنیم. این تیکه کد رو خوب نگاه کنین:
پس توی اینجا میبینید که به راحتی میتونیم به هرکجا که دوست داریم، 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!
و اما اسمگذاری آخر برای 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
مطلبی دیگر از این انتشارات
ساخت یک سیستم اطلاع رسانی درون سازمانی قسمت دوم
مطلبی دیگر از این انتشارات
راه اندازی HttpClient برای دریافت اطلاعات از api برای انگولار
مطلبی دیگر از این انتشارات
چک کردن وضعیت اینترنت کاربر در جاوا اسکریپت