أساسيات JavaScript

المتغيرات: var و let و const

45 دقيقة الدرس 2 من 60

ما هي المتغيرات؟

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

المتغيرات أساسية في كل لغة برمجة. بدونها سيتوجب عليك كتابة كل قيمة مباشرة في برنامجك مما يجعل من المستحيل بناء أي شيء ديناميكي أو تفاعلي. تسمح المتغيرات لبرامجك بتذكر المعلومات والاستجابة لإدخال المستخدم وتغيير السلوك بناء على الشروط.

في JavaScript لديك ثلاث كلمات مفتاحية للتصريح عن المتغيرات: var و let و const. كل منها لها قواعد مختلفة حول النطاق وإعادة التعيين والرفع. فهم الفروقات بينها أمر حاسم لكتابة كود JavaScript نظيف وخال من الأخطاء.

مثال: المتغيرات كحاويات مسماة

// التصريح عن المتغيرات وتعيين القيم
let userName = 'أحمد';         // صندوق بملصق "userName" يحتوي "أحمد"
let userAge = 30;              // صندوق بملصق "userAge" يحتوي 30
let isLoggedIn = true;         // صندوق بملصق "isLoggedIn" يحتوي true

// استخدام المتغيرات
console.log(userName);         // المخرج: أحمد
console.log(userAge);          // المخرج: 30
console.log(isLoggedIn);       // المخرج: true

// استخدام المتغيرات في التعبيرات
console.log('مرحبا ' + userName + '! عمرك ' + userAge + ' سنة.');
// المخرج: مرحبا أحمد! عمرك 30 سنة.

التصريح عن المتغيرات بأستخدام var

كانت الكلمة المفتاحية var الطريقة الأصلية والوحيدة للتصريح عن المتغيرات في JavaScript من 1995 حتى 2015. بينما لا يزال var يعمل في JavaScript الحديث إلا أن له عدة غرائب ومشاكل تجعله إشكاليا. فهم var مهم لأنك ستصادفه في الأكواد القديمة لكن يجب تجنب استخدامه في الكود الجديد.

مثال: التصريح عن المتغيرات بأستخدام var

// تصريح var أساسي
var greeting = 'مرحبا بالعالم!';
console.log(greeting);   // المخرج: مرحبا بالعالم!

// إعادة تعيين متغير var
var score = 0;
score = 10;
score = score + 5;
console.log(score);      // المخرج: 15

// التصريح بدون تعيين (القيمة تكون undefined)
var result;
console.log(result);     // المخرج: undefined
result = 42;
console.log(result);     // المخرج: 42

// تصريحات متعددة في سطر واحد (غير مستحسن لسهولة القراءة)
var a = 1, b = 2, c = 3;
console.log(a, b, c);   // المخرج: 1 2 3

النطاق الوظيفي لـ var

أهم شيء يجب فهمه عن var هو نطاقه الوظيفي. المتغير المصرح عنه بـ var يكون متاحا في أي مكان داخل الدالة التي صرح فيها بغض النظر عن الكتل مثل عبارات if أو حلقات for أو حلقات while. إذا صرح خارج أي دالة يصبح متغيرا عاما متاحا في كل مكان.

مثال: var ذو نطاق وظيفي وليس نطاق كتلة

function demonstrateVarScope() {
    var x = 10;

    if (true) {
        var y = 20;           // هذا ليس محصورا في كتلة if!
        var x = 30;           // هذا يكتب فوق x أعلاه!
        console.log(x);      // المخرج: 30
        console.log(y);      // المخرج: 20
    }

    // y متاح هنا لأن var ذو نطاق وظيفي
    console.log(x);          // المخرج: 30 (تمت الكتابة فوقه!)
    console.log(y);          // المخرج: 20 (تسرب من كتلة if!)
}

demonstrateVarScope();

// قارن هذا مع حلقة for
function loopExample() {
    for (var i = 0; i < 5; i++) {
        // i متاح داخل الحلقة
    }
    // i لا يزال متاحا هنا! لم يبق داخل كتلة for.
    console.log(i);           // المخرج: 5
}

