JavaScript: النطاق في JavaScript

النطاق يحدد أين يمكن الوصول إلى المتغير. فكّر فيه مثل بطاقة الهوية — هوية المدينة تعمل فقط داخل المحافظة، وهوية المحافظة لا تفيدك في الخارج. المتغيرات تعمل بنفس الطريقة: بمغادرتك نطاقها، تصبح غير قابلة للوصول.

1. أنواع النطاقات وقواعدها

▶ مثال

JAVASCRIPT
// مثال على الكود في هذا القسم
const message = "مرحبا بالعالم!";
console.log(message);
▶ جرّب الكود

هذا أبسط مثال; الأقسام التالية ستشرح بالتفصيل.### (1) النطاق العام (Global Scope)

المتغيرات المعلنة بـ var أو let أو const في المستوى الأعلى لها نطاق عام ويمكن الوصول إليها من أي مكان.

HTML
<script>
var globalVar = "أنا متغير عام";
function test() {
  console.log(globalVar); // يمكن الوصول إليه
}
test();
</script>

المتغيرات العامة مريحة لكنها قد تلوث مساحة الأسماء — مع نمو المشروعات، تصبح تعارضات الأسماء حتمية. قلل من استخدام المتغيرات العامة.

(2) نطاق الدالة (Function Scope)

المتغيرات المعلنة بـ var تتبع نطاق الدالة: توجد فقط داخل الدالة الحالية وتختفي خارجها.

HTML
<script>
function greet() {
  var message = "مرحباً";
  console.log(message); // يعمل بشكل صحيح
}
greet();
try {
  console.log(message); // خطأ مرجعي!
} catch(e) {
  console.log("فشل الوصول: " + e.message);
}
</script>

مشكلة شائعة مع نطاق دالة var: استخدام var في حلقة for يعني أن المتغير "يتسرب" بعد انتهاء الحلقة.

(3) نطاق الكتلة (Block Scope)

المتغيرات المعلنة بـ let و const تتبع نطاق الكتلة: توجد فقط داخل {} الحالية.

HTML
<script>
if (true) {
  let x = 10;
  const y = 20;
  var z = 30;
  console.log("داخل الكتلة: x=" + x + "، y=" + y + "، z=" + z);
}
console.log("خارج الكتلة: z=" + z); // var غير مقيد بالكتلة
try {
  console.log(x); // x غير قابل للوصول هنا
} catch(e) {
  console.log("x غير قابل للوصول: " + e.message);
}
</script>

لهذا يجب استخدام let بدلاً من var في عدادات الحلقات.

(4) سلسلة النطاق (Scope Chain)

النطاقات الداخلية يمكنها الوصول إلى متغيرات النطاقات الخارجية — مثل استخدامك لأشياء من المنزل في المدرسة (إذا أحضرتها). عند البحث عن متغير، يبحث JavaScript من النطاق الحالي🦄، طبقة بطبقة، حتى يصل إلى النطاق العام. هذه السلسلة تسمى سلسلة النطاق.

HTML
<script>
const city = "بكين";
function outer() {
  const district = "هايديان";
  function inner() {
    const street = "زونغوانغتشون";
    console.log(street, district, city); // جميعها قابلة للوصول
  }
  inner();
}
outer();
</script>

النطاقات الخارجية لا يمكنها الوصول إلى متغيرات النطاقات الداخلية — مثل عدم قدرتك على الوصول إلى جيب طالب من خارج الفصل.

(5) الرفع (Hoisting)

إعلانات var "ترفع" إلى أعلى نطاقها، لكن التعيينات لا تُرفع:

HTML
<script>
console.log(a); // undefined (ليس خطأً!)
var a = 5;
// ترتيب التنفيذ الفعلي: var a; → console.log(a); → a = 5;
</script>

let و const تُرفع أيضاً، لكن الوصول إليها قبل الإعلان يسبب خطأً — هذه المنطقة تسمى المنطقة الزمنية الميتة (TDZ):

HTML
<script>
try {
  console.log(b); // خطأ مرجعي!
} catch(e) {
  console.log("المنطقة الزمنية الميتة: " + e.message);
}
let b = 5;
</script>

الخلاصة: استخدم دائماً let و const؛ تجنب مشكلات رفع var.

(6) الإغلاق (Closures) بشكل حدسي

الإغلاق = دالة + بيئة المتغيرات التي وُلدت فيها. مثل شخص غادر مسقط رأسه لكنه لا يزال يتذكر شكله — حتى لو تغير hometown لاحقاً، ذاكرته عنه تبقى كما هي.

