اذهب إلى المحتوى

لوحة المتصدرين

  1. سعيد يوسف

    سعيد يوسف

    الأعضاء


    • نقاط

      4

    • المساهمات

      58


  2. Muhammed Hacibrahim

    Muhammed Hacibrahim

    الأعضاء


    • نقاط

      3

    • المساهمات

      148


  3. Ali Ismael

    Ali Ismael

    الأعضاء


    • نقاط

      3

    • المساهمات

      96


  4. توفيق اسحيمة

    توفيق اسحيمة

    الأعضاء


    • نقاط

      3

    • المساهمات

      181


المحتوى الأكثر حصولًا على سمعة جيدة

المحتوى الأعلى تقييمًا في 10/07/21 في كل الموقع

  1. السلام عليكم ورحمه الله وبركاته انا اتعلم في دورة تطوير واجهات المستخدم و وصلت إلى قسم خمس صفحات هبوط و تحديدا في صفحة العد التنازلي للعبة لقد استوعبت كل الدروس html و CSS و bootstrap ولكن اجد صعوبة في لغة جافا سكريبت مع انني متعلم اساسيات لغة python جيدا اريد ان اعرف ان كان هذه طبيعي ام يجب علي تعلمها بشكل جيد اولا لكي استمر في الدورة استطيع مثلا ان اتعلم اساسيات جافا سكريبت في دورة جافا سكريبت في موقع اكاديميه حسوب
    3 نقاط
  2. اثناء تفاعلي مع دورة علوم الحاسوب شرح الأستاذ وائل فوجئت أنني لا أستطيع التعليق على الفيديوهات و ظهرت لي هذه الرسالة المرفقة
    2 نقاط
  3. السلام عليكم لدي هادا المتغير الدي يحتوي على هاته الكلمة دات الحروف المختلطة بين حروف كبيرة وحروف صغيرة let swappedName = "shImA"; احاول ان اتحكم في اعادة جعل الحروف عكس ماهي عليه او بمعنى اخر الحروف الصغيرة تصبح كبيرة والحروف الكبيرة تصبح صغيرة بحيث يكون الناتج كالتالي "SHiMa" ودلك طبعا عن طريق for loop كل محاولاتي بائت بالفشل فمعظم النتائج التي اتحصل عليها اما تكون الكلمة كاملة بحروف كبيرة او العكس جزاكم الله خيرا.
    2 نقاط
  4. سنتعرف في هذا المقال على مفهوم واجهة برمجية التطبيقات Application Programming Interface، هذا المصطلح السهل المعقد حيث سنحاول فهمه وكيفية بناء مواقع الويب والتطبيقات الحديثة في يومنا هذا بالاعتماد على الواجهات البرمجية ونجيب على سؤال مهم وهو كيف ترتبط الواجهة الأمامية مع الواجهة الخلفية لتطبيق الويب أو الموقع الإلكتروني. هذا المقال هو جزء من سلسلة مقالات حول الواجهة البرمجية API وكيفية الاستفادة منها في بناء تطبيق ويب: مدخل إلى الواجهات البرمجية API الاتصال بواجهة زد البرمجية وفهم عملية الاستيثاق والتصريح أمثلة عملية لاستخدام واجهة برمجة متاجر زد zid API تطوير تطبيق عملي يزيد من احتفاظ العملاء عبر واجهة زد البرمجية مفهوم الواجهة البرمجية للتطبيقات API قبل أن ندخل في أية تفاصيل تقنية عن موضوعنا اليوم، سأحكي لك يومًا في حياة علي. علي هو مبرمج تطبيقات ويب يعمل في إحدى الشركات التقنية العربية، علي يحب تجربة المأكوﻻت المختلفة، بحيث يجرب في كل يوم أكلة جديدة في المطاعم المحيطة وإن سمع بافتتاح مطعم جديد بالقرب من مكان عمله، فإنه ﻻ يتوانى في زيارته وتذوق مختلف اﻷطباق التي يقدمها. ذهب علي ﻷحد المطاعم التي فتحت أبوابها مؤخرا، واختار مكانا هادئًا ونادى النادل يسأله عن اﻷطباق التي يقدمونها من أجل أن يأخذ طلبيته إلى الطباخ لتحضير ما طلبه علي. دوَّن النادل ما يريد علي تناوله من مأكوﻻت وذهب بها إلى الطباخ من أجل تحضيرها، بعد مدة وجيزة، عاد النادل إلى علي وهو يحمل كل ما طلبه وقدمها متمنيا أنه يعجبه اﻷكل، تذوق علي المأكوﻻت وأبدى إعجابه اﻷولي بها، وبدأ في اﻷكل إلى أن أنهى كل ما في الصحون، دفع الحساب، وخرج من المطعم شاكرا النادل على حسن اﻷستقبال. هل تتساءل اﻵن، ما علاقة هذه القصة بالواجهات البرمجية؟ وهل تساءلت يومًا عن طريقة عمل تطبيقات الهواتف الذكية، وكيف تتصل بخوادم الشركات المطورة لها، هل استطعت الوصول إلى إجابات كافية عن ذلك؟ سأبين لك ما العلاقة بين المثال السابق والواجهات البرمجية وكيف أن الواجهات البرمجية ماهي إﻻ تطبيق لمثالنا باختلاف بسيط وهو مكان التطبيق فقط، فمثالنا هو من الواقع الذي نعيشه يوميًا ونراه دائمًا حتى تعودنا عليه حتى أصبحنا ﻻ ندركه، أما الواجهات البرمجية، فقد أصبحت جزءًا ﻻ يتجزأ من حياة مبرمج المواقع وتطبيقات الويب المهنية. كنت قد نوهتك ﻷن تسأل نفسك عن طريقة ربط تطبيقات الهواتف الذكية مع خوادم الشركات. هنالك طريقتين لربط تطبيقات الهواتف الذكية مع خوادم الشركات المطورة، الطريقة اﻷقدم تسمى SOAP وهي اختصار لجملة Simple Object Access Protocol، أما الطريقة اﻷحدث فهي الواجهة البرمجيةللتطبيقات API وهي اختصار لجملة Application Programming Interface، وهي التي سأركز عليها، ولكن باختصار، API هي طريقة لتواصل البرمجيات في ما بينها باستخدام صيغة JavaScript Object Notation والتي تعرف اختصارا بـ JSON. لن أدخل في التفاصيل التاريخية وسأبقى مركزا على الجانب التقني فقط، لهذا أتوقع منك أن تحاول البحث عن تاريخ ابتكار وتطوير تقنية API والتقنية المكملة لها REST والتي هي اختصار لجملة REpresentational State Transfer. مصطلحات وجب معرفتها سنسرد بعض المصطلحات باللغة الإنجليزية والعربية الضروري على كل مطور ويب أن يعرفها: Backend: الواجهة الخلفية، هي المسؤولة عن العمليات المنطقية للنظام، تتعامل مع الملفات أيضا ومع قواعد البيانات. Frontend: الواجهات الأمامية، كل ما يراه المستخدم ويتعامل معه بشكل مباشر، ويتم ربطها مع النظم الخلفية بما يعرف بالواجهة البرمجية للتطبيقات API. API: الواجهة البرمجية للتطبيقات، هي حلقة الوصل ما بين النظم أو الواجهة الخلفية والواجهات الأمامية. Request: الطلب الذي يرسله العميل (قد تكون الواجهة الأمامية) إلى الخادم Server الموجود في الواجهة الخلفية. Header: ترويسة الطلب Request المرسل والذي يحوي بعض البيانات الوصفية التي تصف الطلبية وحالها وأية معلومات إضافية مطلوبة. Body: جسم أو متن الطلب المرسل والذي يحوي غالبًا على البيانات المتبادلة في الطلبية. Response: استجابة أو رد الخادم وهي المعلومات الراجعة من الخادم إلى العميل مقدم الطلب ردًا على طلبه. تحوي المعلومات الراجعة من الخادم إلى العميل على ترويسة Header وأيضا على متن Body. Endpoint: نقطة الوصول، وهي نقطة اتصال الواجهات الأمامية مع موقع محدد في الواجهة الخلفية أي نقطة محددة تتصل عبرها الواجهة الأمامية مع الواجهة الخلفية لغرض محدَّد. HTTP Client Software: عميل خادم HTTP وهو برنامج يساعد على تسريع التعامل مع الواجهات البرمجية بتوفير آلية واضحة في عملية إرسال واستقبال الطلبيات والردود. هل تعرفت على أي من المصطلحات التي ذكرناها قبل قليل؟ لا بأس إن لم تفعل، فسنشرحها لك حتى تكون لديك معرفة مبدئية بموضوع الواجهات البرمجية. لماذا نستخدم الواجهات البرمجية للتطبيقات APIs وما هي فائدتها؟ تُعَد الواجهات البرمجية للتطبيقات طبقة الحماية الأولى First Security Layer للبرمجية الموجودة على خادم الويب، بسبب أنها تفصل ما بين النظم الخلفية والعمليات الجارية على قواعد البيانات عن الواجهات الأمامية سواءً كانت صفحات ويب عادية أو تطبيقات هواتف ذكية. أي أن أي تطبيق ويب أو موقع اليوم يتألف من واجهة خلفية وواجهة أمامية وواجهة برمجية تعد وصلة وصل بينهما. أما الواجهة الخلفية، فتحوي على كامل العمليات والإجراءات والخدمات التي يوفرها التطبيق أو الموقع مثل معالجة صورة أو بيانات أو حتى تقديم خدمة الطقس. أما الواجهة الأمامية فهي الواجهة التي يراها المستخدم والمسؤولة عن عرض البيانات القادمة من الواجهة الخلفية للمستخدم بصورة مناسبة ومتناسقة مع إرسال البيانات من المستخدم إلى الخادم بالشكل الذي يطلبها، فالبيانات المتبادلة تلك تكون بشكلها الخام (تستعمل غالبًا صيغة JSON أو حتى صيغة XML)، أما الواجهة البرمجية للتطبيقات API فهي صلة الوصل كما ذكرنا ووظيفتها استلام البيانات من الواجهة الأمامية وتسلميها للواجهة الخلفية وإرسال البيانات من الواجهة الخلفية إلى الأمامية بطريقة وأسلوب موحد أي هي التي تؤمن عملية التفاهم بين الواجهة الأمامية والخلفية لتأمين التخاطب فيما بينهما. كيف تعمل الواجهات البرمجية للتطبيقات API سأحاول قدر اﻹمكان تبسيط آلية عمل الواجهات البرمجية بمثال عملي من حياتنا اليومية، وليكن مثلا منصة فيسبوك. كما تعلم أنه بإمكانك الدخول إلى حسابك في فيسبوك من أي جهاز تريد، سواءً من هاتفك الذكي أو من جهازك اللوحي أو من جهاز الحاسوب بل بإمكانك الدخول منها مجتمعة وفي نفس الوقت، وهنا يجب أن تطرح سؤاﻻ مهمًا، كيف تتم مزامنة حسابك في كل تلك اﻷجهزة؟ هنا تأتي أهمية الواجهة البرمجية، بحيث أن كل تلك اﻷجهزة متصلة بنظام خلفي واحد وكلها تتصل بالواجهة البرمجية التي تكون حلقة الوصل ما بين كل اﻷجهزة المتصلة و النظام الخلفي. سنأخذ مثاﻻ من حياتنا اليومية وهو موقع فيسبوك، سنقوم بالدخول إلى حسابنا باستخدام الأجهزة التي بحوزتنا، إن لم تكن لديك أجهزة غير جهاز الحاسوب، افتح أكثر من متصفح، ليس نفس المتصفح، مثلا متصفح كروم Google Chrome ومتصفح فايرفوكس Mozilla Firefox، في هذه الحالة يمكنك فتح حسابك 4 مرات باستخدام التصفح الخفي، في متصفح كروم يسمى Incognito Mode أما في متصفح فايرفوكس فيسمى Private Mode. هل قمت بذلك؟ كيف تستطيع إرسال رسائل إلى أصدقائك من أي متصفح وتشاهدها في نفس الوقت من بقية المتصفحات؟ قم بالدخول إلى حسابك على فيسبوك من هاتفك الذكي، من التطبيق الرسمي أو من المتصفح، هل تستطيع أن ترى الرسائل التي قمت بإرسالها على هاتفك أيضا، كيف يحدث ذلك؟ كيف تستطيع الدخول إلى حسابك من أماكن مختلفة في نفس الوقت؟ سأشرح العملية بأكملها بشكل بسيط وبالمقارنة مع مثالنا في بداية المقال وبدون الدخول في التفاصيل الدقيقة في الوقت الحالي. عند دخول علي مطور الويب إلى المطعم، كان عليه أن يختار طاولة محددة برقم حتى يعلم النادل موقعه وأنه يريد تناول الطعام وبالتالي يستطيع تقديم مختلف الخدمات التي يعرضها المطعم. هنا الطاولة وتفاصيلها (من رقم وحجم وغيرهما) تعتبر المكان المتفق عليه من أجل اﻹستفادة من خدمات المطعم، ويمكن القول أنها نقطة الوصول إلى خدمات المطعم Endpoint. في حالة موقع فيسبوك، وعند قيامك فتح التطبيق مثلا، سيتصل تطبيقك بخادم الشركة، في نقطة متفق عليها ومحددة مسبقًا في التطبيق وفيها فقط يستطيع الخادم أن يقدم خدماته للتطبيق. جاء النادل إلى عليٍ والذي يسمى العميل client ليأخذ الطلبات منه، ودون أية ملاحظات أو أي خدمات أخرى، وبعدها ذهب إلى المطبخ ليخبر الطباخ بالطلبات من أجل تحضيرها. هنا نسمي العملية: إرسال طلب Send Request من العميل علي إلى الطباخ في المطعم مقدمة الخدمة. في حالة موقع فيسبوك، أقرب عملية لذلك المثال عملية تسجيل الدخول حيث تُدخل اسم المستخدم الخاص بك مع كلمة المرور، تأخذ الواجهة الأمامية منك هذه المعلومات وترسلها للواجهة الخلفية لموقع فيسبوك لتتحقق منها ومن الطلب الخاص بك، طلب تسجيل الدخول. يستلم الطباخ الطلبية ويتأكد من أنها طلبية صالحة ويمكنك تحضيرها (أي ليست طلبية شراء ملابس مثلًا) ثم يبدأ بتحضيرها وعندما ينتهي منها، يعطيها للنادل الذي يرتبها بدوره في صينية ويأخذها إلى علي ليضعها على طاولته حتى يتسنى له البدء في تذوقها. هذه العملية تسمى: اﻹستجابة Send Response أي استجاب الطباخ لطلبية علي وقدم له ما يريد. وفي حالة موقع فيسبوك، إن كانت المعلومات المقدمة صالحة، سيقوم خادم فيسبوك بالسماح لك بالدخول واستعراض مختلف الصفحات واﻷجزاء الخاصة به والاستفادة من خدمته التي يقدمها. هل اتضحت الصورة العامة اﻵن؟ ببساطة، الواجهة البرمجية تنفذ عمل النادل في المطعم، حيث أن النادل يقوم بأخذ طلبات الزبائن إلى الطباخ وفريقه لتحضيرها وبعد ذلك، يقوم بأخذ تلك استجابة الطباخ لتلك الطلبات إلى أصحابها، أي أن الواجهة البرمجية تأخذ الطلبات من المستخدمين (الواجهة الأمامية) إلى النظام الخلفي لتقوم بعمل محدد ومن ثم تعيد النتائج المتحصل عليها إلى طالبيها أي تعيدها للواجهة الأمامية مرةً أخرى. خاتمة تعرفنا على ماهية الواجهة البرمجية للتطبيقات وأهم المصطلحات فيها وكيف يستفيد منها المطورون في بناء تطبيقات الويب الحديثة واستثمارها في التواصل ما بين الواجهة الأمامية والخلفية لتطبيقات الويب والمواقع الحالية، فالتعامل مع الواجهة البرمجية للتطبيقات ضروري لأي مبرمج متخصص في تطوير الويب، وعليه أن يعي مفهوم الواجهة البرمجة تمامًا إذ أصبح هذا المفهوم هو المفهوم الحديث في التواصل ما بين الواجهة البرمجية الخلفية والأمامية للمواقع وتطبيقات الويب، أضف إلى ذلك أن الكثير من الخدمات والمواقع أصبحت تتيح واجهتها البرمجية (مثل الواجهة البرمجية للمطورين من فيسبوك وتويتر وغيرهما) للاستفادة منها أو حتى هنالك واجهة برمجية مخصصة فقط لتقديم خدمات محددة (مثل واجهة برمجية للحصول على معلومات الطقس) وتقدمها للمطورين للاستفادة من تلك الخدمات في مختلف المشاريع. اقرأ أيضًا المقال التالي: الاتصال بواجهة زد البرمجية وفهم عملية الاستيثاق والتصريح كيفية إنشاء متجر إلكتروني متكامل باستعمال منصة زد الواجهة البرمجية Fetch API في جافاسكريبت
    1 نقطة
  5. بقرا في البرمجة عن المصطلحات التالية وأريد شرح عنهم program specifications problem solving implementation testing debuging
    1 نقطة
  6. أريد وضع تعليق بجانب كل نقطة من نقاط البيانات في ال plot على سبيل المثال لدي الرسم التالي: import matplotlib.pyplot as plt fx = [2.1252, 3.5534, 4.3552, 6.3418, 1.055] x = [0.2, 0.28, 0.56, 0.599, 0.80] annotate = ["Syria", "Turkia", "USA", "Qatar", "Polnda"] fig, ax = plt.subplots() ax.scatter(x, fx) والشكل المقابل: أريد أن أضع بجانب كل نقطة من نقاط البيانات أعلاه القيمة التي تقابلها من القائمة annotate.
    1 نقطة
  7. كيف يمكننا قياس أداء تابع أو مجموعة عمليات (Block) في OpenCV وهل توجد طريقة لرفع الأداء (سرعة التنفيذ)؟
    1 نقطة
  8. ماهي النظم الخبيرة وهل يوجد دورة لها هنا وهل هناك ترك معين تنصحون به لكي اتعلمها؟
    1 نقطة
  9. أريد تعريف لخادم Bitnami ومعرفة المجالات التي يستخدم فيها وكيف أستطيع تنصيبه وإدارته
    1 نقطة
  10. لا مشكلة ان كنت تواجه صعوبة في جافاسكربت, لأنك في بداية الطريق فقط وتحتاج الى المزيد من التركيز والصبر, حاول فهم الفكرة العامة من أكواد جافاسكربت الموجودة في المسارات, بعد الانتهاء من المسارات سوف تجد أنه تكون لديك خبرة جيدة في جافاسكربت, ولكن أيضا سوف تحتاح للعديد من التدريبات فيها حيث أن المسارات سوف تقوم باعطاء اساسيات جافاسكربت والعديد من التمارين عليها وهذا غير كافي لزيادة الخبرة فيها, هناك طريقة جيدة أيضا لفهم الأكواد, حاول أن تركز مع المدرب ورؤيته وهو يطبقها في البداية, ثم طبق معه الأكواد, ثم أخيرا حاول كتابة الكود بنفسك, اذا لم واجهتك مشكلة في كتابة الكود بنفسك يمكنك مراجعة الدرس مرة أخرى لكي تثبت لديك الفكرة, واستمر في مراجعة الأكواد وتذكر أن كل بداية صعبة
    1 نقطة
  11. لدي مخطط وأريد حذف أو إخفاء ال xticks منه كيف يمكن القيام بذلك؟ وكذلك كيف يمكننا التعديل عليها؟ import matplotlib.pyplot as plt import numpy as np x = np.array([0, 1, 2, 3]) y = np.array([3, 8, 1, 10]) plt.subplot(1, 2, 1) plt.title("First Plot") plt.plot(x,y) plt.show()
    1 نقطة
  12. أريد تعريف لخادم LAMP ومعرفة المجالات التي يستخدم فيها وكيف يمكنني تثبيته وإدارته
    1 نقطة
  13. لتثبيت البرمجيات على لينوكس نستخدم الأداة apt، أولا نتأكد من تحديثها: sudo apt-get update ثم نحدث المكتبات والبرمجيات الحالية system cache sudo apt update ثم نثبت مخدم أباتشي apache2 sudo apt install apache2 الآن نعدل سماحية جدار الحماية لنسمح للمخدم بتبادل البيانات والوصول للشبكة: sudo ufw app list يظهر التالي ونختار: Output Available applications: Apache Apache Full Apache Secure OpenSSH sudo ufw app info "Apache Full" حيث أنه بعمل على المنفذين ports 80 and 443. الآن ليصبح لنا الموقع متاح، على الرابط مثلا: http://your_server_ip ونضع عنوان IP من ناتج التعليمة التالية: ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//' وإن احتجت curl utility نثبتها: sudo apt install curl ثم تثبيت قاعدة البيانات: sudo apt install mysql-server sudo mysql_secure_installation وتشغيلها sudo mysql ثم نثبت PHP مع الحزم اللازمة لاتصالها مع قاعدة البيانات: sudo apt install php libapache2-mod-php php-mysql ثم نعيد تشغيل المخدم sudo systemctl restart apache2 يمكن أيضا تثبيت phpmyadmin sudo apt-get install phpmyadmin في حال احتجت لاستخدام SSL / HTTPS: sudo apt-get install apache2 apache2-doc apache2-npm-prefork apache2-utils libexpat1 ssl-cert
    1 نقطة
  14. LAMP هو إختصار ل(linux, apache, mysql, python/php/perl) هي مجموعة من الحلول تُشبه xampp(apache,mariadb,perl,php) تُستخدم حقبة خدمات lamb لإدارة خوادم الويب وإنشاء المواقع وهي من حقب الحلول المُفضلة لدى مستخدمين لينكس حيث الapache : يُمثل خادم الويب الذي يتم إستضافة وإدارة الموقع من خﻻله linux يُمثل نظام التشغيل مفتوح المصدر الشهير والمُستخدم مع أغلب الخوادم mariadb/mysql تُمثل قاعدة البيانات المُستخدمة python/php/perl تُمثل لغة البرمجة المُستخدمة في برمجة الخادم والتي يُمكن أن تكون أي لغة مُستخدمة في البرمجة الخلفية (backend)
    1 نقطة
  15. الخوارزمية يمكنك تقسيم النص الى مصفوفة حروف عبر الدالة split بتمرير محرف فارغ لها كالتالي: "shImA".split('') // ['s', 'h', 'I', 'm', 'A'] ثم المرور على مصفوفة الحروف وتغيير حالتها عن طريق الدالة map من أحرف كبيرة إلى صغيرة وبالعكس، عبر مقارنتها بعد التحويل وإعادة الحالة المناسبة كالتالي: "shImA".split('') .map(char => char.toUpperCase() === char ? char : char.toLowerCase()) // ['S', 'H', 'i', 'M', 'a'] ثم إعادة تجميع مصفوفة الحروف إلى نص مجددا عبر الدالة join بتمرير محرف فارغ لتحصل على المطلوب كالتالي: "shImA".split('') .map(char => char.toUpperCase() === char ? char : char.toLowerCase()) .join('') // SHiMa كتابة دالة toggleCase يمكنك كتابة ما سبق في دالة: function toggleCase(text) { return text.split('') .map(char => char.toUpperCase() === char ? char : char.toLowerCase()) .join('') } واستخدامها داخل تطبيقك: let swappedName = "shImA"; console.log(toggleCase(swappedName)) // SHiMa
    1 نقطة
  16. شكرا لك اخي اعتقد انك لم تفهم المقصود فالدوال toUpperCase and toLowerCase اعرف دورهما لكن في الحالة التي دكرت اعلاه نريد ان نغير شكل تلك الكلمة التي ضمن المتغير بحيت انها تحتوي في نفس الوقت على خليط من الاحرف الكبيرة والصغيرة ما نريده بالضبط هو تحويل الاحرف الكبيرة الى احرف صغيرة وتحويل الاحرف الصغيرة الى احرف كبيرة شكرا لك
    1 نقطة
  17. بالإضافة إلى إجابة أستاذ وائل, فإن بيانات الفهرس يتم تخزينها على شاكلة هيكل بيانات الشجرة المتوازنة(b+tree) والتي تعتمد في طريقة تخزينها على أن يحمل كل عنصر في الهيكل ثﻻث معلومات قيمة العنصر مؤشر(pointer) يُشير إلى العنصر على يمينه مؤشر(pointer) يُشير إلى العنصر على يساره وتعتمد الأشجار المتوازنة في ألية عملها على أن دائما وأبداً يكون كل عنصر أكبر من العناصر على يمينه , وأصغر من العناصر على يساره مما يُسهل عملية البحث ويجعلها في أسوأ الحالات تأخذ تعقيد وقتي قيمته O(logn) فعندما نريد إذاً البحث عن عنصر ما في قاعدة البيانات ﻻ نحتاج أن نمر على جميع العناصر وإنما فقط نقوم بالمرور على عدد من العناصر يساوي لوغاريتم العنصر للأساس 2 في أسوأ الحالات ومن الممكن تحديد هيكل البيانات المُستخدم أن يكون من النوع جدول التجزئة( hash table) والذي يعتمد في ألية عمله أن يكون على هيئة القيمة والمفتاح (key&value) فيتم تخزين ناتج تجزئة مفتاح( element hashing) في المصفوفة الخاصة بالجدول, وعند الإحتياج للوصول إليه يتم ذلك في تعقيد وقتي O(1) حيث أن ناتج التجزئة يكون ثابت دائماً فﻻ نحتاج إذا للبحث, ولكن في الحياة العملية ﻻ يتم إستخدام الجدول بسبب وجود عدد من المشاكل مثل إن تم تخزين أكثر من مفتاح لهم نفس ناتج التجزئة(hashing) تحدث حالة تداخل(collision) فيتم تخزين كلا العنصرين في قائمة ويتم تخزين تلك القائمة في العنصر في المصفوفة, وكثرة التداخلات تُسبب إستهﻻك للموارد وأداء سيئ نسبياً ﻻ تعمل جيداً مع العمليات التي نحتاج فيها إلى إستخدام معاملات أكبر من, أو أصغر من , تعمل فقط مع معامل المساواة فمثلاً جملة مثل select * from student where indx>5 سيتم معالجتها بأداء سيئ عند إستخدام جدول التجزئة حيث أنه ﻻ يقوم بتخزين قيمة الفهرس وإنما يٌخزن قيمة التجزئة , فسيكون التعقيد الوقتي هنا مساوي لO(n)
    1 نقطة
  18. يمكنك استخدام دالة toUpperCase في جافاسكريبت بحيث تكون الأسماء لديك بهذا الشكل const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; ثم يمكنك إنشاء for loop بهذا الشكل , ليتم تحويل جميع الكلمات إلى UpperCase عن طريق الدالة toUpperCase for ( let i = 0; i < days.length; i++ ) { days[i] = days[i].toUpperCase(); console.log(days[i]); } فيكون كامل الكود كالتالي const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; for ( let i = 0; i < days.length; i++ ) { days[i] = days[i].toUpperCase(); console.log(days[i]); }
    1 نقطة
  19. ركّزنا في المقالات السابقة من هذه السلسلة على صحة البرامج، وإلى جانب ذلك، تُعَد مشكلة الكفاءة efficiency من المشاكل المهمة كذلك، فعندما نحلِّل كفاءة برنامجٍ ما، فعادةً ما تُطرح أسئلةٌ مثل كم من الوقت سيستغرقه البرنامج؟ وهل هناك طريقةٌ أخرى للحصول على نفس الإجابة ولكن بطريقةٍ أسرع؟ وعمومًا دائمًا ما ستكون كفاءة البرنامج أقلّ أهميةً من صحته؛ فإذا لم تهتم بصحة البرنامج، فيمكنك إذًا أن تشغّله بسرعةٍ، ولكن قلّما سيهتم به أحدٌ. كذلك لا توجد أي فائدةٍ من برنامجٍ يستغرق عشرات الآلاف من السنين ليعطيك إجابةً صحيحةً. يشير مصطلح الكفاءة عمومًا إلى الاستخدام الأمثل لأي مورِد resource بما في ذلك الوقت وذاكرة الحاسوب ونطاق التردد الشبكي، وسنركّز في هذا المقال على الوقت، والسؤال الأهم الذي نريد الإجابة عليه هو ما الوقت الذي يستغرقه البرنامج ليُنجز المَهمّة الموكلة إليه؟ في الواقع، ليس هناك أي معنىً من تصنيف البرامج على أنها تعمل بكفاءةٍ أم لا، وإنما يكون من الأنسب أن نوازن بين برنامجين صحيحين ينفّذان نفس المهمة لنعرف أيًا منهما أكثر كفاءةً من الآخر، بمعنى أيهما ينجز مهمّته بصورةٍ أسرع، إلا أن تحديد ذلك ليس بالأمر السهل لأن زمن تشغيل البرنامج غير معرَّف؛ فقد يختلف حسب عدد معالجات الحاسوب وسرعتها، كما قد يعتمد على تصميم آلة جافا الافتراضية Java Virtual Machine (في حالة برامج جافا) المفسِّرة للبرنامج؛ وقد يعتمد كذلك على المصرّف المستخدَم لتصريف البرنامج إلى لغة الآلة، كما يعتمد زمن تشغيل أي برنامجٍ على حجم المشكلة التي ينبغي للبرنامج أن يحلّها، فمثلًا سيستغرق برنامج ترتيب sorting وقتًا أطول لترتيب 10000 عنصر مما سيستغرقه لترتيب 100 عنصر؛ وعندما نوازن بين زمنيْ تشغيل برنامجين، سنجد أن برنامج A يحلّ المشاكل الصغيرة أسرع بكثيرٍ من برنامج B بينما يحلّ البرنامج B المشاكل الكبيرة أسرع من البرنامج A، فلا يوجد برنامجٌ معينٌ هو الأسرع دائمًا في جميع الحالات. عمومًا، هناك حقلٌ ضِمن علوم الحاسوب يُكرّس لتحليل كفاءة البرامج، ويعرف باسم تحليل الخوارزميات Analysis of Algorithms، حيث يركِّز على الخوارزميات نفسها لا البرامج؛ لتجنُّب التعامل مع التنفيذات implementations المختلفة لنفس الخوارزمية باستخدام لغاتٍ برمجيةٍ مختلفةٍ ومصرّفةٍ بأدواتٍ مختلفةٍ وتعمل على حواسيب مختلفةٍ؛ وعمومًا يُعَد مجال تحليل الخوارزميات مجالًا رياضيًا مجرّدًا عن كل تلك التفاصيل الصغيرة، فعلى الرغم من أنه حقلٌ نظريٌ في المقام الأول، إلا أنه يلزَم كلّ مبرمجٍ أن يطّلع على بعضٍ من تقنياته ومفاهيمه الأساسية، ولهذا سيَتناول هذا المقال مقدمةً مختصرةً جدًا عن بعضٍ من تلك الأساسيات والمفاهيم، ولأن هذه السلسلة ليست ذات اتجاه رياضي، فستكون المناقشة عامةً نوعًا ما. يُعَد التحليل المُقارِب asymptotic analysis أحد أهم تقنيات ذلك المجال، ويُقصد بالمُقارِب asymptotic ما يميل إليه على المدى البعيد بازدياد حجم المشكلة، حيث يجيب التحليل المُقارِب لزمن تشغيل run time خوارزميةٍ عن أسئلةٍ مثل، كيف يؤثر حجم المشكلة problem size على زمن التشغيل؟ ويُعَد التحليل مُقارِبًا؛ لأنه يهتم بما سيحدث لزمن التشغيل عند زيادة حجم المشكلة بدون أي قيودٍ، أما ما سيحدث للأحجام الصغيرة من المشاكل فهي أمورٌ لا تعنيه؛ فإذا أظهر التحليل المُقارِب لخوارزمية A أنها أسرع من خوارزمية B، فلا يعني ذلك بالضرورة أن الخوارزمية A ستكون أسرع من B عندما يكون حجم المشكلة صغيرًا مثل 10 أو 1000 أو حتى 1000000، وإنما يعني أنه بزيادة حجم المشكلة، ستصل حتمًا إلى نقطةٍ تكون عندها خوارزمية A أسرع من خوارزمية B. يرتبط مفهوم التحليل المُقارِب بـمصطلح ترميز O الكبير Big-Oh notation، حيث يمكّننا هذا الترميز من قوْل أن زمن تشغيل خوارزميةٍ معينةٍ هو O(n2)‎ أو O(n)‎ أو O(log(n))‎، وعمومًا يُشار إليه بـ O(f(n))‎، حيث f(n)‎ عِبارةٌ عن دالة تُسند عددًا حقيقيًا موجبًا لكلّ عددٍ صحيحٍ موجبٍ n، بينما يشير n ضِمن هذا الترميز إلى حجم المشكلة، ولذلك يلزَمك أن تحدّد حجم المشكلة قبل أن تبدأ في تحليلها، وهو ليس أمرًا معقدًا على أية حال؛ فمثلًا، إذا كانت المشكلة هي ترتيب قائمةٍ من العناصر، فإن حجم تلك المشكلة يمكنه أن يكون عدد العناصر ضِمن القائمة، وهذا مثالٌ آخرٌ، عندما يكون مُدخَل الخوارزمية عِبارةٌ عن عددٍ صحيحٍ (لفحْص إذا ما كان ذلك العدد عددًا أوليًا prime أم لا) يكون حجم المشكلة في تلك الحالة هو عدد البتات bits الموجودة ضِمن ذلك العدد المُدخَل لا العدد ذاته؛ وعمومًا يُعَد عدد البتات bits الموجودة في قيمة المُدخَل قياسًا جيدًا لحجم المشكلة. إذا كان زمن تشغيل خوارزميةٍ معينةٍ هو O(f(n))‎، فذلك يعني أنه بالنسبة للقيم الكبيرة من حجم المشكلة، لن يتعدى الزمن حاصل ضرْب قيمةٍ ثابتةٍ معينةٍ في f(n)‎، أي أن هناك عدد C وعددٌ صحيحٌ آخرٌ موجبٌ M، وعندما تصبح n أكبر من M، فإن زمن تشغيل الخوارزمية يكون أقلّ من أو يساوي C×f(n)‎؛ حيث يأخذ الثابت C في حسبانه تفاصيلًا مثل سرعة الحاسوب المستخدَم في تشغيل الخوارزمية، وإذا كان ذلك الحاسوب أبطأ، فقد تستخدم قيمة أكبر للثابت، إلا أن تغييره لن يغيّر من حقيقة أن زمن تشغيل الخوارزمية هو O(f(n)‎؛ وبفضل ذلك الثابت، لن يكون من الضروري تحديد إذا ما كنا نقيس الزمن بالثواني أو السنوات أو أي وحدة قياسٍ أخرى؛ لأن التحويل من وحدةٍ معينةٍ إلى أخرى يتمثَّل بعملية ضربٍ في قيمة ثابتة، بالإضافة إلى ما سبق، لا تَعتمد O(f(n))‎ نهائيًا على ما يحدُث في الأحجام الأصغر من المشكلة، وإنما على ما يحدُث على المدى الطويل بينما يزيد حجم المشكلة دون أي قيودٍ. سنفحص مثالًا بسيطًا عِبارةً عن حساب حاصل مجموع العناصر الموجودة ضِمن مصفوفةٍ، وفي هذه الحالة، يكون حجم المشكلة n هو طول تلك المصفوفة، فإذا كان A هو اسم المصفوفة، ستُكتب الخوارزمية بلغة جافا كالتالي: total = 0; for (int i = 0; i < n; i++) total = total + A[i]; تنفِّذ الخوارزمية عملية total = total + A[‎i]‎ عدد n من المرات، أي أن الزمن الكلي المُستغرق أثناء تلك العملية يساوي a×n، حيث a هو زمن تنفيذ العملية مرةٍ واحدةٍ؛ إلى جانب ذلك، تزيد الخوارزمية قيمة المتغيّر i وتوازنه مع قيمة n في كلّ مرةٍ تنفِّذ فيها مَتْن الحلقة loop، الأمر الذي يؤدي إلى زيادة زمن التشغيل بمقدار يساوي b×n حيث b عِبارةٌ عن ثابتٍ، وبالإضافة إلى ما سبق، يُهيئ كلًا من i وtotal إلى الصفر مبدئيًا مما يزيد من زمن التشغيل بمقدار ثابتٍ معينٍ وليكن c، وبالتالي، يساوي زمن تشغيل الخوارزمية للقيمة ‎(a+b)×n+c، حيث a وb وc عِبارةٌ عن ثوابتٍ تعتمد على عوامل مثل كيفية تصريف compile الشيفرة ونوع الحاسوب المستخدَم، وبالاعتماد على حقيقة أن c دائمًا ما تكون أقلّ من أو تساوي c×n لأي عددٍ صحيحٍ موجبٍ n، يمكننا إذًا أن نقول أن زمن التشغيل أقلّ من أو يساوي ‎(a+b+c)×n، أي أنه أقلّ من أو يساوي حاصل ضرْب ثابتٍ في n، أي يكون زمن تشغيل الخوارزمية هو O(n)‎. إذا استشكل عليك أن تفهم ما سبق، فإنه يعني أنه لأي قيم n كبيرة، فإن الثابت c في المعادلة ‎(a+b)×n+c غير مهمٍ إذا ووزِن مع ‎(a+b)×n، ونصيغ ذلك بأن نقول إن c تعبّر عن عنصرٍ ذات رتبةٍ أقل lower order، وعادةً ما نتجاهل تلك العناصر في سياق التحليل المُقارِب؛ ويمكن لتحليلٍ مُقارِب آخرٍ أكثر حدَّة أن يستنتج ما يلي: "يَستغرق كلّ تكرار iteration ضِمن حلقة for مقدارًا ثابتًا من الوقت، ولأن الخوارزمية تتضمّن عدد n من التكرارات، فإن زمن التشغيل الكلي هو حاصل ضرْب ثابتٍ في n مضافًا إليه عناصر ذات رتبة أقلّ للتهيئة المبدئية، وإذا تجاهلنا تلك العناصر، سنجد أن زمن التشغيل يساوي O(n)‎. يفيد أحيانًا أن نخصِّص حدًا أدنىً lower limit لزمن التشغيل، حيث سيمكّننا ذلك من أن نقول أن زمن تشغيل خوارزميةٍ معينةٍ أكبر من أو يساوي حاصل ضرْب قيمةٍ ثابتةٍ في f(n)‎، وهو ما يعرّفه ترميزٌ آخرٌ هو Ω(f(n))‎، ويُقرأ أوميجا لدالة f أو ترميز أوميجا الكبير لدالة f (أوميجا Omega هو حرفٌ أبجديٌ يونانيٌ ويمثِّل الترميز Ω حالته الكبيرة)؛ وإذا شئنا الدقة، عندما نقول أن زمن تشغيل خوارزمية هو Ω(f(n))‎، فإن المقصود هو وجود عددٍ موجبٍ C وعددٍ صحيحٍ آخرٍ موجبٍ M، وعندما تكون قيمة n أكبر من M، فإن زمن تشغيل الخوارزمية يكون أكبر من أو يساوي حاصل ضرْب C في f(n)‎، ونستخلّص مما سبق أن O(f(n))‎ يوفّر معلومةً عن الحد الأقصى للزمن الذي قد تنتظره حتى تنتهي الخوارزمية من العمل، بينما يوفّر Ω(f(n))‎ معلومةً عن الحد الأدنى للزمن. سنفْحص الآن خوارزميةً أخرى: public static void simpleBubbleSort( int[] A, int n ) { for (int i = 0; i < n; i++) { // Do n passes through the array... for (int j = 0; j < n-1; j++) { if ( A[j] > A[j+1] ) { // A[j] و A[j+1] رتِّب int temp = A[j]; A[j] = A[j+1]; A[j+1] = temp; } } } } يمثِّل المعامِل n في المثال السابق حجم المشكلة، حيث ينفِّذ الحاسوب حلقة for الخارجية عدد n من المرات، وفي كلّ مرة ينفِّذ فيها تلك الحلقة، فإنه ينفِّذ أيضًا حلقة for داخليةً عدد n-1 من المرات، إذًا سينفّذ الحاسوب تعليمة if عدد n×(n-1)‎ من المرات، أي يساوي n2-n، ولأن العناصر ذات الرتبة الأقل غير مهمّةٍ في التحليل المُقارِب، سنكتفي بأن نقول أن تعليمة if تنفَّذ عدد n2 مرةٍ؛ وعلى وجهٍ أكثر تحديدًا، ينفِّذ الحاسوب الاختبار A[j] > A[j+1] عدد n2 من المرات، ويكون زمن تشغيل الخوارزمية هو Ω( n2)‎، أي يساوي حاصل ضرْب قيمةٍ ثابتةٍ في n2 على الأقل، وإذا فحصنا العمليات الأخرى (تعليمات الإسناد assignment وزيادة i وj بمقدار الواحد..إلخ)، فإننا لن نجد أي عمليةً منها تنفَّذ أكثر من عدد n2 مرة؛ ونستنتج من ذلك أن زمن التشغيل هو O(n2)‎ أيضًا، أي أنه لن يتجاوز حاصل ضرْب قيمةٍ ثابتةٍ في n2، ونظرًا لأن زمن التشغيل يساوي كلًا من Ω( n2)‎ و O( n2)‎، فإنه أيضًا يساوي Θ( n2)‎. لقد أوضحنا حتى الآن أن زمن التشغيل يعتمد على حجم المشكلة، ولكننا تجاهلنا تفصيلةٍ مهمةٍ أخرى، وهي أنه لا يعتمد فقط على حجم المشكلة، وإنما كثيرًا ما يعتمد على نوعية البيانات المطلوب معالجتها، فمثلًا قد يعتمد زمن تشغيل خوارزمية ترتيب على الترتيب الأولي للعناصر المطلوب ترتيبها، وليس فقط على عددها. لكي نأخذ تلك الاعتمادية في الحسبان، سنُجري تحليلًا لزمن التشغيل على كلًا من الحالة الأسوأ the worst case analysis والحالة الوسطى average case analysis، بالنسبة لتحليل زمن تشغيل الحالة الأسوأ، سنفحص جميع المشاكل المحتملة لحجم يساوي n وسنحدّد أطول زمن تشغيل من بينها جميعًا، بالمِثل بالنسبة للحالة الوسطى، سنفحص جميع المشاكل المحتملة لحجم يساوي n ونحسب قيمة متوسط زمن تشغيلها جميعًا، وعمومًا سيَفترض تحليل زمن التشغيل للحالة الوسطى أن جميع المشاكل بحجم n لها نفس احتمالية الحدوث على الرغم من عدم واقعية ذلك في بعض الأحيان، أو حتى إمكانية حدوثه وذلك في حالة وجود عددٍ لا نهائيٍ من المشاكل المختلفة لحجمٍ معينٍ. عادةً ما يتساوى زمن تشغيل الحالة الأسوأ والوسطى ضِمن مُضاعفٍ ثابتٍ، وذلك يعني أنهما متساويان بقدر اهتمام التحليل المُقارِب، أي أن زمن تشغيل الحالة الوسطى والحالة الأسوأ هو O(f(n))‎ أو Θ(f(n))‎، إلا أن هنالك بعض الحالات القليلة التي يختلف فيها التحليل المُقارِب للحالة الأسوأ عن الحالة الوسطى كما سنرى لاحقًا. بالإضافة إلى ما سبق، يمكن مناقشة تحليل زمن تشغيل الحالة المُثلى best case، والذي سيفحص أقصر زمن تشغيلٍ ممكنٍ لجميع المُدخَلات من حجمٍ معينٍ، وعمومًا يُعَد أقلهم فائدةً. حسنًا، ما الذي ينبغي أن تعرفه حقًا عن تحليل الخوارزميات لتستكمل قراءة ما هو متبقّيٍ من هذه السلسلة؟ في الواقع، لن نقدِم على أي تحليلٍ رياضيٍ حقيقيٍ، ولكن ينبغي أن تفهَم بعض المناقشات العامة عن حالاتٍ بسيطةٍ مثل الأمثلة التي رأيناها في هذا المقال، والأهم من ذلك هو أن تفهم ما يعنيه بالضبط قوْل أن وقت تشغيل خوارزميةٍ معينةٍ هو O(f(n))‎ أو Θ(f(n))‎ لبعض الدوال الشائعة f(n)‎، كما أن النقطة المُهمّة هي أن تلك الترميزات لا تُخبرك أي شيءٍ عن القيمة العددية الفعلية لزمن تشغيل الخوارزمية لأي حالةٍ معينةٍ كما أنها لا تخبرك بأي شيءٍ عن زمن تشغيل الخوارزمية للقيم الصغيرة من n، وإنما ستخبرك بمعدل زيادة زمن التشغيل بزيادة حجم المشكلة. سنفترض أننا نوازن بين خوارزميتين لحل نفس المشكلة، وزمن تشغيل إحداها هو Θ( n2)‎ بينما زمن تشغيل الأخرى هو Θ(n3)‎، فما الذي يعنيه ذلك؟ إذا كنت تريد معرفة أي خوارزميةٍ منهما هي الأسرع لمشكلةٍ حجمها 100 مثلًا، فالأمر غير مؤكدٍ، فوِفقًا للتحليل المُقارِب يمكن أن تكون أي خوارزميةٍ منهما هي الأسرع في هذه الحالة؛ أما بالنسبة للمشاكل الأكبر حجمًا، سيصل حجم المشكلة n إلى نقطةٍ تكون معها خوارزمية Θ( n2)‎ أسرع بكثيرٍ من خوارزمية Θ(n3)‎؛ وعلاوةً على ذلك، يزداد تميز خوارزمية Θ( n2)‎ عن خوارزمية Θ(n3)‎ بزيادة حجم المشكلة أكثر فأكثر، وعمومًا ستكون هناك قيمٌ لحجم المشكلة n تكون معها خوارزمية Θ( n2)‎ أسرع ألف مرةٍ أو مليون مرةٍ أو حتى بليون مرةٍ وهكذا؛ وذلك لأن دالة a×n3 تنمو أسرع بكثيرٍ من دالة b×n2 لأي ثابتين موجبين a وb، وذلك يعني أنه بالنسبة للمشاكل الكبيرة، ستكون خوارزمية Θ( n2)‎ أسرع بكثيرٍ من خوارزمية Θ(n3)‎، ونحن لا نعرف بالضبط إلى أي درجةٍ بالضبط ينبغي أن تكون المشكلة كبيرةٌ، وعمليًا، يُحتمل لخوارزمية Θ( n2)‎ أن تكون أسرع حتى للقيم الصغيرة من حجم المشكلة n؛ وعمومًا تُفضّل خوارزمية Θ( n2)‎ عن خوارزمية Θ(n3)‎. لكي تفهم وتطبّق التحليل المُقارِب، لابدّ أن يكون لديك فكرةً عن معدّل نمو بعض الدوال الشائعة، وبالنسبة للدوال الأسية ‎(power)‎ n, n2, n3, n4, …,، كلما كان الأس أكبر، ازداد معدّل نمو الدالة؛ أما بالنسبة للدوال الأسية exponential من النوع 2n و 10n حيث n هو الأس، يكون معدّل نموها أسرع بكثيرٍ من أي دالةٍ أسيةٍ عاديةٍ power، وعمومًا تنمو الدوال الأسية بسرعةٍ جدًا لدرجة تجعل الخوارزميات التي ينمو زمن تشغيلها بذلك المعدّل غير عمليةٍ عمومًا حتى للقيم الصغيرة من n لأن زمن التشغيل طويلٌ جدًا، وإلى جانب ذلك، تُستخدم دالة اللوغاريتم log(n)‎ بكثرةٍ في التحليل المُقارِب، فإذا كان هناك عددٌ كبيرٌ من دوال اللوغاريتم، ولكن تلك التي أساسها يساوي 2 هي الأكثر استخدامًا في علوم الحاسوب، وتُكتب عادة كالتالي log2(n)‎، حيث تنمو دالة اللوغاريتم ببطئٍ إلى درجةٍ أبطأ من معدّل نمو n، وينبغي أن يساعدك الجدول التالي على فهم الفروق بين معدّلات النمو للدوال المختلفة: يرجع السبب وراء استخدام log(n) بكثرةٍ إلى ارتباطها بالضرب والقسمة على 2، سنفْترض أنك بدأت بعدد n ثم قسَمته على 2 ثم قسَمته على 2 مرةٍ أخرى، وهكذا إلى أن وصَلت إلى عددٍ أقل من أو يساوي 1، سيساوي عدد مرات القسمة (مقربًا لأقرب عدد صحيح) لقيمة log(n)‎. فمثلًا انظر إلى خوارزمية البحث الثنائي binary search في مقال البحث والترتيب في المصفوفات Array في جافا، حيث تبحث تلك الخوارزمية عن عنصرٍ ضِمن مصفوفةٍ مرتّبةٍ، وسنستخدم طول المصفوفة مثل حجمٍ للمشكلة n، إذ يُقسَم عدد عناصر المصفوفة على 2 في كلّ خطوةٍ ضِمن خوارزمية البحث الثنائي، بحيث تتوقّف عندما يصبح عدد عناصرها أقلّ من أو يساوي 1، وذلك يعني أن عدد خطوات الخوارزمية لمصفوفة طولها n يساوي log(n)‎ للحدّ الأقصى؛ ونستنتج من ذلك أن زمن تشغيل الحالة الأسوأ لخوارزمية البحث الثنائي هو Θ(log(n))‎ كما أنه هو نفسه زمن تشغيل الحالة الوسطى، في المقابل، يساوي زمن تشغيل خوارزمية البحث الخطي الذي تعرّضنا له في مقال السابق ذكره أعلاه حول البحث والترتيب في المصفوفات Array في جافا لقيمة Θ(n)‎، حيث يمنحك ترميز Θ طريقةً كميّةً quantitative لتعبّر عن حقيقة كون البحث الثنائي أسرع بكثيرٍ من البحث الخطي linear search. تَقسِم كلّ خطوةٍ في خوارزمية البحث الثنائي حجم المسألة على 2، وعمومًا يحدُث كثيرًا أن تُقسَم عمليةٌ معينةٌ ضِمن خوارزمية حجم المشكلة n على 2، وعندما يحدث ذلك ستظهر دالة اللوغاريتم في التحليل المُقارِب لزمن تشغيل الخوارزمية. يُعَد موضوع تحليل الخوارزميات algorithms analysis حقلًا ضخمًا ورائعًا، ناقشنا هنا جزءًا صغيرًا فقط من مفاهيمه الأساسية التي تفيد لفهم واستيعاب الاختلافات بين الخوارزميات المختلفة. ترجمة -بتصرّف- للقسم Section 5: Analysis of Algorithms من فصل Chapter 8: Correctness, Robustness, Efficiency من كتاب Introduction to Programming Using Java. اقرأ أيضًا المقال السابق: التوكيد assertion والتوصيف annotation في لغة جافا مدخل إلى الخوارزميات ترميز Big-O في الخوارزميات دليل شامل عن تحليل تعقيد الخوارزمية تعقيد الخوارزميات Algorithms Complexity النسخة الكاملة لكتاب مدخل إلى الذكاء الاصطناعي وتعلم الآلة
    1 نقطة
  20. في لغة HTML يمكن عرض الصور من خلال العنصر img كالتالي: <img src="https://via.placeholder.com/150" /> وستكون النتيحة كالتالي بالطبع: ويمكن التحكم في طول وعرض الصورة من خلال إضافة الخاصية width أو height، على النحو التالي: <img src="https://via.placeholder.com/150" width="300" height="200" /> وستظهر الصورة بحجم مختلف عن المرة السابقة:
    1 نقطة
  21. نهدف خلال تقدمنا في المنهاج، إلى تعلم ما يكفي عن JavaScript، فهذا أمر بالغ الأهمية، إضافة إلى تطوير الويب. لقد تطورت JavaScript بسرعة خلال السنوات القليلة الماضية، لذلك سنعتمد هنا الميزات التي توفرها النسخ الجديدة. يطلق على معيار JavaScript رسميًا اسم ECMAScript. وتعتبر النسخة ECMAScript® 2020 هي الأحدث بعد إصدارها في يونيو (حزيران) لعام 2020، كما تُعرف بالرمز ES11. لا تدعم المتصفحات الميزات الأحدث للغة JavaScript حتى الآن. لذلك نقلت الشيفرة (transpiled) التي تنفذها المتصفحات من النسخ الأحدث إلى النسخ الأقدم لتكون أكثر توافقًا. يعتبر استخدام Babel حاليًا أكثر الطرق شعبية لإجراء عملية النقل. وتُهيّأ عملية النقل آليًا في تطبيقات React التي تبنى باستخدام create-react-app. سنلقي نظرة أقرب إلى تهيئة عمليات النقل في القسم 7 لاحقًا. تُعرَّف Node.js بأنها بيئة لتنفيذ شيفرة JavaScript مبنية على محرك JavaScript الذي طورته Google والمعروف باسم chrome V8، وتعمل هذه البيئة في أي مكان من الخوادم إلى الهواتف النقّالة. سنتدرب على كتابة شيفرة JavaScript باستخدام Node، لذلك ينبغي أن تكون نسخة Node.js المثبتة على جهازك 10.18.0 على الأقل. وطالما أنّ أحدث نسخ Node ستتوافق تلقائيًا مع أحدث نسخ JavaScript، لذلك لا حاجة لنقل الشيفرة. تُكتب الشيفرة في ملفات تحمل اللاحقة js. وتنفذ من خلال الأمر node name_of_file.js، كما يمكن أن نكتبها ضمن طرفية Node.js التي نفتحها بكتابة الأمر node في سطر الأوامر، وأيضًا بكتابة الأمر نفسه في طرفية التطوير الخاصة بالمتصفح. تتماشى التنقيحات الأخيرة للمتصفح Chrome جيدًا مع ميزات JavaScript الأحدث دون الحاجة إلى نقل الشيفرة. كما يمكن استخدام أدوات بديلة مثل JS Bin. تُذكِّرنا JavaScript من حيث الاسم والتعابير بلغة Java. لكن من ناحية آلية العمل فهي أبعد ماتكون عنها. فإذا نظرت إليها من منظور مبرمجي Java، سيبدو سلوكها غريبًا قليلًا وخاصة إن لم تكلّف نفسك عناء البحث في ميزاتها. لقد شاع فيما مضى محاكاة ميزات Java في تصميم نماذج JavaScript، لكننا لا نحبّذ هذا الأمر لأن اللغتين والبيئتين اللتين تعملان ضمنهما تختلفان كليًا. المتغيرات Variables تُعرَّف المتغيرات في JavaScript بطرقٍ عدة: const x = 1 let y = 5 console.log(x, y) // 1, 5 y += 10 console.log(x, y) // 1, 15 y = 'sometext' console.log(x, y) // 1, sometext x = 4 // خطا لا تُعرّف الكلمة const متغيرًا، بل تدل على قيمة ثابتة (constant) لايمكن بعد ذلك تغييرها. بالمقابل تُعرّف الكلمة let متغيّرًا عاديًا. يمكن أن نرى بوضوح من خلال المثال السابق أن المتغيّرات قد تأخذ بيانات من أنواع مختلفة أثناء التنفيذ، ففي البداية يخزن المتغير y قيمة صحيحة، ثم سلسة نصية في النهاية. كما تُستخدم الكلمة var في تعريف المتغيّرات، فهذه الكلمة ولفترة طويلة كانت الطريقة الوحيدة لتعريف المتغيّر، ثم أضيفت const وlet مؤخرًا في النسخة ES6. وقد تعمل الكلمة var بطريقة مختلفة لتعريف المتغيرات مقارنة بالطريقة التي تُعرّف بها في معظم اللغات. سنلتزم في منهاجنا باستخدام const وlet عند تعريف المتغيرات. يمكنك الاطلاع على المزيد حول هذا الموضوع عبر YouTube كالفيديو التالي: var, let and const - ES6 JavaScript Features، أومن خلال المقاليين التاليين في أكاديمية حسوب: المتغيرات في JavaScript إفادة var القديمة في JavaScript المصفوفات Arrays تُعرِّف الشيفرة التالية مصفوفة وتقدم مثالين عن كيفية استخدامها: const t = [1, -1, 3] t.push(5) console.log(t.length) // 4 console.log(t[1]) // -1 t.forEach(value => { console.log(value) // 1, -1, 3, 5 }) الأمر الملفت في هذه الشيفرة، هو إمكانية تعديل محتوى المصفوفة بالرغم من كونها ثابتة (const). ذلك أن المصفوفة عبارة عن كائن والمتغيّر سيشير إلى نفس الكائن دومًا حتى لو تغيّر محتواه بإضافة العناصر الجديدة. تستخدم تعليمة forEach كطريقة لتعداد عناصر المصفوفة في الشيفرة السابقة. حيث تمتلك forEach مُعاملًا على شكل دالة سهمية لها الصيغة التالية: value => { console.log(value) } تستدعي forEach الدالة من أجل كل عنصر من عناصر المصفوفة، وتمرر كل عنصر كمُعامل. يمكن للدالة التي تمثل مُعاملًا لتعليمة forEach أن تمتلك مُعاملات أخرى خاصة بها. كما استخدم التابع push لإضافة عنصر جديد إلى المصفوفة في المثال السابق. تستخدم عادة البرمجة بأسلوب الدوال (functional programming) مع React. ولهذا الأسلوب ميزة استخدام بنى المعطيات الصامدة (Immutable). ويفضل عند كتابة شيفرة React استخدام التابع concat، فلا يضيف هذا التابع عنصرًا جديدًا إلى المصفوفة، بل ينشئ مصفوفة جديدة تضم محتويات القديمة بالإضافة إلى العنصر الجديد. const t = [1, -1, 3] const t2 = t.concat(5) console.log(t) // [1, -1, 3] console.log(t2) // [1, -1, 3, 5] لن يضيف الأمر (t.concat(5 عنصرًا جديدًا إلى المصفوفة القديمة، بل سيعيد مصفوفة جديدة تضم العنصر الجديد (5) بالإضافة إلى عناصر المصفوفة القديمة. لقد عُرّفت الكثير من التوابع المفيدة للتعامل مع المصفوفات، فلنلقي نظرة على استخدام التابع map على سبيل المثال: const t = [1, 2, 3] const m1 = t.map(value => value * 2) console.log(m1) // [2, 4, 6] يُنشئ التابع map مصفوفةً جديدةً مبنيةً على أساس القديمة، وتستخدم الدالة التي مُررت كوسيط لإنشاء عناصر المصفوفة الجديدة. لاحظ في مثالنا السابق كيف نتجت العناصر الجديدة عن القديمة بضربها بالعدد 2. ويمكن للتابع map تحويل المصفوفة إلى شيءٍ مختلفٍ تمامًا: const m2 = t.map(value => '<li>' + value + '</li>') console.log(m2) // [ '<li>1</li>', '<li>2</li>', '<li>3</li>' ] لقد تحولّت المصفوفة المكوَّنة من أعداد صحيحة إلى مصفوفة تحوي عبارات HTML باستخدام map. سنستخدم هذا التابع بكثرة في React كما سنرى في القسم 2 لاحقًا. يمكن إسناد قيمة كل عنصر من المصفوفة إلى المتغيّرات بطريقة الإسناد بالتفكيك (destructuring assignment). const t = [1, 2, 3, 4, 5] const [first, second, ...rest] = t console.log(first, second) // 1, 2 console.log(rest) // [3, 4 ,5] "فُكّكت" عناصر المصفوفة t باستخدام الإسناد بالتفكيك، وأسند أول عنصرين منها إلى المتغيّرين first وsecond، ثم جُمّعت بقية عناصر المصفوفة في مصفوفة جديدة أسندت بدورها إلى المتغيّر rest. الكائنات Objects توجد طرق عدة لتعريف الكائنات في JavaScript، وأكثر هذه الطرق شيوعًا هي استخدام الشكل المختصر لتعريف الكائنات (object literals) والتي تُنشأ بوضع خصائصها على شكل قائمة عناصر تفصل بينها الفواصل بين قوسين معقوصين. const object1 = { name: 'Arto Hellas', age: 35, education: 'PhD', } const object2 = { name: 'Full Stack web application development', level: 'intermediate studies', size: 5, } const object3 = { name: { first: 'Dan', last: 'Abramov', }, grades: [2, 3, 5, 3], department: 'Stanford University', } تأخذ الخصائص قيمًا من أي نوع مثل الصحيحة والنصية والمصفوفات والكائنات، وتعرّف خصائص كائن ما باستخدام النقطة "." أو باستخدام الأقواس المعقوفة: console.log(object1.name) // Arto Hellas const fieldName = 'age' console.log(object1[fieldName]) // 35 كما تُضاف الخصائص إلى الكائن مباشرة باستخدام النقطة "." أو الأقواس المعقوفة: object1.address = 'Helsinki' object1['secret number'] = 12341 لقد توجب علينا استخدام الأقواس المعقوفة عند إضافة الخاصية الأخيرة، ذلك أن الاسم (secret number) لا يصلح اسمًا لخاصية نظرًا لوجود فراغ بين الكلمتين. تمتلك الكائنات في JavaScript توابعًا أيضًا، لكننا لن نستخدم في المنهاج كائنات لها توابعها الخاصة، وسنكتفي بمناقشتها بإيجاز خلال تقدمنا. تُعرّف الكائنات أيضًا باستخدام الدوال البانية (constructor functions) التي تشابه في آلية عملها مثيلاتها في لغات برمجة أخرى، كدوال البناء في صفوف Java. مع ذلك، لا تمتلك JavaScript صفوفًا بالمفهوم الذي نراه في البرمجة كائنية التوجه (OOP). أضيفت الصيغة class للغة JavaScript ابتداء من النسخة ES6، والتي تساعد في بعض الحالات على بناء صفوف كائنية التوجه. الدوال Functions تعرّفنا سابقًا على استخدام التوابع السهمية والتي تُكتب بصيغتها الكاملة على النحو: const sum = (p1, p2) => { console.log(p1) console.log(p2) return p1 + p2 } وتُستدعى كما هو متوقع على النحو: const result = sum(1, 5) console.log(result) يمكن تجاهل كتابة الأقواس المحيطة بالمُعاملات إن كان للدالة مُعامل واحد: const square = p => { console.log(p) return p * p } وإن احتوت الدالة على عبارة برمجية واحدة، لا حاجة عندها لاستخدام الأقواس المعقوصة، وستعيد الدالة القيمة الناتجة عن تنفيذ هذه العبارة. لاحظ كيف سيُختصر تعريف الدالة لو أزلنا العبارة التي تطبع النتيجة على الطرفية: const square = p => p * p ولهذا الشكل أهميته التطبيقية عندما نتعامل مع المصفوفات وخاصة باستعمال التابع map: const t = [1, 2, 3] const tSquared = t.map(p => p * p) // tSquared is now [1, 4, 9] أضيفت الدوال السهمية إلى JavaScript مع النسخة ES6 منذ سنتين تقريبًا. وقد عُرّفت الدوال قبل ذلك بالكلمة function فقط. وعمومًا هناك طريقتان لتعريف الدوال، الأولى تسمية الدالة بالتصريح عنها (function declaration): function product(a, b) { return a * b } const result = product(2, 6) // result is now 12 أما الطريقة الثانية فهي استخدام تعبير تعريف دالة (function expression)، وفي هذه الحالة لا داع لإعطاء الدالة اسمًا: const average = function(a, b) { return (a + b) / 2 } const result = average(2, 5) // result is now 3.5 سنستخدم في هذا المنهاج الدوال السهمية دائمًا. التمارين 1.3- 1.5 سنكمل بناء التطبيق الذي بدأناه سابقًا، لهذا يمكنك متابعة كتابة الشيفرة في نفس المشروع طالما أن المهم هو الحالة النهائية للتطبيق الذي ستسلّمه. نصيحة للاحتراف: قد تواجهك عقبات تتعلق ببنية الخصائص التي تمتلكها المكوِّنات. اجعل الأمر أكثر وضوحًا بطباعة الخصائص على الطرفية كالتالي: const Header = (props) => { console.log(props) return <h1>{props.course}</h1> } 1.3 معلومات عن المنهاج: الخطوة 3 سنمضي قدمًا نحو الأمام مستخدمين الكائنات في تطبيقنا. عدّل تعريف المتغيّر في المكوّن App بالشكل التالي وأعد صياغة التطبيق حتى يبقى قابلًا للتنفيذ: const App = () => { const course = 'Half Stack application development' const part1 = { name: 'Fundamentals of React', exercises: 10 } const part2 = { name: 'Using props to pass data', exercises: 7 } const part3 = { name: 'State of a component', exercises: 14 } return ( <div> ... </div> ) } 1.4 معلومات عن المنهاج: الخطوة 4 ضع بعد ذلك الكائنات في مصفوفة. عدّل تعريف المتغيّر في المكوِّن App إلى الشكل التالي ولا تنس تعديل بقية الأجزاء في المكوِّن بما يتوافق معه: const App = () => { const course = 'Half Stack application development' const parts = [ { name: 'Fundamentals of React', exercises: 10 }, { name: 'Using props to pass data', exercises: 7 }, { name: 'State of a component', exercises: 14 } ] return ( <div> ... </div> ) } ملاحظة: اعتبر حتى هذه اللحظة أن هناك ثلاثة عناصر فقط ضمن المصفوفة لذلك لا ضرورة لاستخدام الحلقات عند التنقل بين العناصر. سنعود لاحقًا بتفصيل أعمق إلى موضوع تصيير الكائنات المبنية من عناصر مصفوفة في القسم التالي. لاتُمرِّر كائنات مختلفة على شكل خصائص من المكوِّن App إلى المكوِّنين Content و Total، بل مررها مباشرة كمصفوفة: const App = () => { // const definitions return ( <div> <Header course={course} /> <Content parts={parts} /> <Total parts={parts} /> </div> ) } 1.5 معلومات عن المنهاج: الخطوة 5 لنتقدم خطوة أخرى إلى الأمام. حوّل المنهاج وأقسامه إلى كائن JavaScript واحد، وأصلح كل ما يتضرر: const App = () => { const course = { name: 'Half Stack application development', parts: [ { name: 'Fundamentals of React', exercises: 10 }, { name: 'Using props to pass data', exercises: 7 }, { name: 'State of a component', exercises: 14 } ] } return ( <div> ... </div> ) } توابع الكائنات والمؤشر "this" لن نستخدم كما ذكرنا سابقًا كائنات لها توابعها الخاصة، ذلك أننا سنعتمد على نسخة من React تدعم الخطافات. لن تجد فيما سيأتي لاحقًا في هذا الفصل ما يتعلق بالمنهاج، لكن من الجيد الاطلاع على المعلومات الواردة وفهمها وخاصة عندما تستخدم نسخًا أقدم من React. يختلف سلوك الدوال السهمية عن تلك المعرّفة باستخدام التصريح function فيما يتعلق باستخدام الكلمة this التي تشير إلى الكائن نفسه. من الممكن إسناد التوابع إلى كائن بتعريف بعض خصائصه على شكل دوال: const arto = { name: 'Arto Hellas', age: 35, education: 'PhD', greet: function() { console.log('hello, my name is ' + this.name) },} arto.greet() // "hello, my name is Arto Hellas" وقد تسند التوابع إلى الكائنات حتى بعد إنشاء هذه الكائنات: const arto = { name: 'Arto Hellas', age: 35, education: 'PhD', greet: function() { console.log('hello, my name is ' + this.name) }, } arto.growOlder = function() { this.age += 1} console.log(arto.age) // 35 is printed arto.growOlder() console.log(arto.age) // 36 is printed لنعدّل الكائن كما يلي: const arto = { name: 'Arto Hellas', age: 35, education: 'PhD', greet: function() { console.log('hello, my name is ' + this.name) }, doAddition: function(a, b) { console.log(a + b) },} arto.doAddition(1, 4) // 5 is printed const referenceToAddition = arto.doAddition referenceToAddition(10, 15) // 25 is printed يمتلك الكائن الآن التابع doAddition الذي يجمع الأعداد التي تمرر إليه كوسطاء. يستدعى هذا التابع بالطريقة التقليدية (arto.doAddition(1, 4، أو بإسناد مرجعٍ للتابع إلى متغّير ومن ثم استدعاءه من خلال المتغيّر كما هو الحال في السطرين الأخيرين من الشيفرة السابقة. لكن لو حاولنا استخدام الطريقة ذاتها مع التابع greet سنواجه مشكلة: arto.greet() // "hello, my name is Arto Hellas" const referenceToGreet = arto.greet referenceToGreet() // "hello, my name is undefined" فعندما نستدعي التابع باستخدام مرجع لكائن، سيفقد التابع القدرة على تحديد الكائن الذي تشير إليه الكلمة this. وعلى نقيض بقية اللغات، تُحدّد قيمة this في JavaScript وفقًا للطريقة التي يُستدعى بها التابع. فعندما تُستدعى من خلال مرجع تتحول this إلى كائن عام (global object) ولن تكون النتيجة ما يريده المطوّر في أغلب الأوقات. تقودنا الحالة السابقة لمواجهة العديد من المشاكل وخاصة عندما تحاول React أو Node استدعاء توابع عرفها المطوّر للكائن. لذلك سنتغلب على هذه العقبة بكتابة شيفرة JavaScript لا تستعمل this. لكن هناك حالة خاصة تظهر عندما نحاول ضبط قيمة انتهاء الوقت (timeout) للدالة greet التي تمثل جزءًا من اللكائن arto باستخدام setTimeout. const arto = { name: 'Arto Hellas', greet: function() { console.log('hello, my name is ' + this.name) }, } setTimeout(arto.greet, 1000) تُحدّد قيمة this في JavaScript وفقًا للطريقة التي يُستدعى بها التابع كما أشرنا سابقًا. فعندما نستخدم الدالة setTimeout لاستدعاء تابع، فإن محرك JavaScript هو من يستدعى التابع حقيقةً، وستشير this عندها إلى كائن عام. هناك آليات عديدة للاحتفاظ بمرجع this الأصلي، منها استدعاء الدالة bind كالتالي: setTimeout(arto.greet.bind(arto), 1000) سينشئ الاستدعاء (arto.greet.bind(arto دالة جديدة تشير فيها this إلى Arto بصرف النظر عن الطريقة التي تم بها استدعاء التابع. تُستخدم الدوال السهمية في بعض الأحيان لحل بعض مشاكل this. لكن لا ينبغي استخدام هذه الدوال كتوابع لكائنات، لأن this لن تعمل عندها أبدًا. سنعرّج لاحقًا على توضيح سلوك this مع الدوال السهمية. تمتلئ صفحات الانترنت بمعلومات عن استخدام this في JavaScript، إن أردت فهم ذلك أكثر، نوصيك بشدة متابعة سلسة Understand JavaScript's this Keyword in Depth التي أعدها egghead.io على سبيل المثال. الأصناف Classes لا تمتلك JavaScript كما أشرنا سابقًا صفوفًا تشابه تلك التي تقدمها اللغات كائنية التوجه. لكنها تمتلك ميزات تمكّننا من محاكاة تلك الأصناف. لنلق نظرة سريعة على الصيغة class التي قدمتها النسخة ES6 والتي تبسّط تعريف الأصناف (أو الأغراض الشبيه بالأصناف). سنعرّف في المثال التالي صفًا يسمى Person وكائنين Person: class Person { constructor(name, age) { this.name = name this.age = age } greet() { console.log('hello, my name is ' + this.name) } } const adam = new Person('Adam Ondra', 35) adam.greet() const janja = new Person('Janja Garnbret', 22) janja.greet() تتشابه الأصناف والكائنات الناتجة من الناحية التعبيرية (Syntax) مع تلك التي تقدمها Java، كما تتشابه من ناحية السلوك أيضًا. لكنها تبقى في الصميم كائنات ناتجة عن وراثة نماذج أولية (prototypal inheritance) تقدمها JavaScript. فنمط كلٍّ من الكائنين عمليًا هو Object، لأن JavaScript تعرّف افتراضيًا أنواع البيانات الأساسية التالية: Boolean و Null و Undefined و Number و String و Symbol و Object فقط. على أية حال كان تقديم الصيغة class محط خلاف، يمكنك القراءة أكثر عن ذلك في المقالة Not Awesome: ES6 Classes أو في المقالة ?Is “Class” In ES6 The New “Bad” Part. لقد استخدمت هذه الصيغة مرارًا في النسخ القديمة من React وكذلك في Node.js، لكننا لن نستخدمها في المنهاج كبنية أساسية، طالما أننا سنستخدم ميزة الخطافات الجديدة في React. مواد إضافية لتعلم JavaScript تقدم أكاديمية حسوب توثيقًا عربيًا شاملًا مترجمًا عن الموقع javascript.info وننصح بشدة الاطلاع على كل المقالات الموجودة. بالإضافة إلى ذلك ستجد على شبكة الانترنت الكثير من المواد المتعلقة باللغة منها الجيد ومنها السيء، فيمكنك الاطلاع على المراجع التي تقدمها شركة Mozilla والمتعلقة بميزات JavaScript من خلال الدليل Mozilla's Javascript Guide. كما نوصيك بشدة أن تتطلع مباشرة على الدورة التعليمية A re-introduction to JavaScript JS tutorial على موقع الويب لشركة Mozilla. إن أردت التعمق بهذه اللغة نوصيك بسلسلة كتب مجانية ممتازة على الإنترنت تدعى You-Dont-Know-JS. يعتبر الموقع egghead.io مرجعًا جيدًا حيث يضم شروحات قيّمة عن JavaScript وعن React وغيرها من المواضيع الهامة، لكن ليست جميع المواد الموجودة مجانية. ترجمة -وبتصرف- للفصل JavaScript من سلسلة Deep Dive Into Modern Web Development
    1 نقطة
  22. لنعد إلى العمل مع React، ولنبدأ بمثالٍ جديد: const Hello = (props) => { return ( <div> <p> Hello {props.name}, you are {props.age} years old </p> </div> ) } const App = () => { const name = 'Peter' const age = 10 return ( <div> <h1>Greetings</h1> <Hello name="Maya" age={26 + 10} /> <Hello name={name} age={age} /> </div> ) } الدوال المساعدة لمكون لنوسع المكوّن Hello بحيث يحدد العام الذي ولد فيه الشخص الذي نحيّيه: const Hello = (props) => { const bornYear = () => { const yearNow = new Date().getFullYear() return yearNow - props.age } return ( <div> <p> Hello {props.name}, you are {props.age} years old </p> <p>So you were probably born in {bornYear()}</p> </div> ) } وُضعت الشيفرة التي تحدد عام الولادة ضمن دالة منفصلة تُستدعى حين يُصيَّر المكوّن. لا داعي لتمرير عمر الشخص إلى الدالة كمُعامل لأنها قادرة على الوصول إلى الخصائص (props) مباشرة. لو تفحّصنا الشيفرة المكتوبة عن قرب، لوجدنا أن الدالة المساعدة (helper function) قد عُرّفت داخل دالة أخرى تحدد عمل المكوّن. لا يُستخدَم هذا الأسلوب في Java كونه أمرًا معقدًا ومزعجًا، لكنه مستخدم جدًا في JavaScript. التفكيك Destructuring قبل أن نمضي قدمًا، سنلقي نظرة على ميزة بسيطة لكنها مفيدة أضيفت في النسخة ES6. تسمح هذه الميزة بتفكيك القيم عن الكائنات والمصفوفات عند إسنادها. لقد ربطنا البيانات التي مررناها إلى المكوّن مستخدمين العبارتين props.name وprops.age. كما كررنا أيضا العبارة props.age مرتين في الشيفرة. وطالما أن props هو كائن له الشكل التالي: props = { name: 'Arto Hellas', age: 35, } سنجعل المكوّن أكثر حيوية بإسناد قيمتي خاصيّتيه مباشرة إلى المتغّيرين name وage، ومن ثم نستخدمهما في الشيفرة: const Hello = (props) => { const name = props.name const age = props.age const bornYear = () => new Date().getFullYear() - age return ( <div> <p>Hello {name}, you are {age} years old</p> <p>So you were probably born in {bornYear()}</p> </div> ) } لاحظ كيف استخدمنا العبارة المختصرة للدوال السهمية عندما عرفنا الدالة bornYear. لقد أشرنا إلى هذه النقطة سابقًا، فلا حاجة لوضع جسم الدالة السهمية بين قوسين معقوصين إذا احتوت على عبارة واحدة فقط، حيث تعيد الدالة نتيجة تنفيذ تلك العبارة. وبالتالي فالطريقتان المستخدمتان في تعريف الدالتين التاليتين متطابقتان: const bornYear = () => new Date().getFullYear() - age const bornYear = () => { return new Date().getFullYear() - age } إذًا تُسهّل عملية التفكيك إسناد القيم للمتغيّرات، حيث نستخلص قيم خصائص الكائنات ونضعها في متغيّرات منفصلة: const Hello = (props) => { const { name, age } = props const bornYear = () => new Date().getFullYear() - age return ( <div> <p>Hello {name}, you are {age} years old</p> <p>So you were probably born in {bornYear()}</p> </div> ) } فلو افترضنا أنّ الكائن الذي نفككه يمتلك القيم التالية: props = { name: 'Arto Hellas', age: 35, } ستُسنِد العبارة const { name, age } = props القيمة "Arto Hellas" إلى المتغيّر name والقيمة "35" إلى المتغيّر age. سنتعمق قليلًا في فكرة التفكيك: const Hello = ({ name, age }) => { const bornYear = () => new Date().getFullYear() - age return ( <div> <p> Hello {name}, you are {age} years old </p> <p>So you were probably born in {bornYear()}</p> </div> ) } لقد فككت الشيفرة السابقة الخاصيتين اللتين يمتلكهما المكوّن وأسندتهما مباشرة إلى المتغيّرين name وage. أي باختصار لم نسند الكائن الذي يدعى props إلى متغيّر يدعى props ثم أسندت خاصيتيه إلى المتغيّرين name وage كما يحدث في الشيفرة التالية: const Hello = (props) => { const { name, age } = props بل أسندت قيمتي الخاصيتين مباشرة إلى المتغيّرات بتفكيك الكائن props أثناء تمريره كمُعامل لدالة المكوًن: const Hello = ({ name, age }) => { إعادة تصيير الصفحة Page re-rendering لم تطرأ أية تغييرات على مظهر التطبيقات التي كتبناها حتى الآن. ماذا لو أردنا أن ننشئ عدادًا تزداد قيمته مع الوقت أو عند النقر على زر؟ لنبدأ بالشيفرة التالية: const App = (props) => { const {counter} = props return ( <div>{counter}</div> ) } let counter = 1 ReactDOM.render( <App counter={counter} />, document.getElementById('root') ) تُمرَّر قيمة العدّاد إلى المكوّن App عبر الخاصيّة counter، ثم سيعمل على تصييرها على الشاشة. لكن ما الذي سيحدث لو تغيرت قيمة counter؟ حتى لو زدنا قيمتها تدريجيًا كالتالي: counter += 1 فلن يصيّر المكوّن القيمة الجديدة على الشاشة. لحل المشكلة، علينا استدعاء التابع ReactDOM.render حتى يُعاد تصيير الصفحة مجددًا بالطريقة التالية: const App = (props) => { const { counter } = props return ( <div>{counter}</div> ) } let counter = 1 const refresh = () => { ReactDOM.render(<App counter={counter} />, document.getElementById('root')) } refresh() counter += 1 refresh() counter += 1 refresh() غُلّف أمر إعادة تصيير الصفحة داخل الدالة refresh لتَسهُل كتابة الشيفرة. لاحظ كيف أعيد تصيير المركب ثلاث مرات، الأولى عندما كانت قيمة العدّاد 1 ثم 2 وأخيرًا 3. وطبعًا لن تلاحظ ظهور القيمتين 1 و2 نظرًا لفترة ظهورهما القصيرة جدًا. يمكننا أن نجعل الأداء أفضل قليلًا بتصيير الصفحة وزيادة العدّاد كل ثانية مستخدمين الدالة setInterval التي تنفذ ما بداخلها خلال فترة زمنية محددة بالميلي ثانية: setInterval(() => { refresh() counter += 1 }, 1000) وأخيرًا لا تكرر استدعاء التابع ReactDOM.render، ولا ننصحك بذلك، لأنك ستتعلم لاحقًا طريقةً أفضل. مكوّن متغير الحالات Stateful component تبدو جميع المكوّنات التي تعاملنا معها حتى الآن بسيطة من مبدأ أنها لم تحتوي على حالات قد تتغير خلال فترة عمل المكوّن، لذلك سنضيف الآن حالة إلى المكوّن App بمساعدة أحد خطافات React وهو state hook. فلنقم إذًا بتعديل التطبيق على النحو: import React, { useState } from 'react'import ReactDOM from 'react-dom' const App = () => { const [ counter, setCounter ] = useState(0) setTimeout( () => setCounter(counter + 1), 1000 ) return ( <div>{counter}</div> ) } ReactDOM.render( <App />, document.getElementById('root') ) يُدرِج التطبيق في السطر الأول الدالة useState باستخدام الكلمة import: import React, { useState } from 'react' تبدأ التعليمات داخل دالة المكوّن باستدعاء دالة أخرى كما يلي: const [ counter, setCounter ] = useState(0) تضيف الدالة المستدعاة useState "حالة" وتصيّرها بعد إعطائها قيمة ابتدائية تساوي 0. بعدها تعيد الدالة مصفوفة تضم عنصرين تسندهما إلى المتغيّرين counter وsetCounter مستخدمةً الإسناد بالتفكيك. سيحمل المتغيّر counter القيمة الابتدائية للحالة وهي 0، بينما سيحمل setCounter القيمة التي تعيدها الدالة المغيّرة للحالة. يستدعي التطبيق بعد ذلك الدالة setTimeout ويمرر لها مُعاملان أحدهما دالة لزيادة قيمة العدّاد، والآخر لتحديد وقت الانتهاء (timeout) بثانية واحدة: setTimeout( () => setCounter(counter + 1), 1000 ) تُستدعى الدالة setCounter التي مُررت كمُعامل للدالة setTimeout بعد ثانية من استدعاء الأخيرة. () => setCounter(counter + 1) تعيد React تصييرالمكوّن بعد استدعاء الدالة setCounter والتي تعتبر هنا الدالة المغيّرة للحالة (كونها غيرت قيمة العدّاد)، ويعني هذا إعادة تنفيذ الشيفرة في دالة المكوّن: (props) => { const [ counter, setCounter ] = useState(0) setTimeout( () => setCounter(counter + 1), 1000 ) return ( <div>{counter}</div> ) } عندما تُنفَّذ دالة المكوّن ثانيةً، سيستدعي الدالة useState ثم يعيد القيمة الجديدة للحالة:1. وكذلك الأمر سيُستدعى setTimeout مجددًا والذي سيستدعي بدوره setCounter بعد انقضاء ثانية وسيزيد العدّاد هنا بمقدار 1 ليصبح 2 بعد أن أصبحت قيمة counter تساوي 1. () => setCounter(2) خلال هذه العملية ستظهر على الشاشة القيمة الأقدم للعدّاد. وهكذا سيعاد تصيير الكائن في كل مرة تغيّر فيها الدالة setCounter حالة المكوّن، وستزداد قيمة العدّاد مع تكرار العملية طالما أن التطبيق يعمل. إن لم تحدث عملية إعادة التصيير في الوقت المحدد أو ظننت أنها تحصل في توقيت خاطئ، يمكنك تنقيح (Debug) التطبيق بإظهار قيم المتغيّرات على الطرفية بالطريقة التالية: const App = () => { const [ counter, setCounter ] = useState(0) setTimeout( () => setCounter(counter + 1), 1000 ) console.log('rendering...', counter) return ( <div>{counter}</div> ) } فمن السهل عندها تتبع ومراقبة استدعاءات دالة التصيير: معالجة الأحداث Event handling أشرنا في القسم 0 في مواضع عدة إلى معالجات الأحداث، وهي دوال تُستدعَى عندما يقع حدث ما في زمن التشغيل. فعندما يتعامل المستخدم مع العناصر المختلفة لصفحة الويب، سيحرّض ذلك مجموعة مختلفة من الأحداث. لنعدّل تطبيقنا قليلًا بحيث تزداد قيمة العدّاد عندما ينقر المستخدم على زر، سنستخدم هنا العنصر button. تدعم هذه العناصر ما يسمى بأحداث الفأرة (mouse events)، وأكثر هذه الأحداث شيوعًا هو حدث النقر على الزر(click). يُسجَّل معالج حدث النقر في React كالتالي: const App = () => { const [ counter, setCounter ] = useState(0) const handleClick = () => { console.log('clicked') } return ( <div> <div>{counter}</div> <button onClick={handleClick}> plus </button> </div> ) } نحدد من خلال الصفة onClick للعنصر button أن الدالة handleClick ستتولى معالجة حدث النقر على زر الفأرة. وهكذا ستُستدعَى الدالة handleClick في كل مرة ينقر فيها المستخدم على الزر plus، وستظهر الرسالة "clicked" على طرفية المتصفح. كما يُعرّف معالج الحدث أيضًا باسناده مباشرة إلى الصفة onClick: const App = () => { const [ counter, setCounter ] = useState(0) return ( <div> <div>{counter}</div> <button onClick={() => console.log('clicked')}> plus </button> </div> ) } وبتغيير الشيفرة التي ينفذها معالج الحدث لتصبح على النحو: <button onClick={() => setCounter(counter + 1)}> plus </button> سنحصل على السلوك المطلوب وهو زيادة العدّاد بمقدار 1 وإعادة تصييرالمكوّن. سنضيف الآن زرًا آخر لتصفير العدّاد: const App = () => { const [ counter, setCounter ] = useState(0) return ( <div> <div>{counter}</div> <button onClick={() => setCounter(counter + 1)}> plus </button> <button onClick={() => setCounter(0)}> zero </button> </div> ) } وهكذا سيكون تطبيقنا جاهزًا الآن. معالج الحدث هو دالة لقد عرفنا سابقًا معالج الحدث كقيمة للصفة onClick لعنصر الزر (button): <button onClick={() => setCounter(counter + 1)}> plus </button> لكن ماذا لو عرّفنا معالج الحدث بشكل أبسط كما يلي: <button onClick={setCounter(counter + 1)}> plus </button> سيدمر هذا العمل التطبيق: ماذا حدث؟ يفترض أن يكون معالج الحدث دالة أو مرجعًا إلى دالة، فعندما نعرّفه على النحو: <button onClick={setCounter(counter + 1)}> يمثل معالج الحدث بهذه الطريقة استدعاءً لدالة، وقد يكتب بالشكل السابق، لكن ليس في حالتنا الخاصة هذه. حيث تكون قيمة counter في البداية 0، ثم تُصيّر React التابع للمرة الأولى مستدعيةً الدالة (setCounter(0+1 التي ستُغير الحالة من 0 إلى 1، وفي نفس الوقت تعيد React تصييرالمكوّن مستدعيةً من جديدsetCounter، وستتغير الحالة مجددًا يعقبها إعادة تصيير وهكذا. لنعد الآن إلى تعريف معالج الحدث بالشكل الصحيح: <button onClick={() => setCounter(counter + 1)}> plus </button> تمتلك الآن الصفة onClick التي تحدد ما يجري عند النقر على الزر، القيمة الناتجة عن تنفيذ (setCounter(counter+1)<=() وبذلك تزداد قيمة العدّاد فقط عندما ينقر المستخدم على الزر. لا يعتبر تعريف معالجات الأحداث باستخدام قوالب JSX فكرة جيدة. لا بأس بذلك في حالتنا هذه لأن معالج الحدث بسيط جدًا. لنقم بفصل معالج الحدث إلى عدة دوال: const App = () => { const [ counter, setCounter ] = useState(0) const increaseByOne = () => setCounter(counter + 1) const setToZero = () => setCounter(0) return ( <div> <div>{counter}</div> <button onClick={increaseByOne}> plus </button> <button onClick={setToZero}> zero </button> </div> ) } لقد عُرّف معالج الحدث في الشيفرة السابقة بشكلٍ صحيح، حيث أُسند للصفة onClick متغيّر يتضمن مرجعًا لدالة: <button onClick={increaseByOne}> plus </button> تمرير الحالة إلى المكوّنات الأبناء (child components) يفضل عند كتابة مكونات React أن تكون صغيرة وقابلة للاستخدام المتكرر ضمن التطبيق وحتى عبر المشاريع المختلفة. لنُعِد صياغة تطبيقنا إذًا حتى يضم ثلاث مكونات أصغر، الأول لإظهار العدّاد والآخرين للزرين. لنبدأ بالمكون Display الذي سيكون مسؤولًا عن إظهار قيمة العدّاد على الشاشة. من الأفضل الإبقاء على حالة التطبيق ضمن مكونات المستوى الأعلى (lift the state up)، حيث ينص التوثيق على ما يلي: لذا سنضع حالة التطبيق في المكوّن App، ونمرره نزولًا إلى المكوّن Display عبر الخصائص: const Display = (props) => { return ( <div>{props.counter}</div> ) } سنستخدم المكوّن مباشرة، إذ علينا فقط أن نمرر إليه حالة المتغيّر counter: const App = () => { const [ counter, setCounter ] = useState(0) const increaseByOne = () => setCounter(counter + 1) const setToZero = () => setCounter(0) return ( <div> <Display counter={counter}/> <button onClick={increaseByOne}> plus </button> <button onClick={setToZero}> zero </button> </div> ) } سيجري كل شيء بشكل طبيعي، فحين ننقر الزر سيعاد تصيير المكوّن App وكذلك المكوّن الابنDisplay. سننشئ تاليًا المكوّن Button ليمثل أزرار التطبيق. علينا أن نمرر معالجات الأحداث وعنوان الزر عبر خصائص المكوّن: const Button = (props) => { return ( <button onClick={props.handleClick}> {props.text} </button> ) } سيبدو المكوّن App الآن بهذا الشكل: const App = () => { const [ counter, setCounter ] = useState(0) const increaseByOne = () => setCounter(counter + 1) const decreaseByOne = () => setCounter(counter - 1) const setToZero = () => setCounter(0) return ( <div> <Display counter={counter}/> <Button handleClick={increaseByOne} text='plus' /> <Button handleClick={setToZero} text='zero' /> <Button handleClick={decreaseByOne} text='minus' /> </div> ) } طالما أننا حصلنا على المكوّن Button الذي يمكن استخدامه بسهولة أينما كان، سنزيد من إمكانية التطبيق بإضافة زر جديد ليُنقص قيمة العدّاد. يمرر معالج الحدث إلى المكوّن Button عبر الخاصية handleClick. ليس مهمًا الاسم الذي اطلقناه على الخاصية، لكننا لم نختره عشوائيًا أيضًا، فقد اقترح أسلوب التسمية هذا في الدورة التعليمية الرسمية لمكتبة React. تغّير الحالة يستوجب إعادة التصيير لنستعرض المبدأ الرئيسي لعمل التطبيق مرّة أخرى. عندما يبدأ العمل ستُنفَّذ شيفرة المكوّن App. تستخدم تلك الشيفرة الخطاف useState لبناء حالة التطبيق وتحدد القيمة البدائية للمتغيّر counter. يضم هذا المكوّن مكوّنًا آخر هو Display الذي يعرض القيمة البدائية 0 للعداد، كما يضم ثلاثة مكونات Button، ولكل ٍّ منها معالج حدث يغيّر حالة العدّاد. فعندما يُنقر أي زر سيُنفَّذ معالج الحدث المقابل والذي يستخدم الدالة setCounter لتغيير حالة المكوّن App. ودائمًا استدعاء الدالة التي تغير حالة المكوّن يستوجب إعادة التصيير. فلو نقر المستخدم زر الزيادة (زر عنوانه plus)، سيغيّر معالج الحدث الخاص به قيمة العدّاد إلى 1، وسيعاد تصيير المكوّن App. سيستقبل المكوّن Display أيضًا قيمة العدّاد الجديدة كخاصية، كما سيُزوَّد المكوّن Button بمعالج حدث سيُستخدم لتغيير حالة العدّاد. إعادة تصميم المكوّنات Refactoring the components يظهر المكوِّن الذي يعرض قيمة العدّاد على الصفحة بالشكل التالي: const Display = (props) => { return ( <div>{props.counter}</div> ) } يستخدم المكوّن عمليًا الحقل counter فقط من الخصائص، لهذا يمكن تبسيطه باستخدام التفكيك كما يلي: const Display = ({ counter }) => { return ( <div>{counter}</div> ) } يضم التابع الذي يعرّف المكوّن العبارة التي تعيد نتيجة التنفيذ فقط، لذا يمكننا كتابته بالطريقة المختصرة التي تقدمها الدوال السهمية: const Display = ({ counter }) => <div>{counter}</div> لنبسط أيضًا كتابة المكوّن Button: const Button = (props) => { return ( <button onClick={props.handleClick}> {props.text} </button> ) } وهكذا نرى فائدة التفكيك في استخدام الحقل المطلوب فقط من الخصائص، وسهولة كتابة دوال بشكل مختصر بالاستفادة من الدوال السهمية: const Button = ({ handleClick, text }) => ( <button onClick={handleClick}> {text} </button> ) ترجمة -وبتصرف- للفصل JavaScript من سلسلة Deep Dive Into Modern Web Development
    1 نقطة
  23. شرح رائع جدا نتمنى المزيد وشرح اكثر دقة ووضوح 😆
    1 نقطة
  24. يُعرَّف البرنامج بأنّه سلسلة من التعليمات التي يستطيع الحاسب تنفيذها لأداء مهمّةٍ ما. تبدو فكرةٍ بسيطةٍ للغاية، لكن حتى يتمكن الحاسب من الاستفادة من التعليمات، يجب أن تكون هذه التعليمات مكتوبةً بصيغةٍ يستطيع الحاسب استخدامها. هذا يحتّم استخدام لغات البرمجة لكتابة هذه البرامج. تختلف لغات البرمجة عن اللغات البشرية العادية بكونها واضحةً كليًّا، وهي حازمةٌ للغاية فيما هو مسموح أو غير مسموح في البرنامج. تدعى القواعد التي تُحدد ما هو مسموح بصياغة اللغة (Syntax). تُوصّف قواعد الصياغة المفردات الأساسية للغة وكيفيّة بناء البرامج باستخدام أشياء كالحلقات والأفرع والبرامج الفرعية. يُعرّف البرنامج صحيح الصياغة بأنّه البرنامج الذي يمكن تصريفه أو ترجمته بنجاحٍ؛ تُرفض البرامج التي تحتوي على أخطاء صياغة (ولعلّ هذا يترافق مع رسالة خطأ مفيدة تساعدك في حل المشكلة). لذا، لكي تكون مُبرمجًا ناجحًا، عليك أن تكتسب معرفة مُفصّلة بصياغة لغة البرمجة التي ستستخدمها. بيد أنّ الصياغة هي مجرّد جانب من الأمر. ليس كافيًا أن تكتب برنامجًا يعمل، أنت تريد برنامجًا يعمل، ويقدّم نتيجة صحيحة. بكلمات أخرى، يجب أن يكون "معنى" البرنامج سليمًا. يُشار إلى معنى البرنامج بمصطلح "الدلالة اللفظيّة" (Semantics). بكلمات أدق، تُحدّد الدلالةُ اللفظية للغة البرمجة مجموعة القواعد التي تحدد معنى البرنامج المكتوب بتلك اللغة. يُعرّف البرنامج صحيح الدلالة بأنه البرنامج الذي يقوم بما تريد له أن يقوم به. أبعد من ذلك، يمكن للبرنامج أن يكون صحيح الصياغة والدّلالة إلا أنّ هذا لا ينفي كونه برنامجًا سيئًا. إذ أنّ استخدام اللغة على نحو صحيح لا يكافئ استخدامها على نحو مفيد. على سبيل المثال، يمتاز البرنامج الجيد بأسلوب كتابةٍ، أي أنّه يُكتب بطريقةٍ تجعل من السهل قراءته وفهمه عبر اتباع أعرافٍ معروفةٍ لدى المبرمجين الآخرين. كما أنّ البرنامج الجيّد يمتاز بتصميم شامل مفهوم للقرّاء البشريين. لا يكترث الحاسب لأمور كهذه البتة، إلّا أنها بالغة الأهمية لقارئ بشري. يُشار عادةً إلى الجوانب هذه من البرمجة بمصطلح التأويل أو التداوليات (pragmatics). (سنستخدم التعبير الأكثر شيوعًا "أسلوب" [style].) عندما نتطرّق لميزة جديدة في اللغة، سنشرح الصياغة والدلالة وبعضًا من تداوليات تلك الميزة. عليك أوّلًا أن تحفظ الصياغة، وهذا الجانب السهل من الموضوع. ثمّ عليك فهم الدلالة عبر متابعة الأمثلة المطروحة، والتأكد من أنك تدرك كيفيّة عملها، والأفضل من ذلك أن تكتب برامج قصيرةً بنفسك لاختبارك فهمك. وعليك أن تحاول إدراك واستيعاب التداوليات، ونعني تعلّم كيفية استخدام ميزة اللغة على نحوٍ سليمٍ وذلك بأسلوبٍ يُكسبك احترام باقي المبرمجين. لا شكّ أنّ كونك معتادًا على جميع الميزات الفردية للغة لا يعني أنّك أصبحت مُبرمجًا. لا زال عليك تعلّم كيفية بناء برامج معقدة لحل مسائل بعينها. وستحتاج في هذا الأمر إلى الخبرة والذوق. ستجد في هذا الكتاب تلميحات حول تطوير البرمجيات. نبدأ رحلتنا في اكتشاف جافا بمسألة أضحت عُرفًا في بدايات كهذه: كتابةُ برنامج يعرض الرسالة "مرحبًا أيها العالم!". قد تبدو مسألة هامشيّة، لكن قدرتك على جعل الحاسب يقوم بذلك هي خطوةٌ أولى كبيرة في تعلم لغة برمجة جديدة (خاصةً إذا ما كانت لغتك الأولى)، وهي تعنى فهمك للعملية الأساسيّة التالية: جلب نص البرنامج إلى الحاسب. تصريف البرنامج، ومن ثمّ تنفيذ البرنامج المُصرّف. سيتطلب الأمر في الغالب عدّة محاولات لتتمكن من القيام بذلك في المرة الأولى. لن نتعمّق في تفاصيل كيفية القيام بكلّ من تلك الخطوات هنا، الأمر يتعلق بجهاز حاسبك وبيئة برمجة جافا التي تستخدمها. انظر القسم 2.6 لمعلومات حول إنشاء وتنفيذ برامج جافا في بيئات برمجة معينة. لكن بشكلٍ عام، ستكتب البرنامج باستخدام محرر نصوص ما وتحفظ البرنامج في ملف. ثم ستستخدم أمرًا في محاولةٍ لتصريف الملف، فإمّا أن تحصل على رسالة مفادها أنّ البرنامج يحتوي على أخطاء صياغة، أو تحصل على نسخةٍ مُصرّفة إلى شيفرة جافا الثّمانيّة، وليس إلى لغة الآلة. أخيرًا، يمكنك تنفيذ البرنامج المصرف عبر استخدام الأمر المناسب. في الواقع، ستستخدم من أجل لغة جافا مُفسرًا لتنفيذ شيفرة جافا الثمانية. قد تؤتمت بيئة البرمجة التي اخترتها بعضًا من الخطوات عوضًا عنك. على سبيل المثال، عادةً ما تتمّ خطوة التصريف أوتوماتيكيًا لكن كن متأكدًا أن الخطوات الثلاث نفسها تجري دومًا وراء الكواليس. إليك برنامج جافا يعرض الرسالة "مرحبًا أيها العالم!". لا عليك إن لم تفهم كل ما يجري هنا الآن، حيث لن نتطرق لبعض مفاهيمه حتى بضع فصول من الآن. /** برنامج لعرض الرسالة * ‫ التالية "مرحبًا أيها العالم!" */ public class HelloWorld { public static void main(String[] args) { System.out.println("!‏مرحبًا أيها العالم"); } } // HelloWorld نهاية الصنف الأمر الذي يعرض الرسالة في الحقيقة هو: System.out.println("!مرحبًا أيها العالم"); الأمر السّابق مثالٌ عن تعليمة استدعاء برنامج فرعيّ إذ تستخدم "برنامجًا فرعيًّا مُدمجًا" (built-in subroutine) يُدعى System.out.println لأداء العمل الحقيقيّ. تذكّر أنّ البرنامج الفرعيّ يتألّف من تعليمات لأداء مهمةٍ ما وقد جُمّعت معًا ومُنحت اسمًا. يمكن استخدام هذا الاسم لاستدعاء البرنامج الفرعيّ متى ما احتجنا أداء تلك المهمة. البرنامج الفرعي المدمج هو برنامجٌ فرعيّ مُعرّف مسبقًا كجزء من اللغة وبذلك يكون متاحًا للاستخدام افتراضيًّا في أي برنامج. عندما تُنفّذ هذا البرنامج، تُعرض الرسالة "مرحبًا أيها العالم" (بدون علامتي الاقتباس اللاتينية) على الخرج القياسي. مع الأسف لا أستطيع شرح معنى الخرج القياسي بالضبط. يُفترض أن تعمل جافا على العديد من المنصات المختلفة، ويعني الخرج القياسي أمرًا مختلفًا على كلّ منها. على أيّة حال، يمكنك أن تتوقع ظهور الرسالة في مكان ملائم أو غير ملائم. (إن كنت تستخدم واجهة أوامر سطرية، كتلك الموجودة في أدوات تطوير جافا التي تقدّمها شركة أوراكل، فعندما تكتب الأمر لإخبار الحاسب بتنفيذ البرنامج سيطبع الحاسب خرج البرنامج – الرسالة "مرحبًا أيها العالم!"- على السطر التالي. بيد أنّه في بيئة تطوير متكاملة مثل Eclipse، قد يظهر الخرج في واحدةٍ من نوافذ البيئة.) لا بدّ أنك تشعر بالفضول حيال ما تبقى من أشياء في البرنامج أعلاه. يتألف جزء منه من تعليقاتٍ (comments). يتجاهل الحاسب كليًّا جميع التعليقات في البرنامج، وتقتصر أهميّة وجودها على القراء البشريين فقط. هذا لا يعني أنّها غير مهمّة. يُفترض أن يستطيع الناس كما الحواسيب قراءة البرامج، وقد يكون البرنامج بدون التعليقات صعب الفهم عليهم. يوجد نوعان من التعليقات في جافا. يبدأ النوع الأول بالشريطتين // ويمتد حتى نهاية السطر. يوجد تعليق من هذا الشكل في السطر الأخير من البرنامج أعلاه. يتجاهل الحاسب الشريطتين // وكل ما يليهما على السطر نفسه. من جهة أخرى، يبدأ النوع الثاني من التعليقات بشريطة ونجمة /* وينتهي بشريطة ونجمة */ ويمكن أن يمتد ليشمل عدة أسطر. تُمثل الأسطر الثلاث الأولى من البرنامج مثالًا عن هذا النوع من التعليقات. (للتعليق الذي يبدأ بشريطة ونجمتين /**، كالمذكور أعلاه، معنى خاص؛ حيث أنه تعليق خاص بتوثيق جافا Javadoc ويمكن استخدامه لتوليد توثيق للبرنامج. انظر القسم الفرعي 4.6.5). كل ما عدا ذلك في البرنامج تفرضه قواعد صياغة جافا. تتمُّ البرمجة كاملةً في جافا ضمن "أصناف". ينصّ السطر الأول في البرنامج أعلاه (دون التعليق) أنّ هذا الصنف يدعى HelloWorld. إضافةً لكونه اسم الصنف، يُمثّل الاسم "HelloWorld" اسم البرنامج أيضًا. ليس كل برنامجٍ صنفًا. لتحديد البرنامج، يجب أن يتضمن الصنف برنامجًا فرعيًّا يدعى main وذلك بتعريفٍ يأخذ الشكل الآتي: public static void main(String[] args) { **تعليمات** } عندما تطلب من مُفسّر جافا أن ينفّذ البرنامج، يستدعي المُفسّر البرنامج الفرعيّ main()‎، وبذلك تُنفّذ التعليمات التي يحتويها. تُؤلّف هذه التعليمات النص البرمجيّ الذي يخبر الحاسب بالضبط بما سيقوم به عند تنفيذ البرنامج. يستطيع البرنامج main()‎ استدعاء برامج فرعيّة أخرى مُعرّفة في الصنف نفسه أو حتى في أصنافٍ أخرى، لكن البرنامج main()‎ هو من يحدّد كيفية وترتيب استخدام البرامج الفرعيّة الأخرى. تعني الكلمة public في السطر الأول من التابع ‎main‎‎()‎ أنّ هذا البرنامج (routine) يمكن استدعاؤه من خارج البرنامج. هذا الأمر بالغُ الأهميّة إذ أنّ البرنامج main()‎ يُستدعى من قبل مُفسّر جافا الذي هو أحيانًا خارجيّ بالنسبة للبرنامج نفسه. ما تبقى من السطر الأول من البرنامج أصعب من أن يُفسّر في الوقت الحالي، فكر بالأمر الآن على أنّه مجرّد صياغة ضرورية. البرنامج الفرعيّ هو مجموعةٌ من الأوامر التي تملي ما يجب القيام به، ويتألف في جافا من سلسلة من التعليمات المحاطة بقوسين من الشكل { }. استخدمنا هنا الكلمة «تعليمات» (statements) نيابةً عن التعليمات الفعلية في الحيز المخصص لها ضمن البرنامج. سنستخدم الترجمة العربية في الشيفرات باستمرار في هذا الكتاب للإشارة إلى حيّز ينوب عنه وصفه وتحتاج لكتابته فعلًا عندما تقوم بالبرمجة. كما أشرنا أعلاه، لا يتواجد البرنامج الفرعي بمفرده بل يكون جزءًا من "صنف". يُعرف البرنامج من خلال صنفٍ عام يأخذ الشكل التالي: تصريحات حزم اختيارية استيراد حزم اختيارية public class اسم البرنامج { تصريحات اختيارية لمتغيرات وبرامج فرعية public static void main(String[] args) { تعليمات برمجية } تصريحات اختيارية لمتغيرات وبرامج فرعية } يتعلق أول سطرَين باستخدام الحزم (packages). تُعرّف الحزمة بأنها مجموعة من الأصناف وستتعلم المزيد عنها في القسم 2.4 إلّا أنّ أمثلتنا في بادئ الأمر لن تستخدمها. يُمثل اسم البرنامج في السطر الذي يبدأ بالكلمات المفتاحية public class اسم البرنامج واسم الصنف في آنٍ معًا. تذكر أن استخدامنا لكلمة اسم البرنامج هو نيابةً عن الاسم الفعلي. إن كان اسم الصنف HelloWorld، عند ذلك يجب حفظ الصنف في ملفٍّ يحمل الاسم HelloWorld.java. عند تصريف هذا الملف، يتولد ملف جديد يحمل الاسم HelloWorld.class. يحتوي ملف الصنف هذا على ترجمة البرنامج إلى شيفرة جافا الثمانيّة والتي يمكن تنفيذها باستخدام مفسر جافا. يُدعى الملف HelloWorld.java بالشيفرة المصدرية (source code) للبرنامج. تحتاج لتنفيذ البرنامج إلى ملف class المُصرّف فقط وليس الشيفرة المصدرية. لا أهمية لتنسيق هيئة البرنامج على الصفحة كاستخدام الأسطر الفارغة والمسافات البادئة فهي ليست جزءًا من صياغة أو دلالة اللغة. لا يأبه الحاسب لهيئة البرنامج ولن يتغيّر شيءٌ حتى لو كتبت برنامجك على سطرٍ واحد. بيد أنّ هيئة البرنامج وتنسيق كتابته مهمّ للقراء البشريين، وهنالك ضوابط تنسيق محددة يتبعها معظم المبرمجين. لاحظ أيضًا أنه وفقًا لتوصيف الصياغة أعلاه، يمكن أن يحتوي البرنامج على برامج فرعية أخرى إلى جانب البرنامج الفرعيّ main()‎ إضافةٍ إلى ما يدعى بتصريح المتغيرات. ستتعلم المزيد عن كل هذا لاحقًا في الفصل 4. ترجمة -بتصرّف- للقسم Section 2.1 The Basic Java Application من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java.
    1 نقطة
  25. هناك جانبان أساسيان للبرمجة: البيانات (data) والتعليمات (instructions). للعمل مع البيانات، تحتاج إلى فهم المُتغيّرات (variables) والأنواع (types)؛ أمّا للعمل مع التعليمات عليك فهم بنى التحكم (control structures) والبرامج الفرعيّة (subroutines). ستقضي الجزء الأكبر من بداية هذه السلسلة، مدخل إلى جافا، في التعرف على هذه المفاهيم. المُتغيّر (variable) هو مجرد موقع في الذّاكرة (أو عدة مواقع متتالية تُعامل وكأنها وحدةٌ واحدة) يُمنح اسمًا لتسهيل الإشارة إليه واستخدامه ضمن برنامجٍ. على المبرمج القلق فقط حيال الاسم، وتقع مسؤولية تتبع موقع الذاكرة على عاتق المُصرّف (compiler). بصفتك مُبرمجًا، ستحتاج فقط إلى تذكّر أنّ الاسم يُشير إلى ما يشبه صندوقًا يحتضن البيانات في الذاكرة، على الرغم أنك غير مضطرٍ لمعرفة أين يقع هذا الصندوق بالضبط في الذاكرة. في جافا والعديد غيرها من لغات البرمجة، يكون لكلّ متغيّر نوعٌ يُشير إلى نوع البيانات التي يمكنه الاحتفاظ بها. قد يحتفظ نوع من المتغيرات بالأعداد الصحيحة (integers) – مثل 3، أو 7-، أو 0 – بينما يحتفظ نوع آخرٌ بأعداد عشرية (floating point) وهي الأعداد ذات الفواصل العشرية، مثل 3.14، أو 2.7-، أو 17.0. نعم، يميّز الحاسوب بوضوح بين العدد الصحيح 17 والعدد ذي الفاصلة العشرية 17.0؛ تبدو هاتان القيمتان مختلفتين تمامًا داخل الحاسوب. يمكن أن يكون هناك أيضًا أنواع للمحارف (characters) مثل: 'A'، ';' وغيرها، والسلاسل النصية (strings) مثل: " مرحبًا "، " قد تتضمّن السلسة النصية العديد من المحارف "، وغيرها. وهنالك أنواعٌ أقل شيوعًا مثل التواريخ، الألوان، الأصوات أو أي نوع من البيانات قد يحتاج البرنامج تخزينها. تتضمن لغات البرمجة دائمًا أوامر لجلب البيانات من وإلى المتغيرات وأداء العمليات الحسابيات على هذه البيانات. على سبيل المثال، تُخبر عبارة الإسناد التالي -والتي قد ترد في برنامج جافا- الحاسوبَ بأن يأخذ العدد المُخزّن في المُتغيّر المُسمّى principal، ويضرب ذاك العدد بالقيمة 0.07 ويخزّن النتيجة في المُتغيّر المُسمّى interest: interest = principal * 0.07; هنالك أيضًا "أوامر دخل" لجلب البيانات من المستخدم أو من الملفات المخزَّنة على الحاسوب، وهنالك "أوامر خرج" لإرسال البيانات في الاتجاه المعاكس. هذه الأوامر الأساسيّة الخاصّة بنقل البيانات من مكانٍ إلى آخر وأداء الحسابات هي عناصر البناء الأساسية لجميع البرامج. تُجمع عناصر البناء هذه لتُكوِّن برامج معقدةً باستخدام بنى التحكم والبرامج الفرعيّة. البرنامج هو عبارة عن سلسلة من التعليمات. في تدفق التحكم العادي، يُنفّذ الحاسوب التعليمات وفقًا لترتيب ورودها في البرنامج، واحدةً تلو الأخرى. إلّا أنّ نموذج العمل هذا محدودٌ جدًا، إذ سرعان ما يُنفِّذ الحاسوب التعليمات المراد تنفيذها ولكن مع بنى التحكم (Control structures)، التي هي تعليمات خاصة، يمكن تغيير تدفق تنفيذ الشيفرة. هناك نوعان أساسيّان لبنى التحكم هذه: الحلقات (Loops)، تسمح بتكرار تنفيذ سلسلة من التعليمات مرارًا وتكرارًا، والتّفرعات (branches)، والتي تسمح للحاسوب بالاختيار بين مساري عمل مختلفين أو أكثر من خلال اختبار شروطٍ أثناء تنفيذ البرنامج. على سبيل المثال، قد يكون الشرط كالتالي: إذا كانت قيمة المُتغيّر principal أكبر من 10000، يُحسب عندئذٍ المُتغيّر interest بضرب قيمة principal بالقيمة 0.05؛ وإلا يُحسَب interest بضرب قيمة principal بالقيمة 0.04. يحتاج البرنامج إلى وسيلة للتعبير عن قرارٍ كهذا. في لغة جافا، يُعبّر عما سبق بعبارة if الشرطية التالية: if (principal > 10000) interest = principal * 0.05; else interest = principal * 0.04; (لا تقلق بشأن التّفاصيل الآن. تذكر فقط أن الحاسوب يستطيع اختبار شرطٍ وتحديد ما سيقوم به لاحقًا بناءً على نتيجة ذاك الاختبار.) تُستخدَم الحلقات loops عندما يلزم أداء المهمة ذاتها أكثر من مرة. على سبيل المثال، إذا أردت طباعة تسميةٍ بريدية (mailing label) لكل واحد من الأسماء على قائمة المراسلات، قد يخطر لك التالي: "أحضر الاسم الأول والعنوان واطبع التسمية، أحضر الاسم الثاني والعنوان واطبع التسمية، أحضر الاسم الثالث والعنوان واطبع التسمية.." لكن سرعان ما ستدرك أنّ خطتك غير عمليّة وقد تفشل إن لم تكن تعرف مُسبقًا عدد الأسماء لديك. قد ترغب عوضًا عن ذلك بقول شيء من هذا القبيل: "طالما أن هنالك المزيد من الأسماء التي تحتاج المعالجة، أحضر الاسم التالي والعنوان واطبع التسمية." يُمكن استخدام الحلقة في البرنامج للتعبير عن تكرارٍ كهذا. البرامج الضخمة مُعقَّدة لدرجة تجعل من المستحيل كتابتها إن لم يكن ثمة طريقة لتجزئتها إلى "كتل" تسهل إدارتها. تُؤمن البرامج الفرعية (subroutine) وسيلةً لأداء ذلك. يتألف البرنامج الفرعي من التعليمات اللازمة لأداء مهمةٍ ما وقد جُمعت معًا كوحدةٍ منفردة ومُنحت اسمًا مميِّزًا. يمكن استخدام هذا الاسم بديلًا عن مجموعة التعليمات بأكملها. على سبيل المثال، افرض أنّ إحدى المهام التي يحتاج برنامجك أداءها هي رسم منزلٍ على الشاشة. يمكنك كتابة التعليمات اللازمة، وجمعها في برنامج فرعيّ ومن ثم منح ذلك البرنامج الفرعي اسمًا ملائمًا ولنقل drawHouse()‎. عند ذلك، تستطيع عند حاجتك لرسم منزلٍ في أي مكان من البرنامج أداء ذلك باستخدام أمر واحدٍ كالتالي: drawHouse(); وسيكون له نفس المفعول فيما لو كررت جميع تعليمات رسم المنزل في كل مرة. لا تقتصر إيجابية الأمر على توفير كتابة بعض الأسطر حيث يساعد تنظيم برنامجك ضمن برامج فرعية على تنظيم أفكارك وجهود تصميم برنامجك. عند كتابتك البرنامج الفرعيّ الخاصة برسم المنزل، تستطيع التركيز على مسألة رسم المنزل دون القلق حينها على بقية البرنامج. وحالما تنتهي من كتابة البرنامج الفرعي، يمكنك نسيان تفاصيل رسم المنازل كليًّا، لقد تم حل تلك المشكلة ولديك برنامج فرعي يقوم بها عوضًا عنك. يصبح البرنامج الفرعي جزءًا ضمنيًا من اللغة التي تستطيع استخدامها دون التفكير حول تفاصيل ما يدور "ضمن" البرنامج الفرعي. تُمثل المتغيرات والأنواع، والحلقات، والتّفرعات والبرامج الفرعية أساس ما نصطلح تسميته "البرمجة التقليدية" (traditional programming)، غير أنّه مع ازدياد حجم البرامج، نحتاج أيضًا إلى بنى إضافية لتساعدنا في التعامل مع تعقيد هذه البرامج. أحد أكثر هذه الأدوات فعالية هي "البرمجية كائنية التوجه" (object-oriented programming) وسنتطرق لها في القسم التالي. ترجمة وبتصرف للفصل Fundamental Building Blocks of Programs من كتاب Introduction to Programming Using Java
    1 نقطة
    كتاب مفيد جدا ويتسم بالعمق في المعلومة .. كل الشكر والتقدير لأكاديمية حسوب وللمترجم والمهندس المبدع الأخ عبداللطيف كل الامتنان والتقدير أيضاً.
    1 نقطة
×
×
  • أضف...