کجا یک متغیر در دسترس است؟ Scope و Inheritance

در درس ۱۶-۱، به‌طور مختصر گفتیم که یک متغیر، می‌تونه روی :root یا روی هر Selector دیگه‌ای تعریف بشه، و فقط همون عنصر و فرزندانش بهش دسترسی دارن. حالا وقتشه این مفهوم رو، با دقت کامل، ببینیم؛ چون فهم درست Scope (دامنه دید) و Inheritance (وراثت) در Custom Properties، پایه اصلی استفاده درست از این ابزار در پروژه‌های واقعیه.

یادآوری سریع: Inheritance در فصل ۲

در فصل ۲ (درس ۲-۴) دیدیم که بعضی Propertyهای معمولی CSS (مثل color یا font-family)، به‌طور طبیعی، از والد به فرزند به ارث می‌رسن؛ در حالی که بعضی دیگه (مثل margin) این‌طور نیستن. نکته مهم درباره Custom Properties اینه: تمام Custom Propertyها، صرف‌نظر از نوع مقداری که ذخیره می‌کنن، همیشه Inheritance‌پذیرن.

یک مثال ساده از Inheritance

.parent {
  --text-color: darkred;
}
<div class="parent">
  <p style="color: var(--text-color);">این متن رنگش را از والد می‌گیرد.</p>
</div>

در این مثال، متغیر --text-color، روی .parent تعریف شده. پاراگراف داخلش، بدون این‌که خودش این متغیر رو تعریف کرده باشه، بهش دسترسی داره؛ چون Custom Properties، دقیقاً مثل color در فصل ۲، از والد به فرزند، به‌طور خودکار، ارث می‌رسن.

Scope: محدوده دیدی که یک متغیر در آن معتبر است

.card {
  --card-padding: 24px;
  padding: var(--card-padding);
}

.button {
  padding: var(--card-padding); /* این کار نمی‌کند، چون .button داخل .card نیست */
}
<div class="card">کارت</div>
<button class="button">دکمه</button>

در این مثال، --card-padding، فقط داخل .card و فرزندانش معتبره. چون .button، از نظر ساختار HTML، فرزند .card نیست (یک عنصر کاملاً جدا و مستقله)، این متغیر، اصلاً بهش نمی‌رسه؛ و var(--card-padding) روی .button، بدون Fallback، به یک مقدار نامعتبر منجر می‌شه.

Override کردن یک متغیر در یک Scope داخلی‌تر

:root {
  --text-color: black;
}

.highlight-box {
  --text-color: crimson;
}
<p style="color: var(--text-color);">متن معمولی (مشکی)</p>

<div class="highlight-box">
  <p style="color: var(--text-color);">متن داخل باکس (قرمز)</p>
</div>

یکی از قدرتمندترین ویژگی‌های Scope در Custom Properties، امکان Override کردن یک متغیر، در یک سطح داخلی‌تر از HTMLه. پاراگراف اول، چون هیچ Scope نزدیک‌تری متغیر رو تغییر نداده، از مقدار :root (یعنی black) استفاده می‌کنه. اما پاراگراف دوم، چون داخل .highlight-boxه، و اون عنصر، دوباره همون متغیر رو، با مقدار جدیدی (crimson) تعریف کرده، این مقدار جدید رو می‌گیره؛ دقیقاً همون منطق Cascade که در فصل ۲ دیدیم (نزدیک‌ترین تعریف، برنده می‌شه).

مقایسه Scope در Custom Properties با Specificity معمولی

این رفتار، شبیه چیزیه که در فصل ۲ با Cascade دیدیم، ولی با یک تفاوت مهم: اینجا، بحث سر Specificity دو Selector نیست؛ بحث سر عمق ساختاری در DOMه. یعنی هر عنصری، مقدار یک Custom Property رو، از نزدیک‌ترین جدی که اون رو تعریف کرده، می‌گیره؛ نه لزوماً از جدی که Specificity بالاتری داره.

