اگر حتی یک خط کد جاوا اسکریپت نوشته اید، بدون اینکه متوجه شوید از یکی از چهار scope جاوا اسکریپت استفاده کرده اید و این سطوح مختلف scope در جاوااسکریپت شکل کار کد شما و اینکه کدام متغیر در کجای برنامه قابل دسترس هست را تعیین میکنه و برای اینکه تسلط بهتری روی کد داشته باشیم و کدی پایدار و بدون باگ داشته باشیم لازمه که هر 4 سطح مختلف scope در جاوااسکریپت را بشناسیم و ما در این مقاله به زبانی ساده هر 4 سطح مختلف scope در جاوااسکریپت یعنی Global Scope, Module Scope, Block Scope, Function Scope را توضیح دادیم.
تعریف Scope
اولین سوالی که باید بهش بپردازیم اینه که اصلا scope چی هست. در جاوااسکریپت و تقریبا تمامی زبان های برنامه نویسی، کد ما در قالب یکسری scope تعریف شده اجرا میشن و این scope ها شکل تعامل کد با متغیرهای ما و نحوه اجرای اونهارو تعیین میکنند.اگر بخواهیم تعریف ساده ایی از scope داشته باشیم میتونیم اونهارو به عنوان یکسری جعبه در نظر بگیریم که محتویات داخل جعبه فقط در داخل همان جعبه قابل دسترس هستند.
const outer = "بیرون"
function test() {
const inner = "داخل"
console.log(outer, inner)
// Print: "Out", "In"
}
test()
console.log(outer, inner)
// Throws Uncaught Reference Error: inner is not defined
در مثال بالا ما 2 متغیر با نام های outer
و inner
تعریف کردیم، یکی بیرون فانکشن و یکی هم درون فانکشن و سپس از هر دوتای اونها console.log
گرفتیم. console.log
اول که درون فانکشن نوشته شده بدون مشکل اجرا شد و خروجی مدنظر را به ما داد اما console.log
دومی ارور ایجاد کرد و دلیل این ارور هم عدم دسترسی ما به متغیری هست که داخل فانکشن نوشته شده.
در مثال بالا به دوتا از scope های مختلف در جاوااسکریپت اشاره کردیم و دیدیم که این scope ها چطور تعامل کد با متغیرها و بخش های مختلف برنامه را تعیین میکنند. حالا بیایید بریم سراغ هر 4 scope مختلف در جاوااسکریپت و اونهارو یک به یک شرح بدیم.
سطوح مختلف scope در جاوااسکریپت
4 سطح scope در جاوااسکریپت به شرح زیر هستند:
- Global Scope
- Module Scope
- Block Scope
- Function Scope
شاید در نگاه اول کمی گیج کننده بنظر برسه و فکر کنید که به خاطر سپردن جزئیات تک تک این scope ها کار دشواریه اما در واقعیت و تقریبا در 80% از کدهایی که مینویسیم صرفا داریم از Module Scope و Block Scope استفاده میکنیم و ما فارغ از این قضیه هر 4 سطح مختلف scope هارا به زبانی ساده و با چند مثال ملموس برای شما شرح میدیم.
Global Scope
بیایید با ساده ترین سطح scope شروع کنیم، سطحی که کمترین میزان محدودیت را داره و خصوصا اگر ابتدای راه مسیر یادگیری جاوااسکریپت هستید اونرو بیشتر دیدید.
برای درک بهتر global scope تصور کنید که یک فایل HTML و یک فایل Javascript همانند مثال زیر داریم:
<script src="script.js"></script>
// script.js
const a = 1
console.log(a)
// خروجی: 1
در مثال بالا ما داریم یک متغیر را در سطح global scope ایجاد میکنیم و هر زمانی که متغیری در بالاترین سطح یک فایل ( خارج از هرگونه فانکشن و { }
) ایجاد بشه، اون متغیر به عنوان یک متغیر global scope در نظر گرفته میشه و در نتیجه در تمامی بخش های برنامه ما قابل دسترس هست!
دسترسی global به یکسری از متغیرها کدنویسی را سریعتر و کار مارو هم در قدم اول راحت تر میکنه چراکه دیگه نیازی نیست نگران عدم دسترسی به یک متغیر در جایی که به اون نیاز داریم داشته باشیم. اما این قضیه به همینجا ختم نمیشه و کمی که کد ما پیچیده تر بشه و تعداد فایل های ما بیشتر بشه مدیریت این قضیه هم سخت تر میشه. به مثال زیر توجه کنید:
<script src="script-1.js"></script>
<script src="script-2.js"></script>
// script-1.js
const a = 1
// script-2.js
console.log(a)
// Prints: 1
همینطور که میبیند متغیری که در فایل script-1.js
نوشتیم در فایل script-2.js
هم در دسترسه و global scope بودن اون متغیر کاری کرده تا در تمامی بخش های برنامه بتونیم بهش دسترسی داشته باشیم. از اینرو توصیه میکنیم تا جای ممکن از global scope استفاده نکنید چراکه کار نگهداری از کد را دشوار تر میکنه و در بهینه بودن برنامه ما هم تاثیرگذار هست.
Module Scope
module scope شباهت خیلی زیادی به global scope داره که با یک تفاوت اساسی از global scope متمایز میشه. متغیرهای module scope فقط در همان فایلی که تعریف شدن در دسترس هستند و این بدان معناست که ما نمیتونیم از متغیری که در فایل دیگری ایجاد شده استفاده کنیم و این هم به مدیریت آسان تر کد کمک میکنه و هم به بهینه تر بودن برنامه.
جهت module scope شدن فایل نیازه تا type="module"
را به تگ script
اضافه کنیم:
<script src="script-1.js" type="module"></script>
<script src="script-2.js" type="module"></script>
// script-1.js
const a = 1
console.log(a)
// Prints: 1
// script-2.js
console.log(a)
// Throws Uncaught Reference Error: a is not defined
با همین تغییر جزئی دیگه متغیرهای ما بصورت global و در همه فایل ها در دسترس نیستند و با بزرگتر شدن پروژه با مشکلاتی مثل بهینه نبودن برنامه و یا تداخل دو متغیر مشابه بر نخواهیم خورد.
Block Scope
block scope ساده ترین و قابل فهم ترین scope هست چراکه این scope را با علامت { }
( curly braces ) میشناسیم و scope متغیرهایی که داخل { }
محصور شدن دقیقا همین بلوک از کد در نظر گرفته میشه و این بدان معناست که functions, if statements, for loops و .... block scope خودشان را ایجاد میکنند.
function test() {
const funcVar = "Func"
if (true) {
const ifVar = "If"
console.log(funcVar, ifVar)
// خروجی: "Func", "If"
}
console.log(funcVar, ifVar)
// Throws Uncaught Reference Error: ifVar is not defined
}
در مثال بالا دو block scope وجود داره و هر block scope فقط به متغیرهایی درون خودش یعنی تمام کدی که داخل { }
های خودش نوشته شدن دسترسی داره و این دسترسی به همینجا محدود میشه و ما به کدی که خارج از این { }
و داخل یک { }
نوشته شده دسترسی نداریم.
البته این استثنا را به خاطر داشته باشید که قضیه برای block scope والد فرق میکنه، یعنی چی ؟ یعنی اینکه در مثال بالا ما یک فانکشن با نام test
داریم که یک block scope را ایجاد کرده و ما در بالاترین سطح این فانکشن یک متغیر با نام funcVar
ایجاد کردیم و سپس یک block scope جدید در داخل block scope فانکشن test
ایجاد کردیم، از اینرو فانکشن test
به عنوان والد در نظر گرفته میشه و ما داخل block scope فرزند که if ما باشه به متغیر funcVar
دسترسی داریم ولی خارج از if به متغیری که داخل if نوشته شده دسترسی نداریم.
Function Scope
scope آخر ما Function Scope هست و این scope با متغیرهای ایجاد شده با کلمه var
در ارتباط هست. متغیرهای var
در جاوااسکریپت مرتبط هستند با scope فانکشن ما و این به آن معناست که فقط به { }
فانکشن اهمیت میدن.
function test() {
var funcVar = "Func"
if (true) {
var ifVar = "If"
console.log(funcVar, ifVar)
// خروجی: "Func", "If"
}
console.log(funcVar, ifVar)
// خروجی: "Func", "If"
}
مثال قبل دقیقا مشابه مثالی هست که در بخش block scope زدیم، با این تفاوت که اینجا متغیر ifVar
را به جای استفاده از کلمه const
با کلمه var
در تعریف کردیم و همینطور که میبینید اینبار کد ما بر خلاف مثال قبلی بدون هیچگونه اروری کار کرد و این بخاطر این هست که کلمه var
سطح block را نادیده میگیره و با وجود اینکه متغیر ifVar
داخل block شرط ما نوشته شده کار میکنه.
قبلتر در مقاله ایی با عنوان متغیرها در جاوااسکریپت توضیح جامع و کاملی درمورد متغیرها دادیم و همچنین در ادامه در مقالاتی مجزا به شرح اختصاصی ایجاد متغیر با کلمه let
و var
و const
پرداختیم و لپ کلام یک نکته ایی که در این مقالات بهش اشاره شد این بود که تا جای ممکن متغیرهای خودرا با const
یا let
ایجاد کنید و از ایجاد متغیر با کلمه var
بپرهیزید.
متغیرهای مختلف با نام یکسان
یک نکته خیلی مهم درباره Function scope ها داشتن دو متغیر با نام های یکسان داخل یک فانکشن هست.
function test() {
const a = "Func"
if (true) {
const a = "If"
console.log(a)
// Prints: "If"
}
console.log(a)
// Prints: "Func"
}
در مثال بالا ما یک متغیر با نام a
داریم که در بالاترین سطح فانکشن test
نوشته شده و یک متغیر دیگه با همان نام a
داریم که داخل بلوک if
نوشته شده. وقتی داخل بلوک if
از متغیر a
لاگ میگیریم با مقدار متغیر a
که داخل همان بلوک if
نوشته شده مواجه میشیم و وقتیم که خارج از بلوک if
از a
لاگ میگیریم با مقدار متغیری که در بالاترین سطح فانکشن test
ایجاد شده مواجه میشیم.
نتیجه ایی که از کد بالا میگیریم این هست که این 2 متغیر هیچ تداخلی باهم ندارن و به عنوان 2 متغیر مختلف باهاشون رفتار میشه و تنها شباهتشون اسم اونهاست و وقتی سعی میکنیم ازشون استفاده کنیم، نزدیکترین اونها در دسترس ما هستند.
کد بالا کار درست و اصولی نیست چراکه هم نامگذاری های یکسان با اصول کدنویسی تمیز سازگار نیست و هم ما عملا نمیتونیم از متغیر a
که در بالاترین سطح فانکشن test
نوشته شده استفاده کنیم.