أساسيات JavaScript

عبارات Switch ومطابقة الانماط

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

لماذا نحتاج عبارات switch؟

في الدرس السابق، تعلمت كيفية استخدام if و else if و else لاتخاذ القرارات في الكود. هذه الادوات تعمل بشكل مثالي لمعظم المواقف، لكن احيانا المنطق الخاص بك يتضمن مقارنة قيمة واحدة مع خيارات متعددة ممكنة. عندما تجد نفسك تكتب سلسلة طويلة من عبارات else if التي تقارن جميعها نفس المتغير بقيم مختلفة، يصبح الكود متكررا وصعب القراءة. هذا بالضبط حيث تتالق عبارة switch.

عبارة switch توفر طريقة انظف واكثر تنظيما للتعامل مع قيم متعددة ممكنة لتعبير واحد. فكر فيها كقائمة طعام في مطعم -- بدلا من السؤال "هل هي بيتزا؟ لا؟ هل هي معكرونة؟ لا؟ هل هي سلطة؟" مرارا وتكرارا، تنظر الى القائمة وتنتقل مباشرة الى اختيارك. عبارة switch تعمل بالمثل: تقيم تعبيرا مرة واحدة ثم تنتقل الى الحالة المطابقة.

صيغة switch الاساسية

عبارة switch لها بنية محددة تحتاج لفهمها قبل استخدامها. دعنا نفحص كل جزء من الصيغة بالتفصيل.

مثال: بنية عبارة switch

switch (expression) {
    case value1:
        // الكود الذي يعمل اذا expression === value1
        break;
    case value2:
        // الكود الذي يعمل اذا expression === value2
        break;
    case value3:
        // الكود الذي يعمل اذا expression === value3
        break;
    default:
        // الكود الذي يعمل اذا لم تتطابق اي حالة
}

// مثال حقيقي:
let dayNumber = 3;
let dayName;

switch (dayNumber) {
    case 1:
        dayName = 'الاثنين';
        break;
    case 2:
        dayName = 'الثلاثاء';
        break;
    case 3:
        dayName = 'الاربعاء';
        break;
    case 4:
        dayName = 'الخميس';
        break;
    case 5:
        dayName = 'الجمعة';
        break;
    case 6:
        dayName = 'السبت';
        break;
    case 7:
        dayName = 'الاحد';
        break;
    default:
        dayName = 'رقم يوم غير صالح';
}

console.log(dayName); // "الاربعاء"

دعنا نحلل كل مكون من عبارة switch. الكلمة المفتاحية switch تبدا العبارة، متبوعة بتعبير في اقواس. هذا التعبير يتم تقييمه مرة واحدة، ونتيجته تقارن مع كل case. كل كلمة مفتاحية case متبوعة بقيمة ونقطتين. اذا تطابق التعبير مع قيمة الحالة باستخدام المساواة الصارمة (===)، يعمل الكود بعد تلك الحالة. الكلمة المفتاحية break تخرج من عبارة switch -- بدونها، التنفيذ "يسقط" الى الحالة التالية (المزيد عن هذا قريبا). شرط default اختياري ويعمل عندما لا تتطابق اي حالة -- انه مثل else في سلسلة if...else.

ملاحظة: عبارة switch تستخدم المساواة الصارمة (===) للمقارنات، وليس المساواة المتساهلة (==). هذا يعني ان case '5' لن يتطابق مع الرقم 5. تاكد دائما ان انواع قيم الحالات تتطابق مع نوع التعبير الذي تستخدمه في switch.

الكلمتان المفتاحيتان case و break

الكلمتان المفتاحيتان case و break تعملان معا، لكن فهم كل واحدة على حدة مهم. الكلمة المفتاحية case تحدد نقطة مقارنة -- عندما يتطابق تعبير switch مع قيمة الحالة، يبدا التنفيذ عند تلك النقطة. الكلمة المفتاحية break تخبر JavaScript بالخروج من كتلة switch بالكامل والمتابعة مع الكود بعد switch.

مثال: كيف تعمل case و break معا

let fruit = 'apple';

switch (fruit) {
    case 'apple':
        console.log('التفاح بسعر $1.50 للرطل.');
        console.log('رائع للفطائر!');
        break;  // بدون هذا break، التنفيذ يستمر للحالة التالية!

    case 'banana':
        console.log('الموز بسعر $0.75 للرطل.');
        console.log('غني بالبوتاسيوم!');
        break;

    case 'orange':
        console.log('البرتقال بسعر $2.00 للرطل.');
        console.log('مصدر ممتاز لفيتامين C!');
        break;

    default:
        console.log('عذرا، لا نحمل ' + fruit + '.');
}
// المخرجات:
// "التفاح بسعر $1.50 للرطل."
// "رائع للفطائر!"

كل حالة يمكن ان تحتوي على عدة اسطر من الكود. كل الكود بين علامة case وعبارة break ينتمي لتلك الحالة. يمكنك كتابة عدد الاسطر الذي تحتاجه -- تعريفات متغيرات، استدعاءات دوال، حلقات، او حتى عبارات if متداخلة داخل حالة.

مثال: عبارات متعددة في حالة

let command = 'deploy';

