النطاق في JavaScript
النطاق يحدد أين يمكن الوصول إلى المتغير. فكّر فيه مثل بطاقة الهوية — هوية المدينة تعمل فقط داخل المحافظة، وهوية المحافظة لا تفيدك في الخارج. المتغيرات تعمل بنفس الطريقة: بمغادرتك نطاقها، تصبح غير قابلة للوصول.
📖 ملخص
النطاق العام (Global Scope)
المتغيرات المعلنة بـ var أو let أو const في المستوى الأعلى لها نطاق عام ويمكن الوصول إليها من أي مكان.
<script>
var globalVar = "أنا متغير عام";
function test() {
console.log(globalVar); // يمكن الوصول إليه
}
test();
</script>
المتغيرات العامة مريحة لكنها قد تلوث مساحة الأسماء — مع نمو المشروعات، تصبح تعارضات الأسماء حتمية. قلل من استخدام المتغيرات العامة.
نطاق الدالة (Function Scope)
المتغيرات المعلنة بـ var تتبع نطاق الدالة: توجد فقط داخل الدالة الحالية وتختفي خارجها.
<script>
function greet() {
var message = "مرحباً";
console.log(message); // يعمل بشكل صحيح
}
greet();
try {
console.log(message); // خطأ مرجعي!
} catch(e) {
console.log("فشل الوصول: " + e.message);
}
</script>
مشكلة شائعة مع نطاق دالة var: استخدام var في حلقة for يعني أن المتغير "يتسرب" بعد انتهاء الحلقة.
نطاق الكتلة (Block Scope)
المتغيرات المعلنة بـ let و const تتبع نطاق الكتلة: توجد فقط داخل {} الحالية.
<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 في عدادات الحلقات.
سلسلة النطاق (Scope Chain)
النطاقات الداخلية يمكنها الوصول إلى متغيرات النطاقات الخارجية — مثل استخدامك لأشياء من المنزل في المدرسة (إذا أحضرتها). عند البحث عن متغير، يبحث JavaScript من النطاق الحالي向外، طبقة بطبقة، حتى يصل إلى النطاق العام. هذه السلسلة تسمى سلسلة النطاق.
<script>
const city = "بكين";
function outer() {
const district = "هايديان";
function inner() {
const street = "زونغوانغتشون";
console.log(street, district, city); // جميعها قابلة للوصول
}
inner();
}
outer();
</script>
النطاقات الخارجية لا يمكنها الوصول إلى متغيرات النطاقات الداخلية — مثل عدم قدرتك على الوصول إلى جيب طالب من خارج الفصل.
الرفع (Hoisting)
إعلانات var "ترفع" إلى أعلى نطاقها، لكن التعيينات لا تُرفع:
<script>
console.log(a); // undefined (ليس خطأً!)
var a = 5;
// ترتيب التنفيذ الفعلي: var a; → console.log(a); → a = 5;
</script>
let و const تُرفع أيضاً، لكن الوصول إليها قبل الإعلان يسبب خطأً — هذه المنطقة تسمى المنطقة الزمنية الميتة (TDZ):
<script>
try {
console.log(b); // خطأ مرجعي!
} catch(e) {
console.log("المنطقة الزمنية الميتة: " + e.message);
}
let b = 5;
</script>
الخلاصة: استخدم دائماً let و const؛ تجنب مشكلات رفع var.
الإغلاق (Closures) بشكل حدسي
الإغلاق = دالة + بيئة المتغيرات التي وُلدت فيها. مثل شخص غادر مسقط رأسه لكنه لا يزال يتذكر شكله — حتى لو تغير hometown لاحقاً، ذاكرته عنه تبقى كما هي.
<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 بإحكام، فيبقى حياً. هذا هو الإغلاق.
حالات استخدام عملية للإغلاق
- العدادات: تسجيل الحالة بدون متغيرات عامة (كما هو موضح أعلاه)
- المتغيرات الخاصة: المتغيرات داخل الإغلاقات لا يمكن الوصول إليها مباشرة من الخارج — فقط من خلال الدوال المرجعة
- دوال الاستدعاء: الحفاظ على مراجع للمتغيرات في مستمعي الأحداث والمؤقتات
مثال: فرق var وlet في الحلقات
<!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>
مثال: عرض سلسلة النطاق
<!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>
مثال: عداد الإغلاق مع متغيرات خاصة
<!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>
مثال: مقارنة الرفع
<!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>
❓ أسئلة شائعة
var في حلقة for يسبب خروج جميع الاستدعاءات بالقيمة نفسها؟var لها نطاق الدالة — بعد انتهاء الحلقة، يوجد فقط i واحد، وجميع الاستدعاءات تشترك فيه (الذي يساوي القيمة النهائية للحلقة عند ذلك). مع let، كل تكرار يحصل على نسخة مستقلة من i.null.let أم const؟const كخيار افتراضي؛ استخدم let فقط عندما تحتاج إلى إعادة التعيين. هذه العادة تمنعك من تعديل المتغيرات التي لا ينبغي أن تتغير عن طريق الخطأ.📝 تمارين
- اكتب دالة
makeGreeter(greeting)تعيد دالة تستقبل اسماً وتعيدgreeting + "، " + name. أنشئ دالتي تحية بتحيتين مختلفتين وتحقق أنهما لا تتداخلان. - اكتب دالة
createWallet(initialBalance)تعيد ثلاث طرق:depositوwithdrawوgetBalance. يجب ألا يكونbalanceقابلاً للوصول مباشرة من الخارج (متغير خاص) — لا يمكن التلاعب به إلا من خلال هذه الطرق. - اشرح مخرجات الكود التالي وسببها:
<script>
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100);
}
</script>
ثم عدّله ليطبع 0، 1، 2.