یک مثال کاربردی: تم‌بندی محلی (Local Theming)

.theme-light {
  --bg-color: white;
  --text-color: black;
}

.theme-dark {
  --bg-color: #121212;
  --text-color: #eeeeee;
}

.card {
  background-color: var(--bg-color);
  color: var(--text-color);
}
<div class="theme-light">
  <div class="card">کارت با تم روشن</div>
</div>

<div class="theme-dark">
  <div class="card">همین کارت، ولی با تم تاریک</div>
</div>

در این مثال، .card، هیچ رنگ ثابتی نداره؛ فقط به دو متغیر (--bg-color و --text-color) ارجاع می‌ده. این دو متغیر، بسته به این‌که کارت، داخل کدوم Scope (تم روشن یا تیره) قرار گرفته، مقدار متفاوتی می‌گیرن؛ نتیجه این می‌شه که همون یک کلاس .card، بدون نیاز به نوشتن نسخه‌های جداگانه، می‌تونه در هر دو تم، به‌درستی ظاهر بشه.

یک نکته مهم: Custom Properties به فرزندان می‌رسند، نه به Siblingها یا والدها

.sibling-a {
  --shared-value: 10px;
}

.sibling-b {
  padding: var(--shared-value); /* این کار نمی‌کند */
}
<div class="sibling-a">اول</div>
<div class="sibling-b">دوم</div>

چون Inheritance، فقط از والد به فرزند جریان داره (نه بین Siblingها یا از فرزند به والد)، دو عنصر div که در سطح یکسان و کنار هم قرار دارن، هیچ متغیری رو با هم به اشتراک نمی‌ذارن؛ مگر این‌که اون متغیر، در یک والد مشترک (یا :root) تعریف شده باشه.

جدول جمع‌بندی رفتار Scope

سناریو آیا متغیر در دسترس است؟
متغیر روی :root، استفاده در هر عمقی از سند بله
متغیر روی یک والد، استفاده در فرزند مستقیم یا غیرمستقیم بله
متغیر روی یک عنصر، استفاده در یک Sibling خیر (مگر با تعریف مجدد یا والد مشترک)
متغیر روی یک فرزند، استفاده در والدش خیر

نکات مهم

  • برخلاف Propertyهای معمولی CSS، تمام Custom Properties، صرف‌نظر از نوع مقدار، همیشه Inheritance‌پذیر هستند.
  • Scope یک متغیر، به عنصری که آن را تعریف کرده و فرزندانش (در هر عمقی) محدود است؛ نه به Siblingها یا والدهای آن عنصر.
  • تعریف مجدد یک متغیر در یک Scope داخلی‌تر، مقدار آن را، فقط برای همان عنصر و فرزندانش، Override می‌کند.

اشتباهات رایج

  • تلاش برای استفاده از یک متغیر، در عنصری که Sibling یا والد (نه فرزند) عنصر تعریف‌کننده آن است.
  • فراموش کردن این‌که Override کردن یک متغیر در یک Scope داخلی، فقط همان بخش را تحت تأثیر قرار می‌دهد، نه کل سند.
  • تصور اینکه Scope در Custom Properties، بر اساس Specificity کار می‌کند؛ در حالی که بر اساس عمق ساختاری در DOM عمل می‌کند.

جمع‌بندی

تمام Custom Properties، برخلاف بسیاری از Propertyهای معمولی CSS، همیشه از والد به فرزند به ارث می‌رسند. Scope یک متغیر، محدود به عنصری است که آن را تعریف کرده و فرزندانش؛ تعریف مجدد همان متغیر در یک Scope داخلی‌تر، مقدار آن را فقط برای همان بخش، بدون تأثیر بر بقیه سند، تغییر می‌دهد. این رفتار، پایه‌ی تکنیک‌های قدرتمندی مثل تم‌بندی محلی است که با یک مجموعه کلاس ثابت، ظاهر کاملاً متفاوتی می‌سازند.