دليل تعلم جافاسكربت تعابير الدوال والدوال السهمية


سجى الحاج

الدالة ليست بنية سحرية تُبنَى بها الشيفرة في JavaScript، وإنما هي نوع خاص من القيم. يُطلق على الصياغة التي تُستخدم في بناء الدوال «التصريح عن دالة» (Function Declaration):

function sayHi() {
  alert( "مرحبًا" );
}

هناك صياغة أخرى لبناء دالة تسمى «تعبير دالة» (function expression) وتبدو بالشكل التالي:

let sayHi = function() {
  alert( "مرحبًا" );
};

تُعامل الدالة في هذه الصياغة مثل أي قيمة، إذ تُنشَأ ثم تُسنَد للمتغير sayHi كما توضح الشيفرة. بغض النظر عن الصياغة التي تُعرَّف بها الدالة، فهي مُجرَّد قيمة مُخزَّنة في المتغير sayHi. المراد من هذه الشيفرة هو نفسه في الشيفرة التي تسبقها: «إنشاء دالة ووضعها في المتغير sayHi».

يمكن عرض الدالة ككل باستخدام alert دون الأقواس:

function sayHi() {
  alert( "Hello" );
}
alert( sayHi ); // إظهار شيفرة الدالة

لاحظ أنَّ السطر الأخير في الشيفرة لا يستدعي الدالة لتنفيذها، وذلك بسبب عدم وجود الأقواس بعد اسمها sayHi. العديد من لغات البرمجة لا تتأثر بذلك (أي لا تهتم بوجود هذه الأقواس)، ولكن JavaScript ليست منها.

الدالة في JavaScript عبارة عن قيمة، لذا يمكن التعامل معها على أنها قيمة. يُظهِر الاستدعاء alert( sayHi );‎ سلسلة نصية تمثِّل شيفرة الدالة المُعرَّفة بأكملها، أي الشيفرة المصدرية للدالة.

الدالة عبارة عن قيمة خاصة، وأحد الأمور الخاصَّة التي تنفرد بها عن القيم العادية هي إمكانية استدعائها وتنفيذها وذلك بوضع الأقواس بعد اسمها بالشكل sayHi()‎ في أي مكان داخل الشيفرة. على أي حال، الدالة تبقى قيمة ولا مشكلة في معاملتها مثل أنواع القيم الأخرى. فيمكنك مثلًا إسناد دالة إلى متغير آخر:

function sayHi() {   // (1) إنشاء
  alert( "مرحبًا" );
}
let func = sayHi;    // (2) نسخ
func(); // مرحبًا‎     // (3) تنفيذ النسخة المنسوخة عن الدالة
sayHi(); // مرحبًا‎    //     !وهذا يعمل أيضًا ولمَ لا

إليك تفصيلٌ لما تنفِّذه الشيفرة السابقة:

  1. ينشئ التصريح عن الدالة في السطر (1)الدالة ثمَّ يضعها في المتغير sayHi.
  2. تُنسَخ الدالة في السطر (2) إلى المتغير func. لاحظ هنا عدم وجود أقواس استدعاء الدالة بعد sayHi. إذا أُضيفَت هذه الأقواس فإنَّ الاستدعاء func = sayHi()‎ سوف يُسنِد الناتج الذي تعيده الدالة إلى المتغير func وليس الدالة sayHi نفسها.
  3. يمكنك الآن استدعاء الدالة عبر استدعاء sayHi()‎ أو func()‎.

يمكنك أيضًا استخدم تعبير الدالة للتصريح عن الدالة sayHi في السطر الأول:

let sayHi = function() { ... };
let func = sayHi;
// ...

كلا التعبيرين يعطيان النتيجة نفسها.

ملاحظة حول وجود الفاصلة المنقوطة في نهاية تعبير الدالة

قد تتساءل عن سبب وجود الفاصلة المنقوطة في نهاية «تعبير الدالة»، وعدم وجودها في نهاية «تعريف الدالة»:

function sayHi() {
  // ...
}
let sayHi = function() {
  // ...
};

