سجى الحاج

قد تحتاج أحيانًا إلى تنفيذ إجراء مماثل في أكثر من موضع في السكربت مثل عرض رسالةٍ جميلةٍ للمستخدم عند تسجيل الدخول وتسجيل الخروج وربما في مكان آخر أيضًا.

الدوال (Functions) عبارةٌ عن كتل برمجيَّة تُنفِّذ مجموعة من المهام وفق تسلسل مُحدَّد، فهي بذلك تُشكل «اللبنات الأساسية» للبرنامج. تسمح الدوال باستدعاء شيفرة ما عدَّة مرات دون الحاجة لكتابتها من جديد.

لقد رأيت خلال الدروس السابقة أمثلةً على دوال مبنيَّة مسبقًا (built-in functions)، مثل alert(message)‎، و prompt(message, default)‎، و confirm(question)‎، ويمكنك أيضًا إنشاء دوال خاصَّة بك.

تعريف الدوال

تُعرَّف الدالة بالصياغة التالية:

function showMessage() {
  alert( 'مرحبًا بالجميع!' );
}

تأتي الكلمة المفتاحية function أولًا، يليها اسم الدالة (showMessage في حالتنا)، ثم المعاملات (parameters) التي هي مجموعة من متغيرات تفصل فيما بينها بفاصلة لاتينية , (غير موجودة في المثال أعلاه لأنَّها اختياريَّة)، وأخيرًا جسم الدالة (function body) بين الأقواس المعقوصة وهو الإجراء المراد تنفيذه.

function name(parameters) {
  // جسم الدالة
}

تُستدعَى الدالة بكتابة اسمها متبوعًا بقوسين هلاليين () مثل showMessage()‎:

function showMessage() {
  alert( مرحبًا جميعًا!' );
}
showMessage();
showMessage();

يُنفِّذ الاستدعاء showMessage()‎ جسم الدالة أي أنك سترى الرسالة مرتين.

يوضِّح هذا المثال أحد أسباب استخدام الدوال، وهو تجنب تكرار الجمل البرمجية ذاتها. وفي حال أحتجت لتغيير الرسالة أو الطريقة التي تُعرَض بها، يكفي أن تُعدِّل الشيفرة من مكان واحد أي من الدالة فقط.

المتغيرات المحلية

المتغير الذي عُرِّف (صُرِّح عنه) داخل حدود دالةٍ ما مرئيٌّ فقط داخل هذه الدالة ويدعى أنذاك «متغيِّرًا محليًّا» (Local variable). إليك المثال التالي:

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // متغِّير محلي
  alert( message );
}
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- خطأ! المتغيِّر محلي وموجود ضمن نطاق الدالة فقط

المتغيرات العامة

يُعرَّف المتغير العام (outer variable، ويدعى أيضًا global variable) خارج حدود الدالة ويمكنها الوصول إليه أيضًا مثل المتغيِّر userName في المثال التالي:

let userName = 'محمد';
function showMessage() {
  let message = 'مرحبًا، ' + userName;
  alert(message);
}
showMessage(); // مرحبًا، محمد

تملك الدالة الوصول الكامل إلى المتغير العام، إذ يمكنها التعديل عليه. فيمكننا مثلًا تعديل قيمة المتغيِّر userName العام من داخل الدالة قبل استعماله مثلًا:

let userName = 'محمد';
function showMessage() {
  userName = "أحمد‎"; // (1) تغيير قيمة المتغير العام
  let message = 'مرحبًا، ' + userName;
  alert(message);
}
alert( userName ); // محمد، قبل استدعاء الدالة
showMessage();
alert( userName ); // أحمد، بعد تعديل الدالة قيمته

تستخدم الدالة المتغير العام في حالة واحدة وهي عدم وجود متغير محلي.

