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

Scope چیست؟

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

const outer = "Out"

function test() {
  const inner = "In"
  console.log(outer, inner)
  // Prints: "Out", "In"
}

test()
console.log(outer, inner)
// Throws Uncaught Reference Error: inner is not defined

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

این مثال از دو Scope از چهار نوع Scope موجود در جاوااسکریپت استفاده می‌کند تا کدی که داریم را به گونه‌ای جدا کند که فقط بخش‌های خاصی از کد به متغیرهای خاصی دسترسی داشته باشند. این موضوع دلیل اصلی وجود سطوح مختلف Scope است.

سطوح مختلف Scope

چهار سطح Scope وجود دارد که عبارتند از:

  1. Global Scope
  2. Module Scope
  3. Block Scope
  4. Function Scope

در ادامه هر کدام از این سطوح را باهم بررسی خواهیم کرد.

Global Scope

برای شروع درمورد ساده‌ترین Scope صحبت خواهیم کرد. بسیاری از توسعه‌دهندگان از آن در تمام کدهای خود استفاده می‌کنند، زیرا به نوعی تمام محدودیت‌هایی را که Scopeهای دیگر اعمال می‌کنند نادیده می‌گیرد. برای درک Global Scope تصور کنید که کد HTML و JS زیر را داریم.

<script src="script.js"></script>
// script.js

const a = 1
console.log(a)
// Prints: 1

این مثال ساده از Global Scope برای تعریف متغیرهای استفاده می‌کند. طبیعتا زمانی که متغیری را در سطح بالاتر از یک فایل تعریف کنیم (خارج از هر تابع و یا {}ای) آن را Global Scope در نظر می‌گیریم و می‌توانیم در هر قسمت برنامه به آن متغیرها دسترسی داشته باشیم. این دسترسی سراسری در ابتدا نوشتن کد را آسان‌تر می‌کند، زیرا نیازی نیست نگران بلاک شدن متغیرها توسط انواع Scope های مختلف باشیم، اما با پیچیده‌تر شدن کدها مدیریت آن به شدت دشوار می‌شود. این مشکل زمانی که چندین فایل داریم بیشتر خواهد بود.

<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استفاده کنیم. دلیل این موضوع این است که متغیر script-1.jsبه صورت سراسری تعریف شده است و می‌تواند در هر قسمتی از کد مورد استفاده قرار بگیرد.

Module Scope

Module Scope بسیار شبیه به Global Scope است اما یک تفاوت جزئی با آن دارد. متغیرهای Scopeهای سطح پایین فقط در فایلی که تعریف شده‌اند در دسترس هستند. این به این معنی است که نمی‌توانیم از آن متغیرها در فایل‌های دیگری استفاده کنیم. برای ورود به Module Scope باید از type="module"در تگ‌های اسکریپت خود استفاده کنیم.

<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 Scope برای متغیرها حذف شده و در عین حال مزایای استفاده از هر متغیر در هر قسمت از فایلی که در آن تعریف شده است حفظ می‌شود. به این دلیل است که Module Scope یکی از انواع Scope های محبوب به ‌شمار می‌آید.

Block Scope

Block Scope یکی از ساده‌ترین مفاهیم است زیرا با {}مطابقت دارد. اساساً زمانی که ما کدی را داخل {} قرار می‌دهیم Block Scope مربوط به خود را ایجاد می‌کند. این بدان معناست که مواردی مانند توابع، دستورات if، حلقه‌ها و غیره همگی Block Scope مخصوص به خود را دارند.

function test() {
  const funcVar = "Func"

  if (true) {
    const ifVar = "If"
    console.log(funcVar, ifVar)
    // Prints: "Func", "If"
  }

  console.log(funVar, ifVar)
  // Throws Uncaught Reference Error: ifVar is not defined
}

مثال بالا دارای دو Block Scope جداگانه می‌باشد. هر Block Scope به عنوان کد بین {} تعریف می‌شود و هر بلاک به تمام متغیرهایی که بین دو آکولاد قرار دارند دسترسی دارد. اما این دسترسی در مجموعه دیگری از آکولادها و همچنین تمام متغیرهای Scope اصلی آن وجود ندارد.

در این مثال ما دو بلاک داریم که شامل تابع testو دستور ifمی‌باشند. اسکاپ مربوط به test به تمام متغیرهای تابع که در مجموعه دیگری از {}های تودرتو قرار ندارند دسترسی دارد، به‌طور واضح‌تر یعنی این که دسترسی به متغیر funcVarوجود دارد. Scope تابع test همچنین به هر متغیر سطح Global و یا Madule نیز دسترسی خواهد داشت زیرا که این متغیرها، parent اسکاپ test هستند.

Scope دوم ما دستورif است و دسترسی آن Scope شامل ifVarو هر موردی که تابع test به آن دسترسی دارد می‌باشد، زیرا تابع test اسکاپ parent برای دستورif محسوب می‌شود.

نکته مهمی که هنگام استفاده از {} وجود دارد این است که Scope داخلی به متغیرهای اسکاپ اصلی که در داخل آن قرار گرفته است دسترسی دارد اما Scope اصلی به متغیرهای Scopeای که در داخل خود دارد دسترسی ندارد. این قانون در واقع برای همه Scopeهای موجود صادق است.

{
  const a = 10
}

console.log(a)
// Throws Uncaught Reference Error: a is not defined

حتی می‌توانیم در هر قسمت از کد خود از آکولاد استفاده کنیم و یک Scope ایجاد کنیم. استفاده از این روش رایج نیست اما گاها می‌تواند مفید باشد.

Function Scope

آخرین Scopeای که آن را بررسی خواهیم کرد Function Scope می‌باشد. این Scope به کلمه کلیدی varمربوط می‌شود به این صورت که متغیرهایی که با کلمه کلیدی var تعریف می‌شوند به جای سطح بلاک در سطح تابع قرار می‌گیرند، به این معنی که آن‌ها فقط به {}های یک تابع اهمیت می‌دهند.

function test() {
  var funcVar = "Func"

  if (true) {
    var ifVar = "If"
    console.log(funcVar, ifVar)
    // Prints: "Func", "If"
  }

  console.log(funVar, ifVar)
  // Prints: "Func", "If"
}

کدی که اینجا داریم دقیقاً مشابه کد مثال سطح بلاک است، اما این بار برای تعریف متغیرها به جای constاز var استفاده می‌کنیم. در این مثال خواهیم دید که کد ما به خوبی کار می‌کند و دلیل آن این است که کلمه کلیدی varمحدوده سطح بلاک را نادیده می‌گیرد، بنابراین اگر چه ifVarدر بلاک ifتعریف شده است، اما برای Function Scope اهمیتی ندارد.

چندین متغیر با نام یکسان

یکی از نکات مهمی که باید در مورد این Scoping درک کنیم این است که کد ما هنگامی که چندین متغیر با نام یکسان داریم چگونه عمل می‌کند.

function test() {
  const a = "Func"

  if (true) {
    const a = "If"
    console.log(a)
    // Prints: "If"
  }

  console.log(a)
  // Prints: "Func"
}

در این مثال، متغیر aرا هم در داخل تابع testو هم در بلاک ifتعریف می‌کنیم. هنگامی که a را از داخل بلاک if در کنسول نمایش می‌دهیم، مقدار a را از Scope مربوط به if دریافت می‌کنیم. اما وقتی که وقتی a را خارج از بلاک if در کنسول نمایش می‌دهیم، مقدار آن را از Scope مربوط به تابع test دریافت می‌کنیم.

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

جمع‌بندی

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