الجواب بسيط:

  • ليس هناك حاجة للفاصلة المنقوطة ; في نهاية كتل التعليمات البرمجية والصِيغ التي تستخدمها مثل if { ... }‎، و function f { }‎، و for { }‎ …إلخ.
  • يُعدُّ تعبير الدالة، let sayHi = ...;‎، تعليمة برمجية (statement) ويُستخدَم على أنَّه قيمة أي هو ليس كتلة برمجية (code block) ولكنه عملية إسنادٍ لقيمة. تكتب الفاصلة المنقوطة ; في نهاية التعليمات البرمجية، بغض النظر عن ماهية هذه التعليمات؛ لذلك، لا ترتبط الفاصلة المنقوطة بتعبير الدالة نفسه إطلاقًا، وإنها فقط تُنهِي التعليمة البرمجية.

دوال رد النداء

إليك مزيدًا من الأمثلة حول تمرير الدوال على أنَّها قيم واستخدام تعابير الدوال.

سنكتب الدالة ask(question, yes, no)‎ التي تقبل تمرير ثلاثة معاملات إليها:

  • المعامل question: يمثِّل نص السؤال.
  • المعامل yes: يُمثِّل دالة يراد تنفيذها إذا كانت إجابة المعامل question هي yes.
  • المعامل no: يمثِّل دالة يراد تنفيذها إذا كانت إجابة المعامل question هي no.

تطرح الدالة السؤال question، وبناءً على إجابة المستخدم، تُستدعى الدالة yes()‎ أو الدالة no()‎:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
function showOk() {
  alert( "لقد وافقت!" );
}
function showCancel() {
  alert( "لقد ألغيت التنفيذ." );
}
// ask على أنها وسائط إلى الدالة showOk و showCancel تمرر الدالتين
ask("Do you agree?", showOk, showCancel);

مثل هذه الدوال مفيدةٌ للغاية في الحياة العملية. ويتمثَّل الاختلاف الرئيسي بين التنفيذ الواقعي العملي والمثال النظري أعلاه في أنَّ الأولى تستخدم طرائقًا أكثر تعقيدًا للتفاعل مع المستخدم من مجرد دالة confirm بسيطة. تُظهِر مثل هذه الدالة نافذة أسئلة مُعدَّلة جميلة المظهر في المتصفح لكن هذا موضوع آخر متقدم.

يسمى الوسيطين showOk و showCancel للدالة ask «بدوال ردود النداء» (callback functions) أو «ردود النداء» (callbacks) فقط.

الفكرة القابعة خلف «رد النداء» هي تمرير دالة ثم توقُّع إعادة استدعائها لاحقًا إذا لزم الأمر. أي مثلها كَمَثِل ارتداد صدى الصوت وعودته للمنادي أو مناداة أحدهم بطلبِ تنفيذ أمرٍ وتوقع إجابة النداء (رد النداء) بنتيجة الفعل وغيرها من الأمثلة المستقاة من الواقع.

في المثال السابق، تصبح الدالة showOk رد نداء للإجابة "نعم" (yes)، والدالة showCancel رد نداء للإجابة "لا" (no). يمكن استخدام تعبير دالة لكتابة الدالة نفسها بشكل أقصر:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
ask(
  "هل تقبل؟",
  function() { alert("لقد قبلت."); },
  function() { alert("لقد ألغيت التنفيذ."); }
);

تُعرَّف الدوال هنا مباشرةً داخل الدالة ask(...)‎ ولا تملك اسمًا في هذه الحالة، لذا يُطلَّق عليها «دوال مجهولة» (anonymous). لا يمكن الوصول إلى مثل هذه الدوال من خارج الدالة ask(...)‎ (لعدم إسنادها إلى متغيرات) ولا يهمنا هذا الأمر هنا.

ملاحظة: الدالة عبارة عن قيمة تمثل «إجراء»

القيم العادية مثل السلاسل النصية أو الأعداد تمثِّل البيانات ولكن يمكن اعتبار الدالة -بما أنها قيمة خاصة- على أنَّها إجراء، لذا يمكنك تمريرها بين المتغيرات وتنفيذها عند الحاجة.