loopExample();
تحذير: سلوك النطاق الوظيفي لـ var هو أحد أكثر مصادر الأخطاء شيوعا في JavaScript. المتغيرات المصرح عنها بـ var داخل كتل if أو حلقات for أو حلقات while "تتسرب" خارج تلك الكتل وتكون متاحة في الدالة بأكملها. هذا قد يؤدي إلى كتابة عرضية فوق المتغيرات وأخطاء صعبة الاكتشاف. هذا هو السبب الرئيسي لتقديم let و const في ES6.

رفع var (Hoisting)

الرفع هو آلية في JavaScript حيث يتم نقل تصريحات المتغيرات والدوال إلى أعلى نطاقها قبل تنفيذ الكود. مع var يتم رفع التصريح لكن التعيين يبقى في مكانه. هذا يعني أنه يمكنك الإشارة إلى متغير var قبل التصريح عنه في الكود دون الحصول على ReferenceError -- لكن قيمته ستكون undefined.

مثال: الرفع مع var

// ما تكتبه:
console.log(message);     // المخرج: undefined (ليس خطأ!)
var message = 'مرحبا!';
console.log(message);     // المخرج: مرحبا!

// ما يراه محرك JavaScript فعليا (بعد الرفع):
// var message;            // التصريح يرفع للأعلى
// console.log(message);  // undefined (مصرح عنه لكن لم يعين بعد)
// message = 'مرحبا!';   // التعيين يبقى في مكانه
// console.log(message);  // مرحبا!

// مثال آخر
console.log(count);       // المخرج: undefined
var count = 5;
console.log(count);       // المخرج: 5

// هذا مربك ومعرض للأخطاء!
// مع let أو const سيطرح ReferenceError بدلا من ذلك
// وهو إشارة أوضح بكثير أن شيئا خاطئا.

var يسمح بإعادة التصريح

غرابة أخرى في var هي أنه يسمح لك بالتصريح عن نفس اسم المتغير عدة مرات في نفس النطاق بدون أي خطأ. هذا قد يؤدي بسهولة إلى كتابة عرضية فوق القيم.

مثال: إعادة التصريح عن متغيرات var

var name = 'أحمد';
console.log(name);    // المخرج: أحمد

var name = 'سارة';   // لا خطأ! يكتب فوق القيمة السابقة بصمت.
console.log(name);    // المخرج: سارة

// في ملف كبير بمئات الأسطر قد تعيد التصريح عن متغير
// بدون أن تدرك مما يسبب أخطاء خفية.
// هذا سبب آخر لتفضيل let و const.

التصريح عن المتغيرات بأستخدام let

قدمت الكلمة المفتاحية let في ES6 (2015) لإصلاح مشاكل var. توفر نطاق على مستوى الكتلة مما يعني أن المتغير المصرح عنه بـ let يكون متاحا فقط ضمن الكتلة (المحددة بالأقواس المعقوصة {}) حيث تم التصريح عنه. هذا أكثر بديهية ويمنع أخطاء تسرب النطاق التي تصيب var.

مثال: التصريح عن المتغيرات بأستخدام let

// تصريح let أساسي
let greeting = 'مرحبا بالعالم!';
console.log(greeting);   // المخرج: مرحبا بالعالم!

// إعادة تعيين متغير let
let score = 0;
score = 10;
score = score + 5;
console.log(score);      // المخرج: 15

// التصريح بدون تعيين
let result;
console.log(result);     // المخرج: undefined
result = 42;
console.log(result);     // المخرج: 42

نطاق الكتلة لـ let

نطاق الكتلة يعني أن المتغير المصرح عنه بـ let داخل كتلة (عبارة if أو حلقة for أو حلقة while أو أي كود بين {}) يكون متاحا فقط داخل تلك الكتلة. بمجرد خروج التنفيذ من الكتلة لم يعد المتغير موجودا.

مثال: let ذو نطاق كتلة

function demonstrateLetScope() {
    let x = 10;

    if (true) {
        let y = 20;           // y محصور في كتلة if هذه
        let x = 30;           // هذا متغير جديد منفصل عن x الخارجي
        console.log(x);      // المخرج: 30 (x الداخلي)
        console.log(y);      // المخرج: 20
    }

    console.log(x);          // المخرج: 10 (x الخارجي لم يتغير!)
    // console.log(y);       // خطأ: y غير معرف (بقي في كتلة if)
}

demonstrateLetScope();