HTML
<script>
function createCounter() {
  let count = 0;        // هذا المتغير "مقفل" داخل الإغلاق
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// متغير count لم يُدمر لأن الدالة المرجعة لا تزال "تتذكره"
</script>

createCounter انتهت من التنفيذ منطقياً — كان يجب أن يُدمر count. لكن الدالة المرجعة تحتفظ بـ count بإحكام، فيبقى حياً. هذا هو الإغلاق.

(7) حالات استخدام عملية للإغلاق


▶ مثال: فرق var وlet في الحلقات

HTML 📖 للعرض فقط
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>var مقابل let</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .block { margin: 16px 0; padding: 12px; background: #f5f5f5; border-radius: 6px; }
    .block h3 { margin-top: 0; }
    .var-result, .let-result { margin: 8px 0; font-weight: bold; }
    .var-result { color: #d9534f; }
    .let-result { color: #5cb85c; }
  </style>
</head>
<body>
  <h2>var مقابل let: اختلافات النطاق في الحلقات</h2>
  <div id="output"></div>
  <script>
    const varResults = [];
    const letResults = [];

    for (var i = 0; i < 3; i++) {
      varResults.push(`داخل الحلقة: i = ${i}`);
    }
    varResults.push(`خارج الحلقة: i = ${i} (var تسرب!)`);

    for (let j = 0; j < 3; j++) {
      letResults.push(`داخل الحلقة: j = ${j}`);
    }
    letResults.push(`الوصول إلى j خارج الحلقة → خطأ مرجعي (let لا يتسرب)`);

    document.getElementById("output").innerHTML = `
      <div class="block">
        <h3>إعلان var</h3>
        ${varResults.map(r => `<div class="var-result">${r}</div>`).join("")}
      </div>
      <div class="block">
        <h3>إعلان let</h3>
        ${letResults.map(r => `<div class="let-result">${r}</div>`).join("")}
      </div>
    `;
  </script>
</body>
</html>
41 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: عرض سلسلة النطاق

HTML 📖 للعرض فقط
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>سلسلة النطاق</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .scope { margin: 12px 0; padding: 12px; border-right: 4px solid #4a90d9; background: #f0f7ff; border-radius: 6px 0 0 6px; }
    .scope-inner { margin: 12px 0 12px 20px; padding: 12px; border-right: 4px solid #5cb85c; background: #f0fff0; border-radius: 6px 0 0 6px; }
    .scope-innermost { margin: 12px 0 12px 20px; padding: 12px; border-right: 4px solid #f0ad4e; background: #fffdf0; border-radius: 6px 0 0 6px; }
    code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 3px; }
  </style>
</head>
<body>
  <h2>سلسلة النطاق: البحث عن المتغيرات من الداخل إلى الخارج</h2>
  <div id="output"></div>
  <script>
    const country = "الولايات المتحدة";

    function outer() {
      const state = "كاليفورنيا";

      function middle() {
        const city = "سان فرانسيسكو";

        function inner() {
          const district = "ميشن";
          const result = `${district}، ${city}، ${state}، ${country}`;
          return result;
        }

        return inner();
      }

      return middle();
    }

    const final = outer();

    document.getElementById("output").innerHTML = `
      <div class="scope">
        <strong>النطاق العام</strong>: country = <code>"${country}"</code>
        <div class="scope-inner">
          <strong>نطاق دالة outer</strong>: state = <code>"كاليفورنيا"</code>
          <div class="scope-innermost">
            <strong>نطاق دالة middle</strong>: city = <code>"سان فرانسيسكو"</code>
            <div style="margin-top:12px; padding:12px; border-right:4px solid #d9534f; background:#fff0f0; border-radius:6px 0 0 6px;">
              <strong>نطاق دالة inner</strong>: district = <code>"ميشن"</code>
              <p>النطاق الأعمق يمكنه الوصول إلى جميع المتغيرات الخارجية → <strong>${final}</strong></p>
            </div>
          </div>
        </div>
      </div>
    `;
  </script>
</body>
</html>
50 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: عداد الإغلاق مع متغيرات خاصة

HTML 📖 للعرض فقط
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>الإغلاقات</title>
  <style>
    body { font-family: sans-serif; padding: 20px; text-align: center; }
    .counter { display: inline-block; padding: 20px; background: #f0f7ff; border-radius: 12px; margin: 12px; }
    .count { font-size: 48px; font-weight: bold; color: #4a90d9; }
    button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; margin: 4px; }
    button:hover { background: #357abd; }
    .reset { background: #d9534f; }
    .reset:hover { background: #c9302c; }
    .info { color: #888; font-size: 14px; margin-top: 8px; }
  </style>
</head>
<body>
  <h2>عداد الإغلاق</h2>
  <div class="counter">
    <div class="count" id="display">0</div>
    <button id="increment">+1</button>
    <button id="decrement">-1</button>
    <button id="reset" class="reset">إعادة تعيين</button>
    <div class="info">متغير count محمي بالإغلاق — لا يمكن تعديله مباشرة من الخارج</div>
  </div>
  <script>
    function createCounter() {
      let count = 0;
      return {
        increment() { return ++count; },
        decrement() { return --count; },
        reset() { count = 0; return count; },
        getCount() { return count; }
      };
    }

    const counter = createCounter();
    const display = document.getElementById("display");

    function updateDisplay() {
      display.textContent = counter.getCount();
    }

    document.getElementById("increment").addEventListener("click", function() {
      counter.increment();
      updateDisplay();
    });

    document.getElementById("decrement").addEventListener("click", function() {
      counter.decrement();
      updateDisplay();
    });

    document.getElementById("reset").addEventListener("click", function() {
      counter.reset();
      updateDisplay();
    });
  </script>
</body>
</html>
55 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: مقارنة الرفع

HTML 📖 للعرض فقط
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>الرفع</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .compare { display: flex; gap: 20px; margin: 16px 0; direction: ltr; }
    .col { flex: 1; padding: 16px; border-radius: 8px; }
    .col-var { background: #fff0f0; border: 2px solid #d9534f; }
    .col-let { background: #f0fff0; border: 2px solid #5cb85c; }
    h3 { margin-top: 0; }
    code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 3px; }
    pre { background: #fff; padding: 12px; border-radius: 6px; overflow-x: auto; direction: ltr; text-align: left; }
  </style>
</head>
<body>
  <h2>الرفع: var مقابل let</h2>
  <div id="output"></div>
  <script>
    var varBefore = "الوصول قبل إعلان var → " + typeof aVar;
    var aVar = 5;
    var varAfter = "الوصول بعد إعلان var → " + aVar;

    let letAfter = 5;
    let letBefore = "الوصول قبل إعلان let → يرمي خطأً مرجعياً!";

    document.getElementById("output").innerHTML = `
      <div class="compare">
        <div class="col col-var">
          <h3>var (تُرفع لكن لا تُعيّن)</h3>
          <pre><code>console.log(typeof aVar);  // ${varBefore}
var aVar = 5;
console.log(aVar);         // ${varAfter}</code></pre>
          <p>لا خطأ، لكن تحصل على <code>undefined</code> — خطأ أكثر دقة!</p>
        </div>
        <div class="col col-let">
          <h3>let (المنطقة الزمنية الميتة)</h3>
          <pre><code>console.log(bVar);  // ${letBefore}
let bVar = 5;</code></pre>
          <p>يرمي خطأً فوراً — تكتشف المشكلة مباشرة!</p>
        </div>
      </div>
    `;
  </script>
</body>
</html>
45 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

❓ أسئلة شائعة

س لماذا استخدام var في حلقة for يسبب خروج جميع الاستدعاءات بالقيمة نفسها؟
ج لأن var لها نطاق الدالة — بعد انتهاء الحلقة، يوجد فقط i واحد، وجميع الاستدعاءات تشترك فيه (الذي يساوي القيمة النهائية للحلقة عند ذلك). مع let، كل تكرار يحصل على نسخة مستقلة من i.
س هل الإغلاقات تسبب تسرب الذاكرة؟
ج الإغلاقات نفسها ليست تسرب ذاكرة — إنها تحفظ مراجع للمتغيرات عمداً. التسرب الحقيقي يحدث عندما لا تحتاج إغلاقاً لكنه لا يزال محتفظاً بسلسلة مرجعية أخرى، مما يمنع المتغير من أن يُجمع كقمامة. الحل: اجعل المرجع يساوي null.
س هل أستخدم let أم const؟
ج استخدم const كخيار افتراضي؛ استخدم let فقط عندما تحتاج إلى إعادة التعيين. هذه العادة تمنعك من تعديل المتغيرات التي لا ينبغي أن تتغير عن طريق الخطأ.

📖 ملخص

📝 تمارين

  1. اكتب دالة makeGreeter(greeting) تعيد دالة تستقبل اسماً وتعيد greeting + "، " + name. أنشئ دالتي تحية بتحيتين مختلفتين وتحقق أنهما لا تتداخلان.
  2. اكتب دالة createWallet(initialBalance) تعيد ثلاث طرق: deposit و withdraw و getBalance. يجب ألا يكون balance قابلاً للوصول مباشرة من الخارج (متغير خاص) — لا يمكن التلاعب به إلا من خلال هذه الطرق.
  3. اشرح مخرجات الكود التالي وسببها:
HTML
<script>
for (var i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
</script>

ثم عدّله ليطبع 0، 1، 2.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%