switch (command) {
    case 'build':
        console.log('بدء عملية البناء...');
        console.log('ترجمة ملفات المصدر...');
        console.log('تشغيل الاختبارات...');
        console.log('اكتمل البناء!');
        break;

    case 'deploy':
        console.log('بدء النشر...');
        console.log('رفع الملفات الى الخادم...');
        console.log('تحديث قاعدة البيانات...');
        console.log('مسح ذاكرة التخزين المؤقت...');
        console.log('تم النشر بنجاح!');
        break;

    case 'rollback':
        console.log('بدء التراجع...');
        console.log('استعادة الاصدار السابق...');
        console.log('اكتمل التراجع!');
        break;

    default:
        console.log('امر غير معروف: ' + command);
        console.log('الاوامر المتاحة: build, deploy, rollback');
}
// المخرجات:
// "بدء النشر..."
// "رفع الملفات الى الخادم..."
// "تحديث قاعدة البيانات..."
// "مسح ذاكرة التخزين المؤقت..."
// "تم النشر بنجاح!"

سلوك السقوط: ماذا يحدث بدون break

احد اهم (واكثر) جوانب عبارة switch اربكا هو سلوك السقوط (Fall-Through). اذا حذفت عبارة break بعد حالة، JavaScript لا يتوقف عند تلك الحالة -- يستمر في تنفيذ الكود في الحالة التالية، والتالية، وهكذا حتى يصل الى break او نهاية كتلة switch. هذا يسمى "السقوط" لان التنفيذ يسقط من حالة الى التالية.

مثال: السقوط بدون break (خطا شائع)

let color = 'red';

// خطا: عبارات break مفقودة!
switch (color) {
    case 'red':
        console.log('الاحمر هو لون النار.');
        // لا break! يسقط الى الحالة التالية.
    case 'blue':
        console.log('الازرق هو لون السماء.');
        // لا break! يسقط مرة اخرى.
    case 'green':
        console.log('الاخضر هو لون الطبيعة.');
        break;
    default:
        console.log('لون غير معروف.');
}
// المخرجات (جميع الاسطر الثلاثة تطبع بسبب السقوط!):
// "الاحمر هو لون النار."
// "الازرق هو لون السماء."
// "الاخضر هو لون الطبيعة."
تحذير: نسيان عبارات break هو واحد من اكثر الاخطاء شيوعا في JavaScript. تحقق دائما مرتين ان كل حالة في عبارة switch تنتهي بـ break (او return اذا كانت داخل دالة). سلوك السقوط نادرا ما يكون مقصودا، وعندما يكون كذلك، يجب دائما اضافة تعليق يوضح لماذا حذفت break عمدا.

ومع ذلك، يمكن استخدام السقوط عمدا احيانا. عندما يجب ان تنفذ حالات متعددة نفس الكود، يمكنك تكديس الحالات معا بدون عبارات break بينها. هذا نمط شرعي وشائع.

مثال: السقوط المتعمد للتجميع

let month = 'March';
let season;

switch (month) {
    case 'December':
    case 'January':
    case 'February':
        season = 'شتاء';
        break;

    case 'March':
    case 'April':
    case 'May':
        season = 'ربيع';
        break;

    case 'June':
    case 'July':
    case 'August':
        season = 'صيف';
        break;

    case 'September':
    case 'October':
    case 'November':
        season = 'خريف';
        break;

    default:
        season = 'شهر غير صالح';
}

console.log(month + ' في فصل ' + season);
// المخرجات: "March في فصل ربيع"

الحالة الافتراضية default

حالة default تعمل كشبكة امان لاي قيمة لا تتطابق مع الحالات المحددة. انها مشابهة لـ else النهائي في سلسلة if...else if...else. بينما default اختياري تقنيا، يجب دائما تقريبا تضمينه. يتعامل مع القيم غير المتوقعة بشكل مناسب ويجعل الكود اكثر متانة. بدون default، اذا لم تتطابق اي حالة، كتلة switch بالكامل لا تفعل شيئا -- مما قد يؤدي الى اخطاء صامتة يصعب تتبعها.

مثال: استخدام default بفعالية

// مثال: معالجة الاخطاء مع default
function getStatusMessage(statusCode) {
    let message;

    switch (statusCode) {
        case 200:
            message = 'OK -- الطلب ناجح.';
            break;
        case 201:
            message = 'تم الانشاء -- تم انشاء المورد بنجاح.';
            break;
        case 301:
            message = 'تم النقل دائما -- المورد له عنوان URL جديد.';
            break;
        case 400:
            message = 'طلب سيء -- تحقق من تنسيق طلبك.';
            break;
        case 401:
            message = 'غير مصرح -- يرجى تسجيل الدخول.';
            break;
        case 403:
            message = 'محظور -- ليس لديك صلاحية.';
            break;
        case 404:
            message = 'غير موجود -- المورد غير موجود.';
            break;
        case 500:
            message = 'خطا داخلي في الخادم -- حدث خطا ما في الخادم.';
            break;
        case 503:
            message = 'الخدمة غير متاحة -- الخادم معطل مؤقتا.';
            break;
        default:
            message = 'رمز حالة غير معروف: ' + statusCode + '. يرجى مراجعة التوثيق.';
    }

    return message;
}