// متغير حلقة for يبقى داخل الحلقة
function loopExample() {
    for (let i = 0; i < 5; i++) {
        console.log(i);      // المخرجات: 0, 1, 2, 3, 4
    }
    // console.log(i);       // خطأ: i غير معرف
}

loopExample();
ملاحظة: نطاق الكتلة أكثر قابلية للتنبؤ وأسهل في الفهم من نطاق الدالة. عندما ترى متغيرا مصرحا عنه بـ let داخل كتلة تعرف فورا مدة حياته -- يوجد فقط ضمن تلك الأقواس المعقوصة. هذا يجعل كودك أسهل في الفهم والتصحيح.

المنطقة الميتة المؤقتة (TDZ)

على عكس var الذي يرفع ويهيأ بقيمة undefined، فإن المتغيرات المصرح عنها بـ let ترفع لكن لا تهيأ. الفترة بين دخول النطاق والتصريح الفعلي تسمى المنطقة الميتة المؤقتة (TDZ). الوصول إلى متغير let في TDZ يطرح ReferenceError.

مثال: المنطقة الميتة المؤقتة مع let

// TDZ تبدأ في بداية الكتلة
// وتنتهي عند تصريح let

{
    // TDZ تبدأ هنا للمتغير 'message'
    // console.log(message);  // خطأ: لا يمكن الوصول لـ 'message' قبل التهيئة
    // console.log(message);  // خطأ: لا تزال في TDZ

    let message = 'مرحبا!';  // TDZ تنتهي هنا
    console.log(message);    // المخرج: مرحبا! (يعمل بشكل صحيح)
}

// قارن مع سلوك var
{
    console.log(greeting);   // المخرج: undefined (var يرفع ويهيأ)
    var greeting = 'أهلا!';
    console.log(greeting);   // المخرج: أهلا!
}

// مثال واقعي لـ TDZ يكتشف خطأ
function processOrder(items) {
    // console.log(total);   // خطأ: لا يمكن الوصول لـ 'total' قبل التهيئة
    // هذا الخطأ يساعدك فعلا -- يخبرك أنك
    // تحاول استخدام متغير قبل تعيين قيمة له.

    let total = 0;
    for (let item of items) {
        total += item.price;
    }
    return total;
}
نصيحة: المنطقة الميتة المؤقتة هي في الواقع شيء جيد. تكتشف الأخطاء مبكرا بطرح خطأ عندما تحاول استخدام متغير قبل تعيين قيمة له. مع var ستحصل بصمت على undefined مما قد يؤدي لأخطاء خفية أصعب بكثير في التتبع.

let لا يسمح بإعادة التصريح

على عكس var لا يمكنك التصريح عن نفس اسم المتغير مرتين في نفس النطاق مع let. هذا يمنع الكتابة العرضية فوق القيم.

مثال: لا إعادة تصريح مع let

let name = 'أحمد';
// let name = 'سارة';     // خطأ: المعرف 'name' تم التصريح عنه بالفعل

// ومع ذلك يمكنك أن يكون نفس الاسم في نطاقات مختلفة
let color = 'أحمر';

if (true) {
    let color = 'أزرق';  // هذا مقبول -- نطاق مختلف (كتلة داخلية)
    console.log(color);  // المخرج: أزرق
}

console.log(color);      // المخرج: أحمر (المتغير الخارجي لم يتغير)

التصريح عن المتغيرات بأستخدام const

الكلمة المفتاحية const التي قدمت أيضا في ES6 تصرح عن ثابت -- متغير لا يمكن إعادة تعيين ربطه بعد التعيين الأولي. مثل let، const ذو نطاق كتلة وله منطقة ميتة مؤقتة. الفرق الرئيسي هو أن const يتطلب قيمة أولية وقت التصريح ولا يمكن إعادة تعيينه.

مثال: التصريح عن الثوابت بأستخدام const

// const يتطلب التهيئة عند التصريح
const PI = 3.14159;
const APP_NAME = 'تطبيقي';
const MAX_USERS = 100;
const IS_PRODUCTION = false;

console.log(PI);           // المخرج: 3.14159
console.log(APP_NAME);    // المخرج: تطبيقي

// const لا يمكن إعادة تعيينه
// PI = 3.14;             // خطأ: تعيين لمتغير ثابت
// APP_NAME = 'اسم جديد'; // خطأ: تعيين لمتغير ثابت

