٢٠ مارس ٢٠٢١

الدوال

أحيان كثيرة نريد تنفيذ نفس الأوامر في أماكن مختلفة من البرنامج.

مثلًا نريد إظهار رسالة عندما يقوم المستخدم بتسجيل الدخول أو الخروج أو أماكن أخرى.

الدوال هي وحدة البناء الأساسية لأي برنامج فهي تمكننا من تنفيذ نفس الأوامر في عدة أماكن دون تكرار.

لقد رأينا من قبل العديد من الدوال المدمجة في اللغة مثل alert(message), prompt(message, default) و confirm(question). ولكن يمكننا إنشاء الدوال الخاصة بنا.

Function Declaration

لإنشاء دالة نستخدم function declaration.

مثل هذا:

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

كلمة function تكتب أولا ثم يكتب اسم الدالة ثم قائمة parameters بين القوسين (يفصل بينهم بفاصلة وهي فارغة في المثال السابق) وأخيرا الكود الذي ينفذ ويسمى “the function body” بين القوسين المعقوفين.

function name(parameters) {
  ...body...
}

يمكننا استدعاء دالتنا الجديدة عن طريق اسمها: showMessage().

For instance:

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

showMessage();
showMessage();

استدعاء showMessage() يقوم بتنفيذ الكود وتظهر لنا الرسالة مرتين.

هذا المثال يوضح الهدف الأساسي للدوال وهو تجنب تكرار الكود.

إذا أردنا تغيير الرسالة التي تعرض سيكون كافيًا تغيير الكود في مكان واحد فقط وهو الدالة التي تعرض لرسالة.

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

المتغير الذي يعرف داخل دالة يكون متاح داخل هذه الدالة فقط

مثلًا:

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // local variable

  alert( message );
}

showMessage(); // Hello, I'm JavaScript!

alert( message ); // <-- خطأ! المتغير متاح فقط داخل الدالة

المتغيرات الخارجية

يمكن للدالة الوصول للمتغيرات خارجها مثل:

let userName = 'John';

function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}

showMessage(); // Hello, John

الدالة لديها وصول كامل للمتغيرات خارجها ويمكنها أيضًا التعديل عليهم.

مثلًا:

let userName = 'John';

function showMessage() {
  userName = "Bob"; // (1) تغيير قيمة المتغير الخارجي

  let message = 'Hello, ' + userName;
  alert(message);
}

alert( userName ); // John قبل استدعاء الدالة

showMessage();

alert( userName ); // Bob تم تغيير المتغير بواسطة الدالة

يتم استخدام المتغير الخارجي فقط إذا لم يوجد متغير محلي.

إذا تم عمل متغير محلي بنفس الاسم فيتم استخدامه بدلًا من الخارجي فعلى سبيل المثال الدالة التالية ستستخدم المتغير المحلي userName وتتجاهل المتغير الخارجي:

let userName = 'John';

function showMessage() {
  let userName = "Bob"; // تعريف متغير محلي

  let message = 'Hello, ' + userName; // Bob
  alert(message);
}

// ستقوم الدالة بعمل واستخدام userName الخاص بها
showMessage();

alert( userName ); // John, لم يتغير, الدالة لن تصل للمتغير الخارجي
المتغيرات العالمية

المتغيرات التي تعرف خارج أي دالة مثل المتغير الخارجي userName في المثال السابق تسمى global.

المتغيرات العالمية يمكن استخدامها بواسطة أي دالة (إذا لم يتم تعرف متغير محلي بنفس الاسم).

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

Parameters

يمكننا تمرير أي قيم إلى الدالة باستخدام parameters (أيضًا تسمى function arguments) .

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

function showMessage(from, text) { // arguments: from, text
  alert(from + ': ' + text);
}

showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)

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

هنا مثال آخر حيث لدينا المتغير from وقمنا بتمريره إلى الدالة. لاحظ أن الدالة قامت بتغيير قيمة from ولكن التغيير لا يؤثر في المتغير الممرر لأن الدالة تحصل على نسخة من القيمة:

function showMessage(from, text) {

  from = '*' + from + '*'; // make "from" look nicer

  alert( from + ': ' + text );
}

let from = "Ann";

showMessage(from, "Hello"); // *Ann*: Hello

// قيمة "from" تظل كما هي لأن الدالة قامت بالتعديل على متغيرها المحلي
alert( from ); // Ann

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

إذا لم يتم تمرير قيمة Parameter يأخذ القيمة undefined.

على سبيل المثال الفنكشن السابقة showMessage(from, text) يمكن استدعائها وتمرير قيمة واحدة فقط:

showMessage("Ann");

هذا ليس خطأ ولكن سينتج "Ann: undefined". لم يتم تمرير text لذلك يتم افتراض أن text === undefined.

إذا أردت تخصيص قيمة إفتراضية ل text يمكن وضعها بعد =:

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

showMessage("Ann"); // Ann: no text given

إذا لم يتم تمرير قيمة text سيتم إعطائه القيمة "no text given"

هنا استخدمنا النص "no text given" ولكن يمكن أن تكون القيمة معقدة أكثر من:

function showMessage(from, text = anotherFunction()) {
    // anotherFunction() سيتم تنفيذها فقط إذا لم يحدد قيمة
    // وناتجها سيوضع كقيمة للمتغير text
}
تنفيذ القيم الإفتراضية

في جافا سكريبت يتم تنفيذ القيم الإفتراضية في كل مرة يتم استدعاء الدالة دون تمرير قيمة.

في المثال السابق سيتم تنفيذ anotherFunction() في كل مرة يتم استدعا showMessage() دون تمرير قيمة text.

بديل القيم الإفتراضية

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

لمعرفة المتغير الذي لم يمرر قيمته يمكننا مقارنته مع undefined:

function showMessage(text) {
  if (text === undefined) {
    text = 'empty message';
  }

  alert(text);
}

showMessage(); // empty message

…أو نستخدم العامل ||:

// إذا لم يتم تمرير قيمة text أو تم تمرير "" يجعل قيمته 'empty'
function showMessage(text) {
  text = text || 'empty';
  ...
}

محركات جافا سكريبت الحديثة تدعم nullish coalescing operator ??وهوأفضل في التعامل مع falsy values مثل 0:

// إذا لم يوجد قيمة "count" يعرض "unknown"
function showCount(count) {
    alert(count ?? "unknown");
}

showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

إرجاع قيمة

يمكن للدالة إرجاع قيمة كناتج لها.

أبسط مثال هو دالة تقوم بجمع رقمين:

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('Do you have permission from your parents?');
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}

يمكن استخدام return بدون قيمة عندما نريد إيقاف الدالة في الحال.

مثلًا:

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

في المثال بالأعلى إذا كانت checkAge(age) ترجع false عندها showMovie لن تكمل إلى alert.

``smart header=“الدالة التي لديها return فارغة أو ليس لديها ترجع undefined” إذا كانت الدالة لا ترجع قيمة فكأنها ترجع undefined:

function doNothing() {
    /* empty */
}

alert(doNothing() === undefined); // true

استخدام return فارغة هو أيضًا مثل return undefined:

function doNothing() {
    return;
}

alert(doNothing() === undefined); // true
````warn header="لا تضع سطر جديد بين `return` والقيمة"
إذا اردنا استخدام تعبير طويل مع `return` سيكون من الأفضل وضعه في سطر جديد:

```js
return
 (some + long + expression + or + whatever * f(a) + f(b))
```
هذا لن يعمل لأن جافا سكريبت ستفترض وجود فاصلة منقوطة بعد `return`. وهذا يطابق:

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

وبهذا سيصبح return فارغة.

إذا أردنا استخدام تعبير طويل في سطر جديد يجب وضع بدايته في نفس السطر مع `return`. أو نضعه بين قوسين كالآتي:

```js
return (
  some + long + expression
  + or +
  whatever * f(a) + f(b)
  )
```
وسيعمل كما هو متوقع.

تسمية الدالة

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

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

على سبيل المثال فالدوال التي تبدأ بكلمة "show" فهي في الغالب تعرض شئ معين.