console.log(getStatusMessage(200)); // "OK -- الطلب ناجح."
console.log(getStatusMessage(404)); // "غير موجود -- المورد غير موجود."
console.log(getStatusMessage(999)); // "رمز حالة غير معروف: 999. يرجى مراجعة التوثيق."
نصيحة: حالة default لا يجب ان تكون في نهاية كتلة switch -- JavaScript يسمح بها في اي مكان. ومع ذلك، بحسب الاتفاقية ولسهولة القراءة، ضع دائما default في النهاية. اذا وضعتها في مكان آخر، تاكد من ان لديها break لمنع السقوط الى الحالة التالية.

switch مقابل if-else: متى تستخدم ايهما

كلا switch و if...else if يمكنهما التعامل مع شروط متعددة، فكيف تقرر ايهما تستخدم؟ الاجابة تعتمد على نوع المقارنة التي تجريها. اليك ارشادات واضحة للاختيار بينهما.

استخدم switch عندما:

  • تقارن متغيرا واحدا او تعبيرا واحدا مع قيم محددة متعددة.
  • لديك ثلاث قيم محتملة او اكثر للتحقق منها.
  • جميع المقارنات هي فحوصات مساواة صارمة (===).
  • تريد كودا نظيفا وقابلا للقراءة للمنطق من نوع القوائم او القائم على الحالات.

استخدم if...else if عندما:

  • تحتاج للتحقق من نطاقات قيم (مثلا x > 10 && x < 20).
  • تحتاج للتحقق من متغيرات مختلفة في شروط مختلفة.
  • تحتاج تعبيرات منطقية معقدة مع && و ||.
  • لديك شروط ليست فحوصات مساواة بسيطة.

مثال: مقارنة switch مع if-else

// استخدام جيد لـ switch -- مقارنة متغير واحد بقيم محددة
let role = 'editor';

switch (role) {
    case 'admin':
        console.log('وصول كامل');
        break;
    case 'editor':
        console.log('وصول تعديل');
        break;
    case 'viewer':
        console.log('وصول للقراءة فقط');
        break;
    default:
        console.log('بدون وصول');
}

// استخدام جيد لـ if-else -- نطاقات وشروط معقدة
let score = 85;
let temperature = 30;
let isRaining = true;

if (score >= 90 && !isRaining) {
    console.log('يوم مثالي للاحتفال في الخارج!');
} else if (score >= 80 && temperature < 35) {
    console.log('اداء جيد في طقس مريح.');
} else if (score >= 70) {
    console.log('درجة نجاح.');
} else {
    console.log('يحتاج تحسين.');
}
ملاحظة: بعض المطورين يستخدمون نمط switch (true) مع تعبيرات منطقية في الحالات (مثل case score >= 90:). بينما هذا يعمل تقنيا، يعتبره معظم ادلة الانماط نمطا مضادا. انه يهزم الغرض من عبارة switch ويجب استبداله بـ if...else if.

تجميع الحالات: منطق مشترك لقيم متعددة

واحدة من اقوى ميزات عبارة switch هي القدرة على تجميع حالات متعددة يجب ان تنفذ نفس الكود. هذا انظف بكثير من كتابة if (value === 'a' || value === 'b' || value === 'c'). بتكديس علامات الحالات بدون عبارات break بينها، يمكنك التعامل بانيقة مع قيم متعددة بنفس المنطق.

مثال: تجميع الحالات لمنطق مشترك

// تصنيف امتدادات الملفات
function getFileCategory(extension) {
    let category;

    switch (extension.toLowerCase()) {
        case 'jpg':
        case 'jpeg':
        case 'png':
        case 'gif':
        case 'svg':
        case 'webp':
            category = 'صورة';
            break;

        case 'mp4':
        case 'avi':
        case 'mkv':
        case 'mov':
        case 'webm':
            category = 'فيديو';
            break;

        case 'mp3':
        case 'wav':
        case 'flac':
        case 'ogg':
        case 'aac':
            category = 'صوت';
            break;

        case 'pdf':
        case 'doc':
        case 'docx':
        case 'txt':
        case 'rtf':
        case 'odt':
            category = 'مستند';
            break;

        case 'html':
        case 'css':
        case 'js':
        case 'py':
        case 'java':
        case 'php':
            category = 'كود';
            break;

        case 'zip':
        case 'rar':
        case '7z':
        case 'tar':
        case 'gz':
            category = 'ارشيف';
            break;

        default:
            category = 'غير معروف';
    }

    return category;
}

console.log(getFileCategory('PNG'));   // "صورة"
console.log(getFileCategory('mp3'));   // "صوت"
console.log(getFileCategory('py'));    // "كود"
console.log(getFileCategory('xyz'));   // "غير معروف"

مثال: التجميع مع اجراءات مختلفة

// معالج احداث لوحة المفاتيح
function handleKeyPress(key) {
    switch (key) {
        // مفاتيح الحركة -- جميعها تشترك في منطق مماثل لكن باتجاهات مختلفة
        case 'w':
        case 'ArrowUp':
            console.log('التحرك للاعلى');
            break;

        case 's':
        case 'ArrowDown':
            console.log('التحرك للاسفل');
            break;

        case 'a':
        case 'ArrowLeft':
            console.log('التحرك لليسار');
            break;

        case 'd':
        case 'ArrowRight':
            console.log('التحرك لليمين');
            break;

        // مفاتيح الاجراءات
        case ' ':
        case 'Enter':
            console.log('اجراء/اختيار');
            break;

        case 'Escape':
            console.log('ايقاف/قائمة');
            break;

        default:
            console.log('مفتاح غير مربوط: ' + key);
    }
}

handleKeyPress('ArrowUp');   // "التحرك للاعلى"
handleKeyPress('w');          // "التحرك للاعلى"
handleKeyPress('Escape');     // "ايقاف/قائمة"
handleKeyPress('x');          // "مفتاح غير مربوط: x"

switch مع التعبيرات

التعبير داخل switch() يمكن ان يكون اي تعبير JavaScript صالح -- لا يجب ان يكون متغيرا بسيطا. يمكنك استخدام استدعاءات الدوال والقيم المحسوبة او اي تعبير يقيم الى قيمة. وبالمثل، قيم الحالات يمكن ان تكون تعبيرات ايضا، رغم ان استخدام قيم حرفية بسيطة اكثر شيوعا وقابلية للقراءة.

مثال: التعبيرات في switch

// استخدام استدعاء دالة كتعبير switch
let input = '  Hello World  ';

switch (input.trim().toLowerCase()) {
    case 'hello world':
        console.log('تم اكتشاف تحية قياسية.');
        break;
    case 'goodbye':
        console.log('تم اكتشاف رسالة وداع.');
        break;
    case 'help':
        console.log('تم اكتشاف طلب مساعدة.');
        break;
    default:
        console.log('مدخل غير معروف: ' + input.trim());
}
// المخرجات: "تم اكتشاف تحية قياسية."

مثال: switch مع دوال السلاسل

// التوجيه بناء على مسار URL
function handleRoute(url) {
    // استخراج الجزء الاول من مسار URL
    let path = url.split('/')[1] || 'home';

    switch (path) {
        case 'home':
        case '':
            console.log('عرض الصفحة الرئيسية.');
            break;

        case 'about':
            console.log('عرض صفحة حول.');
            break;

        case 'products':
            console.log('عرض كتالوج المنتجات.');
            break;

        case 'contact':
            console.log('عرض نموذج الاتصال.');
            break;

        case 'admin':
            console.log('عرض لوحة تحكم المسؤول.');
            break;

        default:
            console.log('404: الصفحة غير موجودة للمسار "/' + path + '/"');
    }
}

handleRoute('/about');     // "عرض صفحة حول."
handleRoute('/products');  // "عرض كتالوج المنتجات."
handleRoute('/unknown');   // "404: الصفحة غير موجودة للمسار "/unknown/""

switch مع return في الدوال

عند استخدام switch داخل دالة، يمكنك استخدام return بدلا من break للخروج من switch والدالة في آن واحد. هذا نمط نظيف لانه يزيل الحاجة لمتغير نتيجة -- تعيد ببساطة القيمة مباشرة من كل حالة. بما ان return يخرج فورا من الدالة، لا يوجد خطر سقوط.

مثال: switch مع return

// استخدام return بدلا من break
function getDayType(day) {
    switch (day.toLowerCase()) {
        case 'monday':
        case 'tuesday':
        case 'wednesday':
        case 'thursday':
        case 'friday':
            return 'يوم عمل';

        case 'saturday':
        case 'sunday':
            return 'عطلة نهاية الاسبوع';

        default:
            return 'يوم غير صالح: ' + day;
    }
}

console.log(getDayType('Monday'));    // "يوم عمل"
console.log(getDayType('Saturday'));  // "عطلة نهاية الاسبوع"
console.log(getDayType('Funday'));    // "يوم غير صالح: Funday"

// مثال اكثر تعقيدا: مصنع تكوين
function createDatabaseConfig(environment) {
    switch (environment) {
        case 'development':
            return {
                host: 'localhost',
                port: 5432,
                database: 'myapp_dev',
                debug: true,
                pool: { min: 1, max: 5 }
            };

        case 'testing':
            return {
                host: 'localhost',
                port: 5432,
                database: 'myapp_test',
                debug: true,
                pool: { min: 1, max: 3 }
            };

        case 'staging':
            return {
                host: 'staging-db.example.com',
                port: 5432,
                database: 'myapp_staging',
                debug: false,
                pool: { min: 5, max: 20 }
            };

        case 'production':
            return {
                host: 'prod-db.example.com',
                port: 5432,
                database: 'myapp_prod',
                debug: false,
                pool: { min: 10, max: 50 }
            };

        default:
            throw new Error('بيئة غير معروفة: ' + environment);
    }
}

let config = createDatabaseConfig('development');
console.log(config.host);     // "localhost"
console.log(config.database); // "myapp_dev"
نصيحة: عندما تعيد كل حالة في switch قيمة، استخدام return بدلا من break هو النمط المفضل. انه انظف، يزيل احتمال نسيان break، ويجعل من الواضح ان كل حالة تنتج نتيجة. هذا واحد من اكثر انماط switch شيوعا في قواعد الكود المهنية.