// const يجب تهيئته عند التصريح
// const x;               // خطأ: مهيئ مفقود في تصريح const

const مع الكائنات والمصفوفات: إعادة التعيين مقابل التحوير

هذا من أكثر الجوانب التي يساء فهمها في const. عندما تصرح عن كائن أو مصفوفة بـ const فإن الربط (اسم المتغير الذي يشير إلى القيمة) لا يمكن تغييره لكن محتويات الكائن أو المصفوفة يمكن تعديلها. هذا هو الفرق الحاسم بين إعادة التعيين و التحوير.

مثال: const مع الكائنات -- إعادة التعيين مقابل التحوير

// const يمنع إعادة التعيين (تغيير ما يشير إليه المتغير)
const user = { name: 'أحمد', age: 30 };

// لا يمكنك إعادة تعيين المتغير لكائن جديد
// user = { name: 'سارة', age: 25 };  // خطأ: تعيين لمتغير ثابت
// user = 'شيء آخر';                  // خطأ: تعيين لمتغير ثابت

// لكن يمكنك تحوير الكائن (تغيير خصائصه)
user.name = 'سارة';           // هذا يعمل!
user.age = 25;                 // هذا يعمل!
user.email = 'sara@test.com'; // إضافة خصائص جديدة يعمل!
delete user.age;               // حذف الخصائص يعمل!

console.log(user);
// المخرج: { name: 'سارة', email: 'sara@test.com' }

// فكر في الأمر هكذا:
// const يعني أن "ملصق الصندوق" لا يمكن نقله لصندوق مختلف
// لكن يمكنك تغيير ما بداخل الصندوق

مثال: const مع المصفوفات -- إعادة التعيين مقابل التحوير

const fruits = ['تفاح', 'موز', 'كرز'];

// لا يمكنك إعادة تعيين المصفوفة
// fruits = ['عنب', 'شمام'];  // خطأ: تعيين لمتغير ثابت

// لكن يمكنك تحوير المصفوفة (تغيير محتوياتها)
fruits.push('تمر');               // إضافة في النهاية
fruits.pop();                      // إزالة من النهاية
fruits[0] = 'أفوكادو';           // تغيير عنصر
fruits.splice(1, 1);              // إزالة عنصر

console.log(fruits);
// المخرج: ['أفوكادو', 'كرز']

// دوال المصفوفات الشائعة التي تحور المصفوفة تعمل جميعها مع const:
const numbers = [3, 1, 4, 1, 5];
numbers.sort();                    // ترتيب في المكان
numbers.reverse();                 // عكس في المكان
console.log(numbers);             // المخرج: [5, 4, 3, 1, 1]
تحذير: يعتقد كثير من المبتدئين أن const يجعل القيمة غير قابلة للتغيير تماما. هذا غير صحيح للكائنات والمصفوفات. const يمنع فقط إعادة تعيين ربط المتغير -- لا يجمد القيمة. إذا كنت تحتاج فعلا لكائن غير قابل للتغيير استخدم Object.freeze() لكن كن على علم أنه يجري تجميدا سطحيا فقط (الكائنات المتداخلة لا تجمد).

مثال: Object.freeze() لعدم القابلية للتغيير الحقيقي

const config = Object.freeze({
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3
});

// الآن التحويرات يتم تجاهلها بصمت (أو تطرح أخطاء في الوضع الصارم)
config.apiUrl = 'https://other.com';  // يفشل بصمت
config.newProp = 'test';              // يفشل بصمت
delete config.timeout;                 // يفشل بصمت

console.log(config);
// المخرج: { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }

// مع الوضع الصارم:
'use strict';
// config.apiUrl = 'https://other.com';  // خطأ: لا يمكن التعيين لخاصية للقراءة فقط

نطاق الكتلة مقابل نطاق الدالة: مقارنة شاملة

فهم الفرق بين نطاق الكتلة ونطاق الدالة أمر ضروري. دعنا نضع الكلمات المفتاحية الثلاث جنبا إلى جنب في مقارنة شاملة.

مثال: مقارنة النطاق -- var مقابل let مقابل const

function scopeComparison() {
    // الثلاثة متاحة داخل الدالة
    var varVariable = 'أنا var';
    let letVariable = 'أنا let';
    const constVariable = 'أنا const';

    if (true) {
        var varInBlock = 'var في كتلة';
        let letInBlock = 'let في كتلة';
        const constInBlock = 'const في كتلة';

        console.log(varInBlock);     // المخرج: var في كتلة
        console.log(letInBlock);     // المخرج: let في كتلة
        console.log(constInBlock);   // المخرج: const في كتلة
    }

    // بعد الكتلة:
    console.log(varInBlock);         // المخرج: var في كتلة (تسرب!)
    // console.log(letInBlock);      // خطأ: letInBlock غير معرف
    // console.log(constInBlock);    // خطأ: constInBlock غير معرف
}

scopeComparison();

// سلوك النطاق العام
var globalVar = 'أنا var عام';
let globalLet = 'أنا let عام';
const globalConst = 'أنا const عام';

// var يرتبط بكائن window في المتصفحات
// console.log(window.globalVar);   // 'أنا var عام'
// console.log(window.globalLet);   // undefined
// console.log(window.globalConst); // undefined

مثال: مشكلة حلقة for الكلاسيكية مع var

// الخطأ الكلاسيكي: var في حلقة for مع setTimeout
console.log('--- استخدام var ---');
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log('var i:', i);
    }, 100);
}
// المخرج (بعد 100 مللي ثانية):
// var i: 3
// var i: 3
// var i: 3
// لماذا؟ لأن var ذو نطاق وظيفي يوجد متغير 'i' واحد فقط.
// بحلول وقت تنفيذ setTimeout تكون الحلقة انتهت و i يساوي 3.

// الحل: let في حلقة for مع setTimeout
console.log('--- استخدام let ---');
for (let j = 0; j < 3; j++) {
    setTimeout(function() {
        console.log('let j:', j);
    }, 100);
}
// المخرج (بعد 100 مللي ثانية):
// let j: 0
// let j: 1
// let j: 2
// لماذا؟ لأن let ذو نطاق كتلة وكل تكرار للحلقة
// ينشئ متغير 'j' جديدا بقيمته الخاصة.
ملاحظة: مثال حلقة for مع setTimeout هو أحد أشهر أسئلة مقابلات JavaScript. يوضح بالضبط لماذا تم إنشاء let -- لتوفير نطاق كتلة قابل للتنبؤ يمنع هذه الفئة من الأخطاء تماما.

اصطلاحات تسمية المتغيرات

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

القواعد (ما يسمح به JavaScript)

  • يمكن أن تحتوي الأسماء على أحرف وأرقام وشرطات سفلية (_) وعلامات الدولار ($).
  • يجب أن تبدأ الأسماء بحرف أو شرطة سفلية أو علامة دولار (وليس رقم).
  • الأسماء حساسة لحالة الأحرف (myVar و myvar مختلفان).
  • لا يمكن استخدام الكلمات المحجوزة كأسماء متغيرات (مثل let و const و class و return و if الخ).

الاصطلاحات (ما يتفق عليه المطورون)

  • camelCase للمتغيرات والدوال العادية: firstName و userAge و calculateTotal و isLoggedIn.
  • UPPER_SNAKE_CASE للثوابت التي تمثل قيما ثابتة تشبه الإعدادات: MAX_RETRIES و API_BASE_URL و DEFAULT_TIMEOUT.
  • PascalCase للفئات ودوال المنشئ: UserProfile و ShoppingCart و HttpClient.
  • استخدم أسماء وصفية تكشف عن النية. فضل userAge على a أو x.
  • المتغيرات المنطقية يجب أن تقرأ كأسئلة: isActive و hasPermission و canEdit و shouldRetry.

مثال: أسماء متغيرات جيدة مقابل سيئة

// سيء: أسماء بلا معنى أو مختصرة
let x = 'أحمد';
let n = 30;
let f = true;
let arr = ['تفاح', 'موز'];
let temp = 99.5;
let d = new Date();

// جيد: أسماء وصفية تشرح ما تمثله القيمة
let userName = 'أحمد';
let userAge = 30;
let isSubscribed = true;
let shoppingCartItems = ['تفاح', 'موز'];
let temperatureInFahrenheit = 99.5;
let currentDate = new Date();

// سيء: طويل جدا أو مكرر
let theUserFirstNameString = 'أحمد';
let arrayOfAllShoppingCartItemsThatUserHasAdded = [];

// جيد: مختصر لكن واضح
let firstName = 'أحمد';
let cartItems = [];

// الثوابت: استخدم UPPER_SNAKE_CASE للقيم الثابتة
const MAX_LOGIN_ATTEMPTS = 5;
const API_BASE_URL = 'https://api.example.com';
const DEFAULT_LANGUAGE = 'ar';

// لكن استخدم camelCase لمتغيرات const التي لا يعاد تعيينها فحسب
const userName2 = getUserInput();    // تحدد وقت التشغيل
const filteredList = items.filter(i => i.active);  // ليست ثابتا ثابتا
نصيحة: استخدم UPPER_SNAKE_CASE فقط للثوابت الحقيقية -- القيم المكتوبة في الكود والمعروفة وقت الكتابة والتي لن تتغير أبدا (مثل PI و MAX_SIZE و API_KEY). لمتغيرات const التي تحمل قيما تحدد وقت التشغيل (مثل نتيجة استدعاء دالة أو استعلام DOM) استخدم camelCase العادي. التمييز يتعلق بطبيعة القيمة وليس الكلمة المفتاحية const.

متى تستخدم var و let أو const

الآن بعد فهمك للفروقات إليك أفضل الممارسات الحديثة للاختيار بين الكلمات المفتاحية الثلاث:

القاعدة الحديثة

  1. استخدم const افتراضيا. معظم المتغيرات في الكود المكتوب جيدا لا تحتاج لإعادة تعيين. البدء بـ const يجعل نواياك واضحة: هذه القيمة لا يجب أن تتغير.
  2. استخدم let عندما تحتاج لإعادة التعيين. عدادات الحلقات والمتراكمات والقيم التي تتغير بناء على الشروط -- هذه حالات استخدام صالحة لـ let.
  3. لا تستخدم var أبدا. لا يوجد سيناريو في JavaScript الحديث حيث يكون var الخيار الأفضل. يوجد فقط للتوافق مع الأكواد القديمة.

مثال: اختيار الكلمة المفتاحية الصحيحة

// استخدم const: القيم التي لا يجب إعادة تعيينها
const API_URL = 'https://api.example.com';
const user = { name: 'أحمد', role: 'مدير' };
const colors = ['أحمر', 'أخضر', 'أزرق'];
const element = document.getElementById('title');
const isProduction = process.env.NODE_ENV === 'production';

// استخدم let: القيم التي تحتاج لإعادة تعيين
let score = 0;
score += 10;

let currentPage = 1;
currentPage++;

let status = 'معلق';
if (isApproved) {
    status = 'مقبول';
}

let total = 0;
for (const item of cartItems) {
    total += item.price;    // total يتغير مع كل تكرار
}

// عدادات الحلقات
for (let i = 0; i < 10; i++) {
    console.log(i);
}

// لا تستخدم var أبدا
// var name = 'أحمد';    // لا تفعل هذا
// let name = 'أحمد';    // افعل هذا بدلا من ذلك (إذا كنت تحتاج لإعادة التعيين)
// const name = 'أحمد';  // أو هذا (إذا لن تتغير القيمة)

الرفع: نظرة شاملة

دعنا نجمع كل شيء عن الرفع مع أنواع التصريح الثلاثة جنبا إلى جنب.

مثال: مقارنة سلوك الرفع

// ===== var: يرفع ويهيأ بـ undefined =====
console.log(varMessage);     // المخرج: undefined (لا خطأ)
var varMessage = 'مرحبا من var';
console.log(varMessage);     // المخرج: مرحبا من var

// ===== let: يرفع لكن لا يهيأ (TDZ) =====
// console.log(letMessage);  // خطأ: لا يمكن الوصول لـ 'letMessage' قبل التهيئة
let letMessage = 'مرحبا من let';
console.log(letMessage);     // المخرج: مرحبا من let

// ===== const: يرفع لكن لا يهيأ (TDZ) =====
// console.log(constMessage); // خطأ: لا يمكن الوصول لـ 'constMessage' قبل التهيئة
const constMessage = 'مرحبا من const';
console.log(constMessage);   // المخرج: مرحبا من const

// ===== تصريحات الدوال: ترفع بالكامل =====
sayHello();                  // المخرج: مرحبا! (تعمل حتى قبل التصريح)
function sayHello() {
    console.log('مرحبا!');
}

// ===== تعبيرات الدوال مع var: ترفع جزئيا =====
// greet();                  // خطأ: greet ليست دالة (هي undefined)
var greet = function() {
    console.log('أهلا!');
};
greet();                     // المخرج: أهلا!

أمثلة عملية من العالم الحقيقي

دعنا نلقي نظرة على سيناريوهات عملية حيث فهم المتغيرات مهم حقا.

مثال: سلة التسوق

// أسعار المنتجات (const: هذه قيم ثابتة)
const TAX_RATE = 0.08;
const SHIPPING_THRESHOLD = 50;
const SHIPPING_COST = 5.99;

// عناصر السلة (const: لن نعيد تعيين المصفوفة لكن سنعدل محتوياتها)
const cart = [];

// إضافة عناصر للسلة
cart.push({ name: 'قميص', price: 25.99, quantity: 2 });
cart.push({ name: 'جينز', price: 49.99, quantity: 1 });
cart.push({ name: 'حذاء رياضي', price: 89.99, quantity: 1 });

// حساب المجاميع (let: هذه القيم تتغير أثناء الحساب)
let subtotal = 0;
for (const item of cart) {
    subtotal += item.price * item.quantity;
}

let tax = subtotal * TAX_RATE;
let shipping = subtotal >= SHIPPING_THRESHOLD ? 0 : SHIPPING_COST;
let grandTotal = subtotal + tax + shipping;

console.log('المجموع الفرعي: $' + subtotal.toFixed(2));
console.log('الضريبة: $' + tax.toFixed(2));
console.log('الشحن: $' + (shipping === 0 ? 'مجاني' : shipping.toFixed(2)));
console.log('الإجمالي: $' + grandTotal.toFixed(2));

مثال: تدفق مصادقة المستخدم

// الإعدادات (const: قيم ثابتة)
const MAX_LOGIN_ATTEMPTS = 3;
const LOCKOUT_DURATION_MINUTES = 15;

// تتبع الحالة (let: تتغير أثناء التدفق)
let loginAttempts = 0;
let isLocked = false;
let lastAttemptTime = null;

function attemptLogin(username, password) {
    if (isLocked) {
        const minutesSinceLock = (Date.now() - lastAttemptTime) / 60000;
        if (minutesSinceLock < LOCKOUT_DURATION_MINUTES) {
            const remainingMinutes = Math.ceil(LOCKOUT_DURATION_MINUTES - minutesSinceLock);
            console.log('الحساب مقفل. حاول مرة أخرى خلال ' + remainingMinutes + ' دقيقة.');
            return false;
        }
        // انتهت فترة القفل، إعادة تعيين
        isLocked = false;
        loginAttempts = 0;
    }

    // محاكاة فحص بيانات الاعتماد
    const isValid = username === 'admin' && password === 'secret123';

    if (isValid) {
        loginAttempts = 0;
        console.log('تم تسجيل الدخول بنجاح! مرحبا ' + username + '.');
        return true;
    }

    loginAttempts++;
    lastAttemptTime = Date.now();

    if (loginAttempts >= MAX_LOGIN_ATTEMPTS) {
        isLocked = true;
        console.log('محاولات فاشلة كثيرة. الحساب مقفل لمدة ' + LOCKOUT_DURATION_MINUTES + ' دقيقة.');
    } else {
        const remaining = MAX_LOGIN_ATTEMPTS - loginAttempts;
        console.log('بيانات اعتماد غير صحيحة. ' + remaining + ' محاولات متبقية.');
    }

    return false;
}

مثال: محول درجات الحرارة

// صيغ التحويل ثوابت
const FAHRENHEIT_TO_CELSIUS_OFFSET = 32;
const FAHRENHEIT_TO_CELSIUS_RATIO = 5 / 9;

function convertTemperature(value, fromUnit) {
    // result سيعاد تعيينه بناء على اتجاه التحويل
    let result;
    let toUnit;

    if (fromUnit === 'C') {
        result = (value * (1 / FAHRENHEIT_TO_CELSIUS_RATIO)) + FAHRENHEIT_TO_CELSIUS_OFFSET;
        toUnit = 'F';
    } else if (fromUnit === 'F') {
        result = (value - FAHRENHEIT_TO_CELSIUS_OFFSET) * FAHRENHEIT_TO_CELSIUS_RATIO;
        toUnit = 'C';
    } else {
        console.error('وحدة غير معروفة: ' + fromUnit);
        return;
    }

    // تقريب لمنزلتين عشريتين
    const rounded = Math.round(result * 100) / 100;
    console.log(value + ' درجة ' + fromUnit + ' = ' + rounded + ' درجة ' + toUnit);
    return rounded;
}

convertTemperature(100, 'C');   // 100 درجة مئوية = 212 فهرنهايت
convertTemperature(32, 'F');    // 32 فهرنهايت = 0 درجة مئوية
convertTemperature(72, 'F');    // 72 فهرنهايت = 22.22 درجة مئوية

ملخص: var مقابل let مقابل const

إليك جدول مرجعي كامل يلخص الفروقات بين الكلمات المفتاحية الثلاث للتصريح عن المتغيرات:

الميزة var let const
النطاق الدالة الكتلة الكتلة
الرفع نعم (يهيأ بـ undefined) نعم (TDZ، لا يهيأ) نعم (TDZ، لا يهيأ)
إعادة التعيين مسموح مسموح غير مسموح
إعادة التصريح مسموح غير مسموح غير مسموح
يجب التهيئة لا لا نعم
الموصى به أبدا عند الحاجة لإعادة التعيين الخيار الافتراضي

تمرين عملي 1: محقق النطاق

بدون تشغيل الكود توقع مخرج كل عبارة console.log() في البرنامج التالي. ثم شغله في وحدة تحكم المتصفح للتحقق من إجاباتك:

  1. var a = 1; { var a = 2; } console.log(a);
  2. let b = 1; { let b = 2; } console.log(b);
  3. const c = 1; { const c = 2; console.log(c); } console.log(c);
  4. for (var i = 0; i < 3; i++) {} console.log(i);
  5. for (let j = 0; j < 3; j++) {} console.log(typeof j);

الإجابات هي: (1) 2، (2) 1، (3) 2 ثم 1، (4) 3، (5) "undefined". إذا أخطأت في أي منها عد واقرأ قسم نطاق الكتلة مقابل نطاق الدالة مرة أخرى.

تمرين عملي 2: إعادة هيكلة var إلى let و const

خذ الكود التالي الذي يستخدم var فقط وأعد هيكلته لاستخدام let و const بشكل مناسب. قرر أي المتغيرات يجب أن تكون const (لا يعاد تعيينها) وأيها يجب أن تكون let (يعاد تعيينها). أنشئ ملفا بأسم refactor.js واختبره في المتصفح.

var taxRate = 0.10;
var items = [12.99, 24.50, 8.75, 15.00];
var total = 0;
for (var i = 0; i < items.length; i++) {
    var price = items[i];
    total = total + price;
}
var tax = total * taxRate;
var grandTotal = total + tax;
console.log('الإجمالي: $' + grandTotal.toFixed(2));

تمرين عملي 3: بناء عداد

أنشئ صفحة HTML بها رقم معروض على الشاشة (يبدأ من 0) وزر "زيادة" وزر "نقصان" وزر "إعادة تعيين". استخدم JavaScript لجعل الأزرار تعمل. فكر بعناية في أي المتغيرات يجب أن تكون const وأيها يجب أن تكون let. قيمة العداد ستتغير لذا يجب أن تكون let. مراجع عناصر DOM ومراجع الأزرار لن يعاد تعيينها لذا يجب أن تكون const. أضف قاعدة أن العداد لا يمكن أن ينخفض تحت 0 (أظهر تحذيرا في وحدة التحكم إذا حاول المستخدم).

تمرين عملي 4: تحدي تسمية المتغيرات

أعد تسمية المتغيرات ذات الأسماء السيئة التالية لتتبع اصطلاحات تسمية JavaScript. لكل واحد قرر هل يجب أن يكون const أو let:

var x = 'أحمد محمد';
var n = 42;
var flag = true;
var arr = ['القراءة', 'البرمجة', 'الألعاب'];
var TEMP = 98.6;
var d = new Date();
var e = document.getElementById('main');
var s = 0;  // يستخدم لتتبع النتيجة في لعبة

اكتب النسخ المعاد تسميتها مع الكلمات المفتاحية المناسبة const أو let. مثال: const fullName = 'أحمد محمد';

ES
Edrees Salih
منذ 8 ساعات

We are still cooking the magic in the way!