Function starting with…

  • "get…" – ترجع قيمة,
  • "calc…" – تحسب شئ ما,
  • "create…" – تنشئ شئ ما,
  • "check…" – تختبر قيمة ما وترجع قيمة منطقية.

أمثلة:

showMessage(..)     // shows a message
getAge(..)          // returns the age (gets it somehow)
calcSum(..)         // calculates a sum and returns the result
createForm(..)      // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false

استخدام هذه الكلمات يعطي فكرة عن ما تفعله الدالة وما ينتج عنها.

دالة واحدة – فعل واحد

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

الفعلين الغير معتمدين على بعضهما يتم وضع كل منهما في دالة منفصلة حتى لو سيتم استدعائها مع بعضهما (في هذه الحالة يمكن عمل دالة ثالثة تستدعيهما).

أمثلة:

  • getAge – لن تكون جيدة إذا كانت تعرض alert به العمر (فقط نحصل على القيمة منها).
  • createForm – لن تكون جيدة إذا كانت تقوم بتعديل الصفحة وإضافة الاستمارة لها (فقط تنشئها وترجعها).
  • checkPermission – لن تكون جيدة إذا كانت تعرض رسالة access granted/denied message (فقط تفحص وترجع الناتج).

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

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

الدوال التي تستخدم بكثرة غالبًا يتم إعطائها اسم قصير.

على سبيل المثال مكتبة 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); // a prime
    }
}

الثانية تستخدم دالة منفصلة اسمها 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). بعض الناس يسمون هذا الكود self-describing.

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

الخلاصة

تعريف الدالة يكون كالتالي:

function name(parameters, delimited, by, comma) {
    /* code */
}
  • القيم الممرة إلى الدالة يتم نسخها إلى متغيراتها المحلية.
  • يمكن للدالة الوصول للمتغيرات خارجها ولكن لا يمكن للكود بالخارج أن يصل إلى المتغيرات المحلية داخل دالة.
  • يمكن للدالة إرجاع قيمة وإذا لم تفعل فيكون ناتجها undefined.

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

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

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

  • يجب أن يكون اسم الدالةواضح ويعبر عن عملها بحيث عندما نرى استدعاء الدالة نعرف ما تقوم به وما نتيجتها.
  • الدالة هي فعل لذا ففي الغالب اسمها يكون فعل.
  • هناك كلمات تستخدم قبل اسم الدالة create…, show…, get…, check… وغيرها لتعطي تلميح عن ما تفعل الدالة.

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

مهمه

الدالة التالية ترجع true إذا كانت قيمة age أكبر من 18.

وإلا فهي تطلب تأكيد وترجع نتيجته:

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

هل سيحدث اختلاف إذا حذفنا else ?

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

هل هناك أي اختلاف في سلوك الدالتين ؟

لا اختلاف.

الدالة التالية ترجع true إذا كانت قيمة age أكبر من 18.

وإلا فهي تطلب تأكيد وترجع نتيجته:

function checkAge(age) {
    if (age > 18) {
        return true;
    } else {
        return confirm("Did parents allow you?");
    }
}

اعد كتابتها للحصول على نفس النتيجة ولكن بدون if وفي سطر واحد.

اعد كتابة checkAge:

  1. باستخدام عامل علامة الاستفهام ?
  2. باستخدام OR ||

استخدام عامل علامة الاستفهام '?':

function checkAge(age) {
    return age > 18 ? true : confirm("Did parents allow you?");
}

Using OR || (the shortest variant):

function checkAge(age) {
    return age > 18 || confirm("Did parents allow you?");
}

لاحظ أن الأقواس حول age > 18 غير مطلوبة ولكن تم وضعها لزيادة القدرة على قراءة الكود.

اكتب دالة 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) التي ترجع x مرفوعة لأس n. أو بكلمات أخرى, تضرب x في نفسها عدد n من المرات وترجع الناتج.

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

انشئ صفحة ويب تطلب من المستخدم قيم 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(`Power ${n} is not supported, use a positive integer`);
} else {
  alert( pow(x, n) );
}
خريطة الدورة التعليمية