إذا عُرِّف متغير محلي داخل دالة يحمل اسم المتغير العام نفسه، يغطي المتغيرالمحلي على العام ضمن حدود الدالة ويطغى عليه. في الشيفرة أدناه مثلًا، تستخدم الدالة showMessage()‎ المتغير userName الذي أنشئ محليًّا وتتجاهل المتغير الخارجي العام:

let userName = 'محمد';
function showMessage() {
  let userName = "أحمد‎"; // التصريح عن متغيِّر محلي
  let message = 'مرحبًا، ' + userName; // أحمد
  alert(message);
}
// ستنشئ الدالة المتغيّر المحلي الخاص بها وتستعمله
showMessage();
alert( userName ); // محمد، لم تغير الدالة المتغير العام أو تصل إليه

ملاحظة: المتغيرات العامة هي المتغيرات التي تُعرَّف خارج حدود الدالة وتكون مرئيةً من أي دالة (إلا إن حُجِبَت [shadowed] بمتغيِّر محلي يحمل الاسم نفسه). يعدُّ الحد من استخدام المتغيرات العامة سلوكًا جيدًا، إذ تحوي الشيفرة الحديثة عددًا قليلًا من المتغيرات العامة أو لا تحتوي عليها إطلاقًا. ومعظم المتغيرات مُعرَّفةٌ داخل دوالها (أي الاقتصار على المتغيرات المحلية). تفيد المتغيرات العامة أحيانًا في تخزين بيانات على مستوى المشروع ككل (project-level data).

المعاملات

يمكنك تمرير أية بيانات إلى الدوال باستخدام «المعاملات» (Parameters، وتسمى أيضًا وسائط الدالة [function arguments]).

فتملك الدالة في المثال التالي على معاملين هما: from و text.

function showMessage(from, text) { // text و from :الوسائط هي
  alert(from + ': ' + text);
}
showMessage('مريم',‎ 'مرحبًا!'); // ‎مريم: مرحبًا (*)
showMessage('مريم',‎ "كيف الحال؟"); // مريم: كيف الحال؟ (**)

عندما تُستدعَى الدالة في السطر (*) والسطر (**)، تُنسَخ القيم المُعطاة إلى متغير محلي باسم from ومتغير محلي آخر باسم text ثم تستخدمها الدالة آن ذاك.

إليك مثال آخر مهم أرجو التركيز عليه؛ لدينا متغيّر باسم from مرَّرناه إلى الدالة showMessage()‎. لاحظ أن التعديل على المتغير from مرئيٌّ فقط داخل الدالة ولا ينعكس خارجها، وذلك لأنَّ الدالة تحصل دائمًا على نسخة من قيمة المتغيِّر ثم تتركه وشأنه:

function showMessage(from, text) {
  from = '*' + from + '*'; // بمظهر مختلف "from" إظهار
  alert( from + ': ' + text );
}
let from = "مريم";
showMessage(from, "مرحبًا"); // *مريم*: مرحبًا
// لأن الدالة عدّلت على متغير محلي مسمًى باسمه "from" لا تتغير قيمة
alert( from ); // مريم

القيم الافتراضية

إن لم تُمرَّر أية قيمة لمعاملات دالةٍ، تُصبِح قيمها آنذاك غير مُعرَّفة undefined. فيمكن استدعاء الدالة showMessage(from, text)‎ التي ذكرناها مسبقًا مع تمرير قيمةٍ واحدةٍ لها مثل:

showMessage("مريم");

لا يُعدُّ هذا خطأً بل يُظهِر القيمة "مريم: undefined". لمَّا لم تُعطَ قيمةٌ للمعامل text، فسيُفترَّض أن text === undefined (أي يُعطَى القيمة showMessage).

إذا أردت إسناد قيمة افتراضية للمعامل text، فيمكنك تحديدها عبر معامل الإسناد = أثناء تعريف الدالة بالشكل التالي:

function showMessage(from, text = "لم تُعطَ قيمة لـ text") {
  alert( from + ": " + text );
}
showMessage("مريم"); // text مريم: لم تُعطَ قيمة لـ 

