لو فاكر إن “المتغيرات” ده درس تمهيدي بناخده في أول يوم برمجة ونقلب الصفحة، اسمحلي أقولك إنك فايتك كتير.
في JavaScript تحديدًا، المتغيرات هي الخيط اللي لو مسكته صح، هتفهم اللغة دي شغالة إزاي من جوه: من أول الـ Memory Management لحد الـ Closures والـ Performance Optimization.
المقالة دي مش معمولة عشان تقولك “استخدم const ومستخدمش var”. المقالة دي معمولة عشان تشرحلك ليه، وإيه اللي بيحصل في الـ CPU والـ RAM لما بتكتب سطر كود بسيط زي let x = 10.
هناخدك في رحلة من “مجرد مستخدم للغة” لـ “مهندس فاهم الـ Engine”.
1. انسى “الصندوق”: فلسفة المتغيرات (Variable Binding)
أشهر تشبيه بيتشرح للمبتدئين:
“المتغير عامل زي الصندوق، بنحط جواه قيمة.”
التشبيه ده “لطيف” للبدايات، بس كارثي لو عايز تبقى Senior. لأنه بيخليك تتخيل إن القيمة “جوه” المتغير.
في الحقيقة، المتغير في JavaScript (وفي معظم اللغات الحديثة) هو مجرد Binding أو “رباط”. تخيله كـ Label أو “لافتة” مربوطة بخيط، والخيط ده واصل لمكان معين في الذاكرة (Memory Address).
لما بتقول:
let user = { name: 'Ali' };
إنت بتعمل 3 حاجات:
- بتنشئ Object
{ name: 'Ali' }في مكان ما في الذاكرة (في الـ Heap). - بتنشئ اسم
userفي الـ Current Scope. - بتربط (Bind) الاسم
userبمكان الـ Object ده.
ليه التفصيلة دي تفرق؟ لأنك لو فاكر إنه صندوق، مش هتفهم يعني إيه:
const a = { x: 1 };
const b = a; // إحنا مش بننسخ الصندوق، إحنا بنعمل خيط جديد لنفس المكان
b.x = 2;
console.log(a.x); // 2
لو هما صندوقين، المفروض a ميتتأثرش. بس لأنهم “خيطين” واصلين لنفس المكان، أي تعديل من أي خيط بيسمّع في المكان نفسه.
2. دورة حياة المتغير: The 3 Pillars
أي متغير بيمر بـ 3 مراحل، والـ JavaScript Engine بيتعامل مع كل مرحلة بشكل منفصل، وده سر مشاكل كتير زي الـ TDZ والـ Hoisting:
Declaration (الإعلان): اللغة بتسجل إن “في متغير اسمه
xموجود في الـ Scope ده”. بس لسه مفيش قيمة، ورسميًا هو لسه مش جاهز للاستخدام.Initialization (التهيئة): اللغة بتحجز مكان في الذاكرة للمتغير ده، وبتحطله قيمة مبدئية (غالبًا
undefinedفي الـvar، أو بتسيبه “uninitialized” فيlet/const).Assignment (الإسناد): اللغة بتحط القيمة الحقيقية اللي إنت كتبتها (مثلاً
10أو"Hello") في المكان ده.
المشاكل بتحصل لما بنحاول نستخدم متغير عدى بمرحلة الـ 1 بس لسه معداش بـ 2، أو عدى بـ 1 و 2 بس لسه 3.
3. أين تعيش المتغيرات؟ (Stack vs Heap)
عشان تبقى جامد، لازم تعرف الكود بتاعك بياكل رامات إزاي. الـ JavaScript Engine (زي V8 في Chrome) بيقسم الذاكرة لنوعين:
أ) Stack Memory (السهل الممتنع)
دي ذاكرة سريعة جدًا، منظمة، وبسيطة. بيتخزن فيها:
- الـ Primitives (الأرقام، الـ Booleans، الـ
undefined، الـnull، والـ Symbols) لأن حجمهم ثابت ومعروف. (ملحوظة للمحترفين: الـ Primitives ممكن تروح الـ Heap لو جوه Closure، والـ V8 هو اللي بيقرر ده). - الـ References (مؤشرات) للأشياء الكبيرة.
ب) Heap Memory (المخزن الكبير)
دي ذاكرة عشوائية وكبيرة، بيتخزن فيها أي حاجة حجمها مش ثابت أو كبير:
- Objects
- Arrays
- Functions
مثال للتوضيح:
let age = 25; // بتتخزن كلها في الـ Stack
let user = { id: 1 }; // المتغير 'user' (كمؤشر) في الـ Stack، والقيمة `{ id: 1 }` في الـ Heap
ليه ده مهم؟ عشان الـ Mutability. لما بتعدل في Primitive، إنت فعليًا بتغير القيمة في الـ Stack (أو بتشاور على مكان جديد). لكن لما بتعدل في Object، إنت بتسيب الـ Pointer اللي في الـ Stack زي ما هو، وبتروح تعدل في الداتا اللي في الـ Heap. عشان كده const بتسمح بتعديل الـ Objects (لأن الـ Pointer اللي في الـ Stack متغيرش)، بس مبتسمحش بـ Reassignment (لأن ده معناه تغيير الـ Pointer).
4. Scope & Scope Chain: الفيزياء الخاصة بالكود
الـ Scope هو “مدى الرؤية”. مين شايف مين؟ قاعدة ذهبية: “اللي جوه شايف اللي بره، بس اللي بره مش شايف اللي جوه”.
لما الـ Engine بييجي يدور على قيمة متغير، بيمشي في رحلة اسمها Different scopes lookup أو Scope Chain:
- يدور في الـ Local Scope الحالي.
- ملقاش؟ يطلع للـ Parent Scope.
- ملقاش؟ يفضل يطلع لحد ما يوصل للـ Global Scope.
- ملقاش؟ يرمي
ReferenceError.
ده بيفسر ليه الـ Global Variables “غالية” في الـ Performance (لأن الـ Engine بيمشي المشوار كله عشان يوصلها)، وليه هي “خطر” (لأن أي حد في السلسلة ممكن يغيرها).
5. رحلة عبر الزمن: Hoisting والـ Dead Zone
خلينا نقفل ملف var vs let vs const للأبد، بس من منظور الـ Architecture مش الحفظ.
أولاً: var (الإرث الثقيل)
Scope: Function Scope. يعني
ifوforمش بيعملوا scope جديد ليها. بتدلق بره البلوكات دي.Hoisting: بيحصلها “رفع” لأول الـ Function.
- تريك: هي بتترفع كـ Declaration و Initialization بـ
undefined. - عشان كده لو استخدمتها قبل ما تعرفها، الكود مش بيضرب Error، بس بيرجع
undefined. وده أخطر نوع Bugs.
كارثة تانية لـ
var:var x = 10; console.log(window.x); // 10 (Browser Global Pollution)الـ
varلما بتبقى global بتلزق نفسها في الـwindowobject، وده ممكن يبوظ مكتبات تانية شغالة في الصفحة.letوconstأنضف ومش بيعملوا كده.- تريك: هي بتترفع كـ Declaration و Initialization بـ
ثانياً: let & const (العصر الحديث)
- Scope: Block Scope. بتحترم الـ
{}. - Hoisting: بيحصلها Hoisting؟ آه والله! بس الفرق في الـ Initialization.
- الـ Engine عارف إن المتغير موجود (Declaration تم)، بس المتغير لسه في حالة “Uninitialized”.
- هو محجوز له مكان، بس الـ Engine حاطط عليه “شمع أحمر” ممنوع الاقتراب منه لحد سطر التعريف.
- الفترة الزمنية دي (من بداية الـ Scope لحد سطر التعريف) اسمها TDZ (Temporal Dead Zone). منطقة الموت المؤقت. أي محاولة لمس المتغير فيها بتعمل
ReferenceError.
ثالثاً: const لا تعني “ثابت”
const بتحمي الـ Binding فقط.
- لو
const x = 10، مستحيل تغيرx. - لو
const arr = []، عادي جدًا تعملarr.push(1). الصندوق نفسه مفتوح، بس ممنوع تبدل الصندوق بصندوق تاني.
للمحترفين فقط: لو عايز تحمي الـ Object نفسه من التعديل (Immutability)، استخدم
Object.freeze(arr)، دي حاجة وتلك حاجة.
رابعاً: var vs let في الـ Loops
جرب الكود ده:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// النتيجة: 3, 3, 3 😱
لأن var معندهاش block scope، فالـ i هي نفس المتغير لكل اللفات. لما الـ timeout اشتغل، كانت الـ loop خلصت و i بقت 3.
عدلها لـ let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// النتيجة: 0, 1, 2 ✅
هنا let بتعمل scope “جديد” لكل لفة. كل i هي نسخة منفصلة ومعزولة عن التانية.
6. Closures: السحر الحقيقي
الـ Closure هو الدليل القاطع إن دوال JavaScript مش مجرد سطور كود، دي كائنات حية شايلة “شنطة ذكريات”.
لما Function بتتعرف جوه Function تانية، وتخرج منها (Return)، بتفضل محتفظة بـ Reference للـ Scope اللي اتولدت فيه.
function outer() {
let count = 0; // المفروض المتغير ده يموت بانتهاء الدالة
return function inner() {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // 1
counter(); // 2
إزاي count لسه عايش؟ لأن inner عملت عليه “Closure”. الـ Engine كان ذكي، لاحظ إن inner محتاجة count، فاحتفظ بـ Reference للـ Lexical Scope اللي فيه count.
⚠️ تحذير هام جدًا: الـ Closures سلاح ذو حدين. لو الـ Closure محتفظ بـ reference لحاجات كبيرة مش محتاجها، ده بيعمل Memory Leak لأن الـ Garbage Collector مش هيقدر يمسحها طول ما الـ Closure عايش. استخدمها بحكمة.
تطبيق عملي (Data Privacy): الـ Closure هو الطريقة الوحيدة (قبل الـ Private Fields #) عشان تعمل متغيرات خاصة محدش يقدر يلعب فيها من بره.
7. Zero-Cost Abstractions: نصائح للـ Seniors
إزاي تكتب كود متغيرات Efficient؟
- Avoid Global Variables: مش بس عشان النظافة، عشان الـ Lookup time في الـ Scope Chain، وعشان بتمنع الـ Engine من إنه يعمل Optimizations معينة و Garbage Collection فعال.
- Declare Variables Close to Usage: متعملش زي لغة C زمان وتعرف كل حاجة فوق. عرف المتغير قبل استخدامه مباشرة. ده بيقلل الـ Live Range بتاعه وبيسهل على الـ Garbage Collector شغله.
- Use
constby Default: ده مش بس Style. ده بيسهل على الـ Engine يعرف إن الـ Binding ده مش هيتغير، وساعات بيساعد في الـ Optimization. - Hidden Classes (V8 Magic): لو بتعمل Objects، حاول تثبت شكلها (الـ Keys وترتيبها).
الـ V8 بيعمل “Hidden Classes” عشان يسرع الوصول للـ Properties. تغيير شكل الـ Object بيجبر الـ Engine يعمل كلاسات جديدة، وده بيبطئ الكود في الـ High Performance Apps.// ❌ وحش - كل Object ليه شكل مختلف (Hidden Class مختلف) function Point(x, y) { this.x = x; if (y) this.y = y; // مرة بـ y ومرة من غير } // ✅ صح - شكل ثابت دايماً function Point(x, y) { this.x = x; this.y = y ?? 0; // دايماً في x و y بنفس الترتيب }
الخلاصة
المتغير مش مجرد “مخزن قيم”. هو عقد (Contract) بينك وبين الـ Engine:
- بـ
var، إنت بتعمل عقد متساهل، قديم، ومليان ثغرات. - بـ
let، إنت بتعمل عقد محدد بمدة ومكان (Block Scoped) وقابل للتجديد (Reassignable). - بـ
const، إنت بتعمل عقد صارم ومحدد وغير قابل للفسخ (Immutable Binding).
افهم الـ Memory Model، وافهم الـ Scope Chain، وهتلاقي مشاكل الـ undefined والـ Race Conditions اختفت من حياتك.
نصيحة أخيرة: الكود بيتكتب مرة وبيتقري 100 مرة. استخدم أسماء متغيرات واضحة توصف المحتوى بدقة.
let dوحشة،let daysSinceLoginتحفة فنية.