مثال واقعي: نظام التنقل بالقوائم

التنقل بالقوائم هو حالة استخدام كلاسيكية لعبارات switch. التطبيقات غالبا ما تحتاج للاستجابة لخيارات قائمة مختلفة يختارها المستخدمون. اليك مثال شامل يحاكي نظام قائمة سطر الاوامر.

مثال: نظام قائمة تفاعلي

function handleMenuSelection(menuChoice) {
    console.log('\n=== اختيار القائمة: ' + menuChoice + ' ===');

    switch (menuChoice) {
        case '1':
        case 'profile':
            console.log('فتح الملف الشخصي...');
            console.log('الاسم: احمد محمد');
            console.log('البريد: ahmed@example.com');
            console.log('عضو منذ: يناير 2024');
            return { page: 'profile', status: 'success' };

        case '2':
        case 'settings':
            console.log('فتح الاعدادات...');
            console.log('الاعدادات المتاحة:');
            console.log('  - المظهر (السمة، حجم الخط)');
            console.log('  - الاشعارات (البريد، الدفع)');
            console.log('  - الخصوصية (الرؤية، مشاركة البيانات)');
            console.log('  - الامان (كلمة المرور، المصادقة الثنائية)');
            return { page: 'settings', status: 'success' };

        case '3':
        case 'messages':
            console.log('فتح الرسائل...');
            console.log('لديك 5 رسائل غير مقروءة.');
            console.log('الاحدث: "اجتماع غدا الساعة 10 صباحا" من سارة');
            return { page: 'messages', status: 'success', unread: 5 };

        case '4':
        case 'notifications':
            console.log('فتح الاشعارات...');
            console.log('3 اشعارات جديدة:');
            console.log('  1. تحديث النظام متاح');
            console.log('  2. منشورك حصل على 10 اعجابات');
            console.log('  3. متابع جديد: @webdev_sarah');
            return { page: 'notifications', status: 'success', count: 3 };

        case '5':
        case 'help':
            console.log('مركز المساعدة');
            console.log('  اكتب "1" او "profile" لعرض ملفك الشخصي');
            console.log('  اكتب "2" او "settings" لفتح الاعدادات');
            console.log('  اكتب "3" او "messages" لعرض الرسائل');
            console.log('  اكتب "4" او "notifications" لرؤية الاشعارات');
            console.log('  اكتب "6" او "logout" لتسجيل الخروج');
            return { page: 'help', status: 'success' };

        case '6':
        case 'logout':
            console.log('تسجيل الخروج...');
            console.log('شكرا لاستخدامك تطبيقنا!');
            console.log('تم تسجيل خروجك بامان.');
            return { page: 'logout', status: 'success' };

        default:
            console.log('خيار قائمة غير صالح: "' + menuChoice + '"');
            console.log('يرجى ادخال رقم 1-6 او اسم قائمة.');
            console.log('اكتب "5" او "help" للخيارات المتاحة.');
            return { page: null, status: 'error', message: 'خيار غير صالح' };
    }
}

// اختبار نظام القائمة
handleMenuSelection('1');
handleMenuSelection('settings');
handleMenuSelection('messages');
handleMenuSelection('invalid');

مثال واقعي: معالجة الحالات

التطبيقات تحتاج باستمرار للاستجابة لقيم حالة مختلفة -- من استجابات HTTP الى تتبع الطلبات الى حالات حسابات المستخدمين. عبارة switch مثالية لربط رموز الحالة بالاجراءات او الرسائل. اليك مثال شامل يتعامل مع تغييرات حالة الطلبات مع اجراءات مناسبة لكل انتقال.

مثال: معالج حالة الطلبات

function handleOrderStatus(order) {
    let notification = {
        orderId: order.id,
        timestamp: new Date().toISOString()
    };

    switch (order.status) {
        case 'pending':
            notification.title = 'تم استلام الطلب';
            notification.message = 'طلبك #' + order.id +
                ' تم استلامه وبانتظار التاكيد.';
            notification.action = 'يرجى الانتظار بينما نتحقق من دفعتك.';
            notification.icon = 'clock';
            notification.color = 'orange';
            break;

        case 'confirmed':
            notification.title = 'تم تاكيد الطلب';
            notification.message = 'طلبك #' + order.id +
                ' تم تاكيده!';
            notification.action = 'يتم تجهيز اغراضك للشحن.';
            notification.icon = 'check-circle';
            notification.color = 'blue';
            break;

        case 'shipped':
            notification.title = 'تم شحن الطلب!';
            notification.message = 'طلبك #' + order.id +
                ' في الطريق!';
            notification.action = 'تتبع طردك برقم التتبع: ' +
                (order.trackingNumber || 'قيد الانتظار');
            notification.icon = 'truck';
            notification.color = 'purple';
            break;

        case 'delivered':
            notification.title = 'تم تسليم الطلب';
            notification.message = 'طلبك #' + order.id +
                ' تم تسليمه بنجاح!';
            notification.action = 'نامل ان تستمتع بمشترياتك. اترك تقييما!';
            notification.icon = 'package';
            notification.color = 'green';
            break;

        case 'cancelled':
            notification.title = 'تم الغاء الطلب';
            notification.message = 'طلبك #' + order.id +
                ' تم الغاؤه.';
            notification.action = 'سيتم معالجة استرداد المبلغ خلال 5-7 ايام عمل.';
            notification.icon = 'x-circle';
            notification.color = 'red';
            break;

        default:
            notification.title = 'تحديث الحالة';
            notification.message = 'الطلب #' + order.id +
                ' تغيرت حالته الى: ' + order.status;
            notification.action = 'تواصل مع الدعم اذا كان لديك اسئلة.';
            notification.icon = 'info';
            notification.color = 'gray';
    }

    console.log('[اشعار] ' + notification.title);
    console.log(notification.message);
    console.log('الاجراء: ' + notification.action);

    return notification;
}

// الاختبار
handleOrderStatus({ id: 'ORD-2024-001', status: 'shipped', trackingNumber: 'TRK-9876543' });
handleOrderStatus({ id: 'ORD-2024-002', status: 'delivered' });
handleOrderStatus({ id: 'ORD-2024-003', status: 'cancelled' });

مثال واقعي: الآلة الحاسبة

بناء آلة حاسبة هو تمرين برمجي كلاسيكي يوضح عبارات switch بشكل مثالي. كل عملية حسابية هي حالة مميزة، وبنية switch تجعل من السهل اضافة عمليات جديدة. اليك تنفيذ آلة حاسبة كاملة الميزات.

مثال: آلة حاسبة مع switch

function calculate(num1, operator, num2) {
    // التحقق من صحة المدخلات
    if (typeof num1 !== 'number' || typeof num2 !== 'number') {
        return { error: true, message: 'كلا المعاملين يجب ان يكونا ارقاما.' };
    }

    if (isNaN(num1) || isNaN(num2)) {
        return { error: true, message: 'NaN ليس معاملا صالحا.' };
    }

    let result;
    let operationName;

    switch (operator) {
        case '+':
        case 'add':
            result = num1 + num2;
            operationName = 'جمع';
            break;

        case '-':
        case 'subtract':
            result = num1 - num2;
            operationName = 'طرح';
            break;

        case '*':
        case 'x':
        case 'multiply':
            result = num1 * num2;
            operationName = 'ضرب';
            break;

        case '/':
        case 'divide':
            if (num2 === 0) {
                return {
                    error: true,
                    message: 'القسمة على صفر غير مسموحة.'
                };
            }
            result = num1 / num2;
            operationName = 'قسمة';
            break;

        case '%':
        case 'mod':
        case 'modulo':
            if (num2 === 0) {
                return {
                    error: true,
                    message: 'باقي القسمة على صفر غير مسموح.'
                };
            }
            result = num1 % num2;
            operationName = 'باقي القسمة';
            break;

        case '**':
        case 'pow':
        case 'power':
            result = num1 ** num2;
            operationName = 'الاس';
            break;

        default:
            return {
                error: true,
                message: 'عامل غير معروف: "' + operator + '". العوامل الصالحة: +, -, *, /, %, **'
            };
    }

    // تنسيق النتيجة للعرض
    let displayResult = Number.isInteger(result) ? result : parseFloat(result.toFixed(10));

    return {
        error: false,
        operation: operationName,
        expression: num1 + ' ' + operator + ' ' + num2,
        result: displayResult,
        message: num1 + ' ' + operator + ' ' + num2 + ' = ' + displayResult
    };
}

// اختبار جميع العمليات
console.log(calculate(10, '+', 5));      // 15
console.log(calculate(10, '-', 3));      // 7
console.log(calculate(6, '*', 7));       // 42
console.log(calculate(20, '/', 4));      // 5
console.log(calculate(17, '%', 5));      // 2
console.log(calculate(2, '**', 10));     // 1024
console.log(calculate(10, '/', 0));      // خطا: القسمة على صفر
console.log(calculate(5, 'multiply', 3)); // 15 (باستخدام عامل كلمة)
console.log(calculate(5, '@', 3));       // خطا: عامل غير معروف

نمط متقدم: البحث بالكائنات كبديل لـ switch

في كثير من الحالات، يمكنك استبدال عبارة switch بـ بحث بالكائنات (Object Lookup). هذا النمط اكثر ايجازا ويمكن ان يكون اكثر اداء لاعداد كبيرة من الحالات. انه مفيد بشكل خاص عندما يربط switch ببساطة قيما بقيم اخرى بدون منطق معقد.

مثال: البحث بالكائنات مقابل switch

// استخدام switch
function getColorHexSwitch(colorName) {
    switch (colorName.toLowerCase()) {
        case 'red':     return '#FF0000';
        case 'green':   return '#00FF00';
        case 'blue':    return '#0000FF';
        case 'yellow':  return '#FFFF00';
        case 'purple':  return '#800080';
        case 'orange':  return '#FFA500';
        case 'black':   return '#000000';
        case 'white':   return '#FFFFFF';
        default:        return null;
    }
}

// استخدام البحث بالكائنات (انظف للربط البسيط)
function getColorHexLookup(colorName) {
    let colorMap = {
        'red':    '#FF0000',
        'green':  '#00FF00',
        'blue':   '#0000FF',
        'yellow': '#FFFF00',
        'purple': '#800080',
        'orange': '#FFA500',
        'black':  '#000000',
        'white':  '#FFFFFF'
    };

    return colorMap[colorName.toLowerCase()] || null;
}

// كلاهما ينتج نفس النتيجة
console.log(getColorHexSwitch('blue'));   // "#0000FF"
console.log(getColorHexLookup('blue'));   // "#0000FF"

// البحث بالكائنات مع الدوال (للحالات التي تحتاج منطقا)
let operationsMap = {
    '+': function(a, b) { return a + b; },
    '-': function(a, b) { return a - b; },
    '*': function(a, b) { return a * b; },
    '/': function(a, b) { return b !== 0 ? a / b : 'خطا: القسمة على صفر'; }
};

function quickCalc(a, op, b) {
    let operation = operationsMap[op];

    if (!operation) {
        return 'عامل غير معروف: ' + op;
    }

    return operation(a, b);
}

console.log(quickCalc(10, '+', 5));  // 15
console.log(quickCalc(10, '/', 0));  // "خطا: القسمة على صفر"
console.log(quickCalc(10, '@', 5));  // "عامل غير معروف: @"
ملاحظة: البحث بالكائنات ممتاز لربط القيم البسيطة وعندما تنفذ كل "حالة" اجراء واحدا بسيطا. ومع ذلك، عندما تتضمن حالاتك عبارات متعددة او منطقا معقدا او سلوك السقوط، عبارة switch لا تزال الخيار الافضل. اختر النهج الذي يجعل الكود اكثر قابلية للقراءة للموقف المحدد.

الاخطاء الشائعة وكيفية تجنبها

فهم اخطاء عبارة switch الشائعة سيساعدك على كتابة كود اكثر موثوقية. اليك الاخطاء الاكثر تكرارا التي يرتكبها المطورون وكيفية منعها.

مثال: الاخطاء الشائعة

// الخطا 1: عدم تطابق النوع (switch يستخدم ===)
let userInput = '5'; // سلسلة من حقل نموذج

switch (userInput) {
    case 5: // الرقم 5، وليس السلسلة "5"!
        console.log('هذا لن يتطابق!');
        break;
    case '5': // السلسلة "5" -- هذه تتطابق
        console.log('هذا سيتطابق.');
        break;
}

// الاصلاح: حول الانواع صراحة
let numericInput = Number(userInput);
switch (numericInput) {
    case 5:
        console.log('الآن يتطابق!');
        break;
}

// الخطا 2: نسيان break (سقوط غير مقصود)
let size = 'medium';
let price;

// خطا: break مفقود
switch (size) {
    case 'small':
        price = 5;
    case 'medium':
        price = 8;
    case 'large':
        price = 12;
}
console.log(price); // 12! وليس 8، لان السقوط يعين 12 اخيرا

// الاصلاح: استخدم دائما break او return
switch (size) {
    case 'small':
        price = 5;
        break;
    case 'medium':
        price = 8;
        break;
    case 'large':
        price = 12;
        break;
}
console.log(price); // 8 -- صحيح!

// الخطا 3: تعريف متغيرات بدون نطاق كتلة
let action = 'create';

// الاصلاح: غلف الحالات في كتل لانشاء نطاق مناسب
switch (action) {
    case 'create': {
        let message = 'تم الانشاء!';
        console.log(message);
        break;
    }
    case 'update': {
        let message = 'تم التحديث!'; // لا تعارض -- نطاق كتلة مختلف
        console.log(message);
        break;
    }
}

// الخطا 4: غياب حالة default
let status = 'archived'; // قيمة غير متوقعة

// الاصلاح: ضمن دائما حالة default
switch (status) {
    case 'active':
        console.log('نشط');
        break;
    case 'inactive':
        console.log('غير نشط');
        break;
    default:
        console.log('حالة غير متوقعة: ' + status);
        // سجل، ارم خطا، او تعامل بشكل مناسب
}
تحذير: مشكلة نطاق المتغيرات (الخطا 3) خطيرة بشكل خاص. عندما تعرف متغيرا بـ let او const داخل حالة switch بدون اقواس معقوفة، ذلك المتغير يكون في نطاق كتلة switch بالكامل، وليس فقط الحالة الفردية. هذا يعني انك لا تستطيع تعريف متغير بنفس الاسم في حالة اخرى. غلف دائما اجسام الحالات في اقواس معقوفة {} عندما تحتاج لتعريف متغيرات.

افضل الممارسات لعبارات switch

اتباع افضل الممارسات هذه سيساعدك على كتابة عبارات switch نظيفة وقابلة للصيانة وخالية من الاخطاء في كل مرة.

مثال: ملخص افضل الممارسات

// 1. ضمن دائما break او return في كل حالة
function goodSwitch(value) {
    switch (value) {
        case 'a': return 'ألفا';
        case 'b': return 'بيتا';
        default:  return 'غير معروف';
    }
}

// 2. ضمن دائما حالة default
function withDefault(status) {
    switch (status) {
        case 'ok':
            return 200;
        case 'not_found':
            return 404;
        default:
            console.warn('حالة غير متوقعة: ' + status);
            return 500;
    }
}

// 3. علق السقوط المتعمد
function daysInMonth(month) {
    switch (month) {
        case 2:
            return 28; // مبسط، يتجاهل السنوات الكبيسة

        case 4:
        case 6:
        case 9:
        case 11:
            // سقوط متعمد: هذه الاشهر جميعها 30 يوما
            return 30;

        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            // سقوط متعمد: هذه الاشهر جميعها 31 يوما
            return 31;

        default:
            return -1;
    }
}

// 4. استخدم نطاق الكتلة {} عند تعريف متغيرات في الحالات
function processItem(type) {
    switch (type) {
        case 'book': {
            let discount = 0.1;
            let tax = 0.05;
            return { discount, tax, category: 'ادب' };
        }
        case 'electronics': {
            let discount = 0.05;
            let tax = 0.15;
            return { discount, tax, category: 'تقنية' };
        }
        default: {
            let discount = 0;
            let tax = 0.1;
            return { discount, tax, category: 'عام' };
        }
    }
}

// 5. فكر في البحث بالكائنات للربط البسيط للقيم
let emojiMap = {
    'happy': ':-)',
    'sad': ':-(',
    'surprised': ':-O',
    'angry': '>:-(',
    'cool': 'B-)'
};

function getEmoji(mood) {
    return emojiMap[mood] || ':-|';
}

// 6. ابق الحالات قصيرة -- استخرج المنطق المعقد الى دوال
function handleEvent(event) {
    switch (event.type) {
        case 'click':
            handleClick(event);
            break;
        case 'submit':
            handleSubmit(event);
            break;
        case 'keypress':
            handleKeyPress(event);
            break;
        default:
            logUnknownEvent(event);
    }
}
نصيحة: عند اعادة هيكلة الكود، ابحث عن سلاسل طويلة من عبارات if...else if التي تقارن جميعها نفس المتغير بقيم مختلفة. هذه مرشحات ممتازة للتحويل الى عبارة switch. والعكس صحيح، اذا كان لديك عبارة switch حيث كل حالة تعيد ببساطة قيمة مربوطة، فكر في استبدالها ببحث بالكائنات لكود انظف.

التمرين 1: مخطط اليوم

اكتب دالة تسمى getDaySchedule تاخذ اسم يوم (سلسلة) وترجع كائنا يحتوي جدول اليوم. استخدم عبارة switch مع القواعد التالية: الاثنين حتى الجمعة ايام عمل بانشطة مختلفة -- الاثنين هو "اجتماع الفريق الساعة 9 صباحا، تخطيط السبرنت"، الثلاثاء هو "مراجعة الكود والبرمجة الزوجية"، الاربعاء هو "عمل عميق على تطوير الميزات"، الخميس هو "الاختبار وضمان الجودة"، الجمعة هو "النشر والمراجعة الاستعادية". السبت والاحد يجب ان ترجعا "عطلة -- راحة ومشاريع شخصية". اجمع السبت والاحد باستخدام السقوط. تعامل مع المدخلات غير الصالحة برسالة خطا ذات معنى في حالة default. يجب ان تكون الدالة غير حساسة لحالة الاحرف (حول المدخل الى احرف صغيرة قبل التبديل). اختبر بسبع مدخلات على الاقل بما في ذلك غير الصالحة.

التمرين 2: مترجم لغات

ابن دالة تسمى translate تاخذ كلمة (سلسلة) ورمز لغة هدف (سلسلة). استخدم عبارات switch متداخلة -- اولا switch على رمز اللغة ("es" للاسبانية، "fr" للفرنسية، "de" للالمانية، "ar" للعربية، "ja" لليابانية)، ثم داخل كل حالة لغة، switch على الكلمة لارجاع الترجمة. ادعم على الاقل هذه الكلمات الخمس: "hello"، "goodbye"، "please"، "thanks"، "yes". ضمن حالة default لكل من switch اللغة والكلمة. اختبر الدالة بتركيبات كلمة ولغة مختلفة، بما في ذلك كلمات ولغات غير مدعومة.

التمرين 3: آلة حاسبة متقدمة مع سجل

ابن كائن آلة حاسبة محسن يسمى calculator يستخدم عبارات switch داخليا. يجب ان يكون لديه دالة calculate(num1, operator, num2) تدعم: +، -، *، /، %، ** (الاس)، وايضا max (يرجع الرقم الاكبر)، min (يرجع الرقم الاصغر)، و avg (يرجع المتوسط). يجب على الآلة الحاسبة ايضا الاحتفاظ بمصفوفة history تخزن كل عملية حسابية كسلسلة (مثل "10 + 5 = 15"). اضف دالة getHistory() ترجع جميع العمليات السابقة، ودالة getLastResult() ترجع النتيجة الاحدث، ودالة clearHistory(). تعامل مع القسمة على صفر والعوامل غير الصالحة برسائل خطا مناسبة. استخدم switch مع return للعمليات وتحقق ان تتبع السجل يعمل بشكل صحيح عبر عمليات حسابية متعددة.

ES
Edrees Salih
منذ 16 ساعة

We are still cooking the magic in the way!