تعبير الدوال مقابل التصريح عن الدوال

يسلط هذا القسم الضوء على الاختلافات الرئيسية بين تعبير الدوال (Function Expressions) والتصريح عن الدوال (Function Declarations).

أولًا، الصياغة: كيف نفرِّق بينهما في الشيفرة.

  • التصريح عن دالة: يُصرَّح عن الدالة في سياق الشيفرة الرئيسية بشكل منفصل عن بقية التعليمات البرمجية.
// التصريح عن دالة
function sum(a, b) {
  return a + b;
}
  • تعبير دالة: تُنشَأ الدالة داخل تعبير برمجي (expression) أو داخل صياغة بانية أخرى. هنا، أُنشئَت الدالة في الجانب الأيمن من «تعبير الإسناد» (الإشارة =):
// تعبير دالة
let sum = function(a, b) {
  return a + b;
};

يظهر الفرق جليًّا عندما يُنشِئ محرك JavaScript كلتا الدالتين.

يُنشَأ تعبير الدالة عندما يصل تنفيذ الشيفرة إليه ويصبح جاهزًا للاستخدام من تلك اللحظة فقط.

بمجرد أن ينتقل تنفيذ الشيفرة إلى الجانب الأيمن من معامل الإسناد، let sum = function…‎، تُنشَأ الدالة وتستطيع استخدامها بدءًا من تلك اللحظة (إسناد، استدعاء، …إلخ).

التصريح عند الدوال مختلف بعض الشيء.

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

ذلك الأمر عائدٌ لخوارزميات داخلية تنفِّذها JavaScript قبل تنفيذ الشيفرة. فعندما تستعد JavaScript لتنفيذ سكربت ما، فإنَّها تبحث أولًا عن الدوال المصرَّح عنها في النطاق العام فيه وتنشئها. الأمر مشابه «لمرحلة التهيئة» (initialization stage). وبعد أن تعالج جميع تلك الدوال، تبدأ عملية تنفيذ الشيفرة، لذلك يمكن الوصول إليها من أي جزء من السكربت وتنفيذها. إليك المثال التالي:

sayHi("جعفر"); // ‎مرحبًا، جعفر
function sayHi(name) {
  alert( `مرحبًا، ${name}` );
}

تُنشَأ الدالة sayHi المُصرَّح عنها في المجال العام عندما تستعد JavaScript لتنفيذ السكربت، وتصبح مرئية في كامل أرجائه. لاحظ هنا أن السكربت لن يعمل كما سبق في حال استخدمت تعبير دالة بدلًا من عملية التصريح:

sayHi("جعفر"); // ‎!خطأ
let sayHi = function(name) {  // (*) ;-) بَطُل مفعول السحر هنا 
  alert( `Hello, ${name}` );
};

تُنشَأ الدالة هنا عند وصول التنفيذ إليها، يحدث ذلك في السطر (*)، لتعريفها بتعبيرٍ ويكون ذلك أي بعد فوات الأوان.

ميزة أخرى خاصة بعملية التصريح عن الدوال هي نطاق الكتلة الخاصة بها (block scope). فإن طُبِّق الوضع الصارم (strict mode) في السكربت، تُرَى الدالة المُعرَّفة داخل كتلة برمجية ضمنها فقط ولا يمكن الوصول إليها من خارجها.

على سبيل المثال، لنفترض أننا نريد تعريف الدالة welcome()‎ بناءً على قيمة المتغير age التي نحصل عليها أثناء تنفيذ السكربت من المستخدم. نلاحظ أن التنفيذ لن يسير على ما هو مخطط له إن صرحنا عن الدالة بالشكل التالي:

let age = prompt("كم عمرك؟‎", 18);
// welcome التصريح الشرطي عن الدالة 
if (age < 18) {
  function welcome() {
    alert("مرحبًا!");
  }
} else {
  function welcome() {
    alert("السلام عليكم!");
  }
}
// ثم استعمالها لاحقًا
welcome(); // غير معرّفة welcome خطأ: الدالة

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

let age = 16; // لنعتمد العمر 16 مثلًا
if (age < 18) {
  welcome();              // \   (تنفيذ)
                          //  |
  function welcome() {    //  |
    alert("مرحبًا!");         //‎  |  تعريف الدالة موجود داخل النطاق حيث استدعيت
  }                       //  |  
                          //  |
  welcome();              // /   (تنفيذ)
} else {
  function welcome() {    // لمَّا كان العمر 16، لن تُنشَأ هذه الدالة
    alert("السلام عليكم!");
  }
}
// لم يعد هنالك نطاق (مجال) لأي كتلة هنا لعدم وجود أقواس معقوصة
// لذا، لن نستطيع رؤية الدالتين المنشأتين داخل الكتلتين السابقتين
welcome(); // غير معرّفة welcome خطأ: الدالة

ما الذي يمكن فعله لجعل الدالة welcome مرئية خارج نطاق الكتلة if الشرطية؟ تتمثَّل الطريقة الصحيحة في استخدام تعبير الدوال وذلك بإسناد تنفيذ الدالة welcome لمتغير يُعرَّف في النطاق العام لتصبح مرئية خارج الشرط if. لاحظ أنَّ الشيفرة تعمل على النحو المطلوب بهذه الطريقة:

let age = prompt("كم عمرك؟‎", 18);
let welcome;
if (age < 18) {
  welcome = function() {
    alert("مرحبًا!");
  };
} else {
  welcome = function() {
    alert("السلام عليكم!");
  };
}
welcome(); // تمام، لا يوجد أي خطأ

بإمكانك ببساطة استخدام معامل علامة الاستفهام ? بالشكل التالي:

let age = prompt("كم عمرك؟‎", 18);
let welcome = (age < 18) ?
  function() { alert("مرحبًا!"); } :
  function() { alert("السلام عليكم!"); };
welcome(); // تمام، لا يوجد أي خطأ

متى عليك التصريح عن الدالة ومتى تستعمل تعبير الدالة؟

تمنح عملية التصريح عن الدوال حريةً أكبر في تنظيم الشيفرة، لتوفير إمكانية استدعاء هذه الدوال قبل تعريفها وهذه أهم نقطة تُؤخذ في الحسبان عند المفاضلة بين التصريح والتعبير. أضف إلى ذلك أنَّ صياغة التصريح تُسهِّل قراءة الشيفرة أيضًا، إذ من الأسهل البحث عن function f(…) {…}‎ في الشيفرة بدلًا من let f = function(…) {…}‎. فالتصريح ملفت للنظر أكثر من التعبير.

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

الدوال السهمية

هناك صياغة بسيطة وموجزة لإنشاء الدوال تسمى «الدوال السهمية» (Arrow functions)، وغالبًا ما تكون أفضل من تعبير الدوال. سُمي هذا النوع من الدوال بالدوال السهمية لأنها تشبه السهم ببساطة، وإليك صياغتها:

let func = (arg1, arg2, ...argN) => expression

يُنشِئ هذا دالة func تملك الوسائط arg1..argN، وتُقيِّم التعبير expression على الجانب الأيمن ثم تُرجِع قيمته.

لاحظ أنَّ السطر السابق يقابل الشيفرة التالية بالضبط:

let func = function(arg1, arg2, ...argN) {
  return expression;
};

ولكنه أكثر إيجازًا. إليك مثال آخر:

let sum = (a, b) => a + b;
/* صياغة الدالة السهمية أصغر من الصياغة العادية التالية
let sum = function(a, b) {
  return a + b;
};
*/
alert( sum(1, 2) ); // 3

يمكن حذف الأقواس في حال كان لديك وسيط واحد فقط:

// الصياغتان التاليتان متماثلتان
// let double = function(n) { return n * 2 }
let double = n => n * 2;
alert( double(3) ); // 6

إذا لم يكن هناك أي وسيط، توضع أقواس فارغة بالشكل التالي:

let sayHi = () => alert("مرحبًا!");
sayHi();

يمكن استخدام الدوال السهمية كما يُستخدَم تعبير الدوال. فإليك مثال الدالة welcome()‎ السابق باستعمال الدوال السهمية:

let age = prompt("كم عمرك؟‎", 18);
let welcome = (age < 18) ?
  () => alert('مرحبًا!') :
  () => alert("السلام عليكم!");
welcome(); // تمام، الدالة تعمل

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

الدوال السهمية مناسبة جدًا لكتابة إجراءات بسيطة بسطر واحد فقط وستحبها كثيرًا إن كنت كسولًا (ميزة الكسل في المبرمج إيجابية جدًا :-D ) وتستثقل كتابة كلمات كثيرة.

ملاحظة: الدوال السهمية متعددة الأسطر

الأمثلة أعلاه أخذت الوسائط الموجودة على يسار المعامل <=، ومررتها إلى التعبير expression الموجود على جانبها الأيمن لتقييمه.

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

let sum = (a, b) => {  // يفتتح القوس المعقوص دالة متعددة الأسطر
  let result = a + b;
  return result; // للحصول على نتائج return إن استعملت الأقواس المعقوصة، فاستعمل
};
alert( sum(1, 2) ); // 3

ملاحظة: ما زال هنالك المزيد!

تناول هذا الدرس الدوال السهمية بإيجاز، لكن هذا ليس كل شيء! الدوال السهمية لها ميزات أخرى مثيرة للاهتمام، لذا سنعود إليها لاحقًا في الفصل «زيارة الدوال السهمية مجدَّدًا». يمكننا في الوقت الحالي استخدامها مع إجراءات السطر الواحد وردود النداء.

الخلاصة

  • الدالة عبارة عن قيمة يمكن إسنادها أو نسخها أو تعريفها في أي مكان في الشيفرة.
  • إن صُرِّح عن دالة في تعليمة برمجية (statement) منفصلة في سياق الشيفرة الرئيسية (النطاق العام)، فذلك يدعى «التصريح عن دالة».
  • إن أُنشئت دالة في تعبير برمجي (expression)، فذلك يدعى «تعبير دالة».
  • تعالج الدوال المُصرَّح عنها قبل تنفيذ الكتلة البرمجية (السكربت) الحاوية لها، وتصبح - نتيجةً لذاك - مرئيةً في أي مكان داخل الكتلة.
  • تُنشَأ الدالة المُعرَّفة بوساطة تعبير عندما يحين دورها في التنفيذ بحسب مكان وجودها في السكربت.

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

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

أخيرًا، وجدنا أن الدوال السهمية مفيدةٌ جدًا خصوصًا في كتابة إجراءات بسطر واحد، وتأتي بشكلين:

  1. بدون أقواس معقوصة: ‎(...args) => expression، الجانب الأيمن عبارة عن تعبير، إذ تقيِّمه وترجع الناتج.
  2. مع أقواس معقوصة: ‎(...args) => { body }‎، تساعدك الأقواس في كتابة تعليمات برمجية متعددة داخل الدالة الواحدة، لكننا بحاجة إلى الموجه return لإرجاع شيء ما.

تمارين

اكتب الشيفرة التالية مجدَّدًا باستعمال الدوال السهمية

ضع دوالًا سهمية مقابلة لتعابير الدوال الموجودة في الشيفرة التالية:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
ask(
  "هل تقبل؟",
  function() { alert("لقد قبلت."); },
  function() { alert("لقد ألغيت التنفيذ."); }
);

الحل

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
ask(
  "هل قبلت؟",
  () => alert("لقد قبلت."),
  () => alert("لقد ألغيت التنفيذ.")
);

تبدو الشيفرة واضحة وقصيرة، أليس كذلك؟

ترجمة -وبتصرف- للفصل Function expressions والفصل Arrow functions, the basics من كتاب The JavaScript Language

اقرأ أيضًا





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


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



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

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

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


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

تسجيل الدخول

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


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