إن لم تُمرَّر الآن قيمةٌ للمعامل textعند استدعاء الدالة، فستُسنَد له القيمة "لم تُعطَ قيمة لـ text" التي هي عبارة عن سلسلة نصية. ويمكن أيضًا أن تكون القيمة الافتراضية تعبيرًا معقَّدًا يُقيَّيم ثم تُسنَد القيمة الناتجة إليه إذا، وفقط إذا، لم تُعطَ قيمة لذلك المعامل؛ لذلك، الشيفرة التالية صحيحةٌ أيضًا:

function showMessage(from, text = anotherFunction()) {
  // text إذا لم تُمرَّر قيمة للمعامل anotherFunction() تُنفَّذ الدالة
  // الحالية آنذاك text وتصبح القيمة التي تعيدها هي قيمة المعامل
}

ملاحظة حول تقييم المعاملات الافتراضية

يُقيّم المعامل الافتراضي في JavaScript في كل مرة تُستدعَى فيها الدالة دون المعامل المقابل له. ففي المثال أعلاه، تُستدعَى الدالة anotherFunction()‎ في كل مرة تُستدعَى فيها الدالة showMessage()‎ دون المعامل text.

ملاحظة حول الطراز القديم للمعاملات الافتراضية

لم تكن الإصدارات القديمة من JavaScript تدعم المعاملات الافتراضية، ولكن كان هناك طرائق بديلة لدعمها، قد تمر معك عند مراجعتك لسكربتات قديمة مثل التحقق الصريح من كون المعامل غير مُعرَّف بالشكل التالي:

function showMessage(from, text) {
  if (text === undefined) {
    text = 'no text given';
  }
  alert( from + ": " + text );
}

أو عن طريق استعمال المعامل || بالشكل التالي:

function showMessage(from, text) {
  // غير معطاة (أي غير معرفة)، فستُستعمَل القيمة الافتراضية text إن كانت قيمة المعامل
  text = text || 'لم تُعطَ أية قيمة';
  ...
}

إرجاع قيمة

يمكن للدالة إرجاع قيمة معنيَّة إلى من استدعاها. وأبسط مثال على ذلك دالةٌ تجمع قيمتين ثم تعيد القيمة الناتجة:

function sum(a, b) {
  return a + b;
}
let result = sum(1, 2);
alert( result ); // 3

يمكن أن يقع الموجه return في أي مكان داخل الدالة، ولكن انتبه لأن تنفيذ الدالة يتوقف عند الوصول إليه، وتُعَاد القيمة (أو ناتج تقييم تعبير برمجي) التي تليه إلى من استدعاها (result أعلاه).

قد تحوي دالةٌ واحدةٌ عدَّة موجهات return مثل:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    return confirm('هل تملك إذنًا من والديك؟');
  }
}
let age = prompt('كم عمرك؟‎', 18);
if ( checkAge(age) ) {
  alert( 'سُمِح له/ا بالوصول' );
} else {
  alert( 'مُنِعـ/ت من الوصول' );
}

يمكن إنهاء تنفيذ الدالة فورًا عن طريق استخدام return دون قيمة مثل:

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }
  alert( "يبدأ عرض الفلم" ); // (*)
  // ...
}

في الشيفرة أعلاه، إذا كان التعبير checkAge(age)‎ يُرجع القيمة false، فلن تُظهر عندئذٍ الدالة alert السلسلة النصية showMovie.

ملاحظة حول إرجاع قيمة غير مُعرَّفة

تُرجع دالةٌ القيمة undefined إن لم يلي الموجه return أية قيمة أو تعبير أو لم يكن موجودًا (أي لا تعيد الدالة أي شيء):

function doNothing() { /*  */ }
alert( doNothing() === undefined ); // true

بعبارة أخرى، حالة عدم إرجاع الدالة أي شيء وحالة استعمال return;‎ فقط وحالة استعمال return undefined;‎ كلها سواسية وهي أنَّ الدالة تعيد القيمة undefined:

function doNothing() {
  return;
}
alert( doNothing() === undefined ); // true

تنبيه بخصوص إضافة سطر جديد بين الموجه return والقيمة المعادة

لا تضف سطرًا جديدًا بين الموجه return والقيمة (أو التعبير) التي يفترض أن تعيدها الدالة. فعند وجود تعبير طويل يلي return، قد تضعه في سطر منفصل مثل السطر التالي:

return
 (some + long + expression + or + whatever * f(a) + f(b))

ولكنَّ هذه الشيفرة لا تعمل، لأنَّ JavaScript تفترض من تلقاء نفسها وجود الفاصلة المنقوطة بعد return. أي أن هذه الشيفرة ستُقيَّم كما يلي:

return;
 (some + long + expression + or + whatever * f(a) + f(b))

كما ترى، تصبح return فعليًّا فارغة ولا تُرجع الدالة أية قيمة؛ لذلك، يجب أن تكون القيمة المراد إرجاعها والموجه return على السطر نفسه. أي إن أردت وضع التعبير المراد إعادته على عدة أسطر، فيجب كتابة بدايته على السطر نفسه الموجود عليه الموجه return أو وضع قوس هلالي افتتاحي على أقل تقدير كما يلي:

return (
  some + long + expression
  + or +
  whatever * f(a) + f(b)
  )

وستعمل الشيفرة بالشكل المتوقع لها.

تسمية الدوال

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

أحد ممارسات التسمية الشائعة استعمال فعل مصدري ابتدائي في بداية اسم الدالة لوصف الإجراء الرئيسي الذي تُنفِّذه باختصار. ومن الأفضل أن يتفق أعضاء الفريق على معنى أسماء هذه الأفعال البادئة المستخدمة. فالدوال التي تبدأ تسميتها بالفعل "show" مثلًا عادةً ما تُظهِر شيئًا ما.

الدوال التي تبدأ عادةً بالفعل:

  • "get…‎" تجلب قيمة.
  • "calc…‎" تحسب شيئًا ما.
  • "create…‎" تنشئ شيئًا ما.
  • "check…‎" تفحص شيئًا ما وتُرجع قيمة منطقية وهلم جرًّا.

وإليك أمثلة على هذه الأسماء:

showMessage(..)     // إظهار رسالة
getAge(..)          // (جلب العمر (بطريقة ما
calcSum(..)         // حساب المجموع وإعادة الناتج
createForm(..)      // (إنشاء إستمارة (وإعادتها غالبًا
checkPermission(..) // التحقق من إذنٍ وإعادة قيمة منطقية

يتبادر لذهنك سريعًا ماهية العمل الذي تنفِّذه الدالة ونوع القيمة التي تُرجعها عند استخدام البادئات في تسمية الدوال استخدامًا ملائمًا وصحيحًا.

دالة واحدة مقابل إجراء واحد

يجب أن تُنفذ الدالة ما أشار لها اسمها بالضبط، لا أكثر ولا أقل. فعندما تُريد تنفيذ إجراءين مستقلين، يجب عليك تعريف دالتيّن مستقلتين، حتى لو جرى استدعاؤهما معًا (يمكنك في هذه الحالة تعريف دالة ثالثة تستدعي هاتين الدالتين).

بعض الأمثلة لكسر هذه القاعدة:

  • getAge سيكون سيئًا إذا أظهرت الدالة العمر بتنبيه (يجب أن تجلب العمر وتعيده فقط).
  • createForm سيكون سيئًا إذا عدَّلت الصفحة وأضافت الإستمارة إليها (يجب أن تنشئه وأن تعيده فقط).
  • checkPermission سيكون سيئًا إذا عرضت رسالةً تشرح حالة الوصول (يجب أن تفحص الإذن وأن تعيد النتيجة فقط).

تعتمد هذه الأمثلة على المعاني الشائعة لتلك البادئات. ولك مطلق الحرية أنت وفريقك في الاتفاق على معانٍ أخرى، لكن عادةً لا تختلف كثيرًا عما هو شائع لها. على أي حال، يجب أن يترسخ لديك فهمٌ لما تعنيه كل بادئة وما الأشياء التي يمكن أو لا يمكن للدوال الملحق اسمها ببادئة القيام بها. وأخيرًا، ينبغي للفريق تبادل هذا العُرف الموضوع.

أسماء دوال قصيرة جدًا

الدوال التي تُستخدَم بكثرة تملك أحيانًا أسماءً قصيرة جدًا مثل الدالة $ المُعرَّفة في المكتبة jQuery والدالة _ الأساسية الخاصة بالمكتبة Lodash.

تعد تلك الحالات استثنائية، ولكن يجب عمومًا أن تكون أسماء الدوال وصفية وموجزة.

الدوال == التعليقات

يجب أن تكون الدوال قصيرة وتُنفذ إجراءً واحدًا فقط. إذا كان هذا الإجراء معقد، فيفضَّل تقسيم الدالة إلى عدد من الدوال البسيطة. قد لا يكون اتباع هذه القاعدة في بعض الأحيان سهل، لكنه بالتأكيد أمر جيد.

الدالة المنفصلة ليست سهلة الاختبار والتنقيح فقط بل تُعدُّ تعليقًا عظيم الشأن! فمثلًا وازن بين الدالتين showPrimes(n)‎ أدناه، إذ تخرج كل واحدة منهما الأعداد الأولية حتى العدد n المعطى.

الدالة في المثال الأول تستخدم لافتة:

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {
    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }
    alert( i ); // عدد أولي
  }
}

الدالة في المثال الثاني تستخدم الدالة الإضافية isPrime(n)‎ لاختبار الأعداد إذا كانت أولية أم لا:

function showPrimes(n) {
  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;
    alert(i);  // a prime
  }
}
function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

لاحظ أن المثال الثاني سهل الفهم، أليس كذلك؟ فنرى بدلًا من قطعة مبعثرة من الشيفرة اسم إجراءٍ (الذي هو isPrime في مثالنا). يشير الأشخاص أحيانًا إلى هذه الشيفرة بأنها «ذاتية الوصف» أي تصف نفسها بنفسها.

بناءً على ذلك، يمكنك إنشاء الدوال حتى إذا لم ترد إعادة استخدامها وذلك لتنظيم الشيفرة وتسهيل قراءتها.

الخلاصة

صياغة تعريف دالة ما يبدو بالشكل التالي:

function name(parameters, delimited, by, comma) {
  /* الإجراء الذي تنفذه الدالة */
}
  • تُنسَخ القيم التي تُمرَّر إلى الدالة على أنها معاملات إلى متغيرات محلية تستخدمها الدالة.
  • تستطيع أي دالة الوصول إلى المتغيرات العامة الخارجية، لكن أولوية الاستخدام تكون للمتغيرات المحلية ثم المتغيرات العامة (إن لم تتوفر المحلية). لا تملك الشيفرة التي تقع خارج حدود الدالة وصولًا إلى متغيراتها المحلية.
  • يمكن للدالة إرجاع أي قيمة، أو القيمة undefined إن لم تُرجع أي شيء.

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

فهم عمل دالة تحتوي على معاملات وتستعملها ثم تُرجع قيمةً أسهل من فهم دالة لا تحتوي على أي معاملات، ولكنها تستعمل المتغيرات العامة وتعدل عليها.

تسمية الدالة:

  • يجب أن يصف اسم الدالة الإجراء الذي تُنفِّذه بوضوح. عندما ترى استدعاءً لدالة في شيفرةٍ ما، فإن الاسم الجيد يساعدك على فهم ما ستُنفذه وما ستُرجعه على الفور.
  • الدالة عبارة عن إجراء (فعل)، لذلك عادة ما تستعمل الأفعال المصدرية في تسمية الدوال.
  • يوجد العديد من البادئات المشهورة في إلحاقها بأسماء الدوال مثل create…‎، و show…‎، و get…‎، و check…‎ تُستخدَم للإشارة لما ستُنفذه هذه الدوال.

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

التمارين

هل وجود التعبير البرمجي else مهم في الشيفرة؟

الأهمية: 4

ترجع الدالة التالية القيمة true إذا كانت قيمة المعامل age أكبر من 18 وإلا فإنها تطلب تأكيدًا ثم ترجع نتيجته:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    // ...
    return confirm('هل أخذت إذن والديك؟');
  }
}

هل سيتغير عمل الدالة إذا حُذف التعبير else؟

function checkAge(age) {
  if (age > 18) {
    return true;
  }
  // ...
  return confirm('Did parents allow you?');
}

هل يوجد اختلاف في سلوك هذين المثالين؟

الحل

لا يوجد أي اختلاف.

أعد كتابة الدالة باستخدام المعامل ? أو المعامل ||

الأهمية: 4

ترجع الدالة التالية القيمة true إذا كانت قيمة المعامل age أكبر من 18 وإلا فإنها تطلب تأكيدًا وتُرجع نتيجته:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    return confirm('هل يأذن لك والديك؟');
  }
}

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

اكتب نموذجين مختلفين للدالة checkAge، وليكن النموذج الأول باستخدام المعامل الشرطي علامة الاستفهام ?، والثاني باستخدام المعامل ||.

الحل

النموذج الأول باستخدام المعامل الشرطي علامة الاستفهام ?:

function checkAge(age) {
  return (age > 18) ? true : confirm('هل يأذن لك والديك؟');
}

النموذج الثاني باستخدام المعامل || (النموذج الأبسط):

function checkAge(age) {
  return (age > 18) || confirm('هل يأذن لك والديك؟');
}

لاحظ أن الأقواس حول التعبير age > 18 ليست مطلوبة هنا لكن وجودها يُسهِّل قراءة الشيفرة.

الدالة min(a,b)‎

الأهمية: 1

اكتب الدالة min(a,b)‎ التي ترجع قيمة العدد الأصغر من العددين a و b المعطيين. أي دالة مثل:

min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1

الحل

النموذج الأول باستخدام التعبير الشرطي if:

function min(a, b) {
  if (a < b) {
    return a;
  } else {
    return b;
  }
}

النموذج الثاني باستخدام المعامل الشرطي علامة الاستفهام ?:

function min(a, b) {
  return a < b ? a : b;
}

ملاحظة: في حال كان العددان متساويين a == b، لا فرق إذا أُرجِع الأول أم الثاني.

الدالة pow(x,n)‎

الأهمية: 4

اكتب الدالة pow(x,n)‎ التي تُرجع قيمة العدد x مرفوع للقوة n. بعبارة أخرى، تُرجع ناتج ضرب العدد x في نفسه عدد n من المرات.

pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...* 1 = 1

أنشئ صفحة ويب باستخدام الدالة prompt تسمح للمستخدم إدخال العدد x والعدد n، ثم تُظهر نتيجة الدالة pow(x,n)‎. ملاحظة: في هذا التمرين، يجب أن يكون العدد n من الأعداد الطبيعية الصحيحة وأكبر من العدد 1 أيضًا.

الحل

function pow(x, n) {
  let result = x;
  for (let i = 1; i < n; i++) {
    result *= x;
  }
  return result;
}
let x = prompt("x?", '');
let n = prompt("n?", '');
if (n < 1) {
  alert(`القوة ${n} غير مدعومة، لذا أدخل عددًا صحيحًا أكبر من الصفر`);
} else {
  alert( pow(x, n) );
}

ترجمة -وبتصرف- للفصل Functions من كتاب The JavaScript Language

اقرأ أيضًا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن