محدوده (Scope)
scope یا محدوده در جاوا اسکریپت مشخص می کند که شما به چه متغیرهایی دسترسی دارید. دو نوع scope وجود دارد – محدوده سراسری (global scope) و محدوده محلی (local scope).
محدوده سراسری (Global scope)
اگر متغیری خارج از همه توابع یا براکت های باز و بسته ({}) اعلان شود، گفته می شود که در حوزه سراسری تعریف شده است.
این امر فقط در مورد جاوا اسکریپت در مرورگرهای وب صادق است. شما متغیرهای سراسری را در Node.js به شکل متفاوتی اعلام می کنید، اما ما در این مقاله به Node.js نمی پردازیم.
هنگامی که یک متغیر سراسری را اعلام کردید، می توانید از آن متغیر در هر جایی از کد خود، حتی در توابع استفاده کنید.
اگرچه می توانید متغیرها را در global scope اعلام کنید، توصیه می شود این کار را نکنید. این به این دلیل است که احتمال naming collisions وجود دارد که در آن دو یا چند متغیر یکسان نامگذاری می شوند. (collision یک event است از دو یا چند رکورد که به یک شناسه یا مکان یکسان در حافظه اختصاص داده می شود). اگر متغیرهای خود را با const یا let اعلام کرده باشید، هر زمان که یک name collision رخ دهد، یک خطا دریافت خواهید کرد که نامطلوب است.
اگر متغیرهای خود را با var اعلام کنید، متغیر دوم بعد از اعلام، متغیر اول را بازنویسی می کند. این نیز نامطلوب است زیرا اشکال زدایی کد خود را سخت می کنید.
بنابراین، شما همیشه باید متغیرهای محلی را اعلام کنید، نه متغیرهای سراسری.
محدوده محلی (Local scope)
متغیرهایی که فقط در قسمت خاصی از کد شما قابل استفاده هستند در محدوده محلی در نظر گرفته می شوند. به این متغیرها متغیر محلی نیز گفته می شود.
در جاوا اسکریپت، دو نوع محدوده محلی وجود دارد: محدوده تابع (function scope) و محدوده بلوک (block scope)
بیایید ابتدا در مورد محدوده تابع یا function scope صحبت کنیم.
محدوده تابع (Function scope)
وقتی متغیری را در یک تابع اعلام می کنید، فقط در داخل تابع می توانید به این متغیر دسترسی داشته باشید. وقتی از آن خارج شدید نمی توانید این متغیر را دریافت کنید.
در مثال زیر، متغیر hello در محدوده sayHello قرار دارد:
محدوده بلاک (Block scope)
در JavaScript هنگامی که متغیری را با const یا let درون براکت های باز و بسته ({}) اعلام می کنید، می توانید به این متغیر فقط در آن پرانتز دسترسی داشته باشید.
در مثال زیر، می توانید ببینید که hello به پرانتز محدود شده است:
محدوده بلاک زیر مجموعه ای از محدوده تابع است زیرا توابع باید با براکت ها اعلان شوند (مگر اینکه از توابع پیکان با بازگشت ضمنی - arrow functions with an implicit return - استفاده کنید).
Function hoisting and scopes
توابع، زمانی که با یک اعلان تابع تعریف می شوند، همیشه در بالای محدوده فعلی قرار می گیرند. بنابراین، این دو معادل هستند:
هنگامی که با یک عبارت تابع اعلان می شود، توابع در بالای محدوده فعلی بالا نمی روند.
به دلیل این دو تغییر، بالا بردن تابع (function hoisting) به طور بالقوه می تواند گیج کننده باشد و نباید استفاده شود. همیشه عملکردهای خود را قبل از استفاده از آنها اعلام کنید.
توابع به اسکوپ یکدیگر دسترسی ندارند
وقتی توابع را جداگانه تعریف می کنید، حتی اگر یک تابع در دیگری استفاده شود، به محدوده یکدیگر دسترسی ندارند.
در مثال زیر، second به firstFunctionVariable دسترسی ندارد.
دامنه های تو در تو (Nested scopes)
وقتی یک تابع در تابع دیگری تعریف می شود، تابع داخلی به متغیرهای تابع بیرونی دسترسی دارد. به این رفتار در جاوا اسکریپت lexical scoping یا محدوده واژگانی می گویند.
با این حال، تابع خارجی به متغیرهای تابع داخلی دسترسی ندارد.
برای تجسم نحوه کار اسکوپ ها، می توانید شیشه یک طرفه را تصور کنید. شما می توانید بیرون را ببینید، اما مردم از بیرون نمی توانند شما را ببینند.
اسکوپ ها در توابع مانند یک شیشه یک طرفه رفتار می کنند. شما می توانید بیرون را ببینید، اما افراد بیرون نمی توانند شما را ببینند.
اگر اسکوپ هایی در اسکوپ دارید، چندین لایه شیشه یک طرفه را تجسم کنید.
چندین لایه تابع به معنای چندین لایه شیشه یک طرفه است.
پس از درک همه چیز در مورد دامنه ها تا کنون، به خوبی می توانید بفهمید کهclosures چیست.
Closures
هر زمان که یک تابع را در یک تابع دیگر ایجاد می کنید، یک closure ایجاد کرده اید. تابع درونی closure است. این closure معمولاً برگردانده می شود، بنابراین می توانید بعداً از متغیرهای تابع خارجی استفاده کنید.
از آنجایی که تابع درونی برگردانده شده است، می توانید با نوشتن یک عبارت return در حین اعلام تابع، کد را کمی کوتاه کنید.
از آنجایی که closure ها به متغیرهای تابع خارجی دسترسی دارند، معمولاً برای دو مورد استفاده می شوند:
1- برای کنترل عوارض جانبی
2- برای ایجاد متغیرهای خصوصی
کنترل عوارض جانبی با closures
عوارض جانبی (side effects) زمانی اتفاق می افتد که به غیر از برگرداندن مقداری از یک تابع، کاری را انجام دهید. بسیاری از چیزها می توانند عوارض جانبی باشند، مانند درخواست Ajax، مهلت زمانی (timeout) یا حتی یک عبارت console.log:
وقتی از closures برای کنترل عوارض جانبی استفاده می کنید، معمولاً نگران مواردی هستید که می توانند جریان کد شما را مانند Ajax یا تایم اوت ها مختل کنند.
بیایید این موضوع را با یک مثال مرور کنیم تا همه چیز واضح تر شود.
فرض کنید می خواهید برای تولد دوست خود یک کیک درست کنید. درست کردن این کیک یک ثانیه طول می کشد، بنابراین شما تابعی نوشتید که لاگ ها بعد از یک ثانیه یک کیک درست می کنند.
من از ES6 arrow functions در اینجا استفاده می کنم تا مثال را کوتاه تر و قابل فهم تر کنم.
همانطور که می بینید، این تابع کیک سازی یک عارضه جانبی دارد: timeout.
فرض کنید می خواهید دوستتان طعمی را برای کیک انتخاب کند. برای انجام این کار، می توانید بنویسید که یک طعم به تابع makeCake خود اضافه کنید.
وقتی تابع را اجرا می کنید، متوجه شوید که کیک بلافاصله بعد از یک ثانیه درست می شود.
مشکل اینجا این است که شما نمی خواهید کیک را بلافاصله پس از دانستن طعم آن درست کنید. می خواهید بعداً در زمان مناسب آن را بسازید.
برای حل این مشکل، می توانید یک تابع prepareCake بنویسید که طعم شما را ذخیره می کند. سپس، makeCake closure را داخل prepareCake برگردانید.
از این مرحله به بعد، هر زمان که بخواهید می توانید تابع برگردانده شده را فراخوانی کنید و کیک در عرض یک ثانیه درست می شود.
اینگونه است که از closures برای کاهش عوارض جانبی استفاده می شود - شما تابعی ایجاد می کنید که closure داخلی را به میل شما فعال می کند.
متغیرهای خصوصی با closures
همانطور که می دانید، متغیرهای ایجاد شده در یک تابع خارج از تابع قابل دسترسی نیستند. از آنجایی که نمی توان به آنها دسترسی داشت، آنها را متغیرهای خصوصی نیز می نامند.
با این حال، گاهی اوقات شما نیاز به دسترسی به چنین متغیر خصوصی دارید. شما می توانید این کار را با کمک closures انجام دهید.
saySecretCode در مثال بالا تنها تابعی است (a closure) که SecretCode را خارج از تابع مخفی اصلی آشکار می کند. به این ترتیب، به آن یک تابع ممتاز (privileged function) نیز می گویند.
اشکال زدایی scope ها با DevTools
DevTools در Chrome و Firefox اشکال زدایی متغیرهایی را که می توانید در اسکوپ فعلی به آنها دسترسی داشته باشید را برای شما ساده می کند. دو راه برای استفاده از این قابلیت وجود دارد.
اولین راه این است که کلمه کلیدی دیباگر (debugger) را به کد خود اضافه کنید. این باعث می شود اجرای جاوا اسکریپت در مرورگرها متوقف شود تا بتوانید اشکال زدایی کنید.
در اینجا یک مثال با prepareCake نشان داده شده است:
اگر DevTools خود را باز کنید و به تب Sources در کروم (یا تب Debugger در فایرفاکس) بروید، متغیرهای در دسترس را مشاهده خواهید کرد.
اشکال زدایی اسکوپ prepareCake
شما همچنین می توانید کلمه کلیدی دیباگر را به closure تغییر دهید. توجه کنید که چگونه متغیرهای اسکوپ این بار تغییر می کنند:
خلاصه مطالب این مقاله
درک scope ها و closure ها در JavaScript خیلی سخت نیست. زمانی که بدانید چگونه آنها را از طریق یک شیشه یک طرفه ببینید، بسیار ساده هستند.
وقتی متغیری را در یک تابع اعلام می کنید، فقط می توانید به آن در تابع دسترسی داشته باشید. گفته می شود که این متغیرها در اسکوپ تابع هستند.
اگر هر تابع درونی را در یک تابع دیگر تعریف کنید، این تابع درونی closure نامیده می شود که در واقع دسترسی به متغیرهای ایجاد شده در تابع خارجی را حفظ می کند.