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

الدوال وإعادة استخدام الشيفرة في جافا سكريبت


ابراهيم الخضور

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

ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على:

  1. أساسيات علوم الحاسب.
  2. أساسيات HTML.
  3. أساسيات عمل CSS
  4. أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة.

أين تجد الدوال؟

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

في كل مرة تستخدم بنية في جافا سكربت يليها قوسين () (باستثناء البنى الأصلية في اللغة مثل حلقة for أو حلقة while أو for...else) فأنت تستخدم دالة.

الدوال اﻷصلية المدمجة في المتصفح

استخدمنا سابقًا دوال كثيرة جاهزة في المتصفح، وذلك في كل مرة تعاملنا فيها مع السلاسل النصية:

const myText = "I am a string";
const newString = myText.replace("string", "sausage");
console.log(newString);
// سلسلة نصية مصدرية replace() تأخذ الدالة
// وأخرى هدف وتستبدل السلسلة المصدرية  
//بالسلسلة الهدف وتعيد السلسلة النصية الجديدة

أو في كل مرة تعاملنا فيها مع مصفوفات:

const myArray = ["I", "love", "chocolate", "frogs"];
const madeAString = myArray.join(" ");
console.log(madeAString);
// مصفوفة وتضم جميع عناصرها join() تأخذ الدالة
// في سلسلة نصية جديدة وتعيد هذه السلسلة

أو عندما ولدنا أعدادًا عشوائية:

const myNumber = Math.random();
// عددًا عشوائيًا بين random() تولد الدالة
//الرقم 0 و الرقم 1 (ما عدا 1)

ملاحظة: جرّب الشيفرة السابقة في متصفحك (في طرفية جافا سكريبت) كي تتعود على استخدامها إن اقتضى اﻷمر.

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

وتذكر دائمًا أن الكثير من الدوال المدمجة مع المتصفح ليست جزءًا من نواة جافا سكريبت. فبعضها معرّف كأجزاء من الواجهة البرمجية للمتصفح مبنية على لغات أخرى لتأمين مستوى معين من الوظائف.

الدوال والتوابع

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

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

كما رأيت أيضًا في مقالاتنا السابق العديد من الدوال المخصصة، وهي دوال عرفناها ضمن الشيفرة وليست مدمجة مع المتصفح. فعندما ترى اسمًا مخصصًا يليه قوسين ستكون أمام دالة مخصصة. وكمثال عليها تجد الدالة ()draw المخصصة التي استخدمناها ضمن الملف random-canvas-circles.html (انظر الشيفرة المصدرية) في مقال استخدام الحلقات في جافا سكريبت والذي يبدو كالتالي:

function draw() {
  ctx.clearRect(0, 0, WIDTH, HEIGHT);
  for (let i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = "rgba(255,0,0,0.5)";
    ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
    ctx.fill();
  }
}

ترسم الدالة 100 دائرة عشوائية ضمن العنصر <canvas>، وفي كل مرة نريد تكرار اﻷمر، نستدعي هذه الدالة كالتالي:

draw();

بدلًا من إعادة كتابة الشيفرة من جديد كل مرة. ويمكن أن تضم الدوال أية شيفرة تريدها كما يمكنها استدعاء أي دالة أخرى، فالدالة السابقة تستدعي الدالة ()random المعرّفة كالتالي ثلاث مرات:

function random(number) {
  return Math.floor(Math.random() * number);
}

لقد احتجنا الدالة السابقة لأن الدالة اﻷصلية المدمجة ()Math.random مع المتصفح تولد أرقامًا عشوائية عشرية بين 0 و 1 فقط، لكننا نريد رقمًا عشوائيًا صحيحًا بين 0 وقيمة معينة.

استدعاء الدالة

قد يكون مفهوم الدالة واضحًا بالنسبة لك، لكن نذكرك أن استخدام الدالة فعليًا يكون من خلال استدعائها. ويُنفّذ اﻷمر بكتابة اسم الدالة في المكان الذي تريده ضمن الشيفرة يليه قوسين:

function myFunction() {
  alert("hello");
}

myFunction();
// يستدعي الدالة مرة واحدة

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

معاملات الدالة

تحتاج بعض الدوال إلى معاملات parameters عند استدعائها، وهي قيم ينبغي وضعها ضمن قوسي الدالة كي تعمل الدالة بالشكل المطلوب.

ملاحظة: تُدعى المعاملات أحيانًا "وسائطًا arguments" أو "خاصيات properties" أو "سمات attributes".

وكمثال على ذلك، نجد دالة المتصفح ()Math.random التي لا تأخذ أية معاملات وتعيد دومًا عددًا عشوائيًا بين 0 و1:

const myNumber = Math.random();

بينما تأخذ الدالة ()replace معاملين هما النص الذي تريد إيجاده ضمن السلسلة الرئيسية والنص البديل:

const myText = "I am a string";
const newString = myText.replace("string", "sausage");

ملاحظة: يُفصل بين المعاملات ما بين القوسين بفواصل من الشكل ,.

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

قد تكون المعاملات اختيارية في بعض اﻷحيان ولا حاجة لوضع قيم لها. فإن لم تفعل ذلك تتبنى الدالة نوعًا من القيم الافتراضية. وكمثال عن هذه الدوال نجد دالة المصفوفات ()join:

const myArray = ["I", "love", "chocolate", "frogs"];
const madeAString = myArray.join(" ");
console.log(madeAString);
//'I love chocolate frogs' تعيد الدالة

const madeAnotherString = myArray.join();
console.log(madeAnotherString);
//'I,love,chocolate,frogs'تعيد الدالة 

فإن لم تخصص في الدالة المعامل الذي يمثل محرف الفصل أو الوصل، تستخدم الدالة الفاصلة افتراضيًا.

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

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

function hello(name = "Chris") {
  console.log(`Hello ${name}!`);
}

hello("Ari"); // Hello Ari!
hello(); // Hello Chris!

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

ما تعلمناه حتى اﻵن هو دالة من الشكل:

function myFunction() {
  alert("hello");
}

لكن بإمكانك أيضًا إنشاء دالة بلا اسم:

(function () {
  alert("hello");
});

تُدعى هذه الدوال بالدوال غير المسماة anonymous functions. وترى هذه الدوال عادة عندما تأخذ دالة ما دالة أخرى كمعامل لها، عندها يمرر المعامل كدالة غير مسماة.

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

مثال عن الدوال غير المسماة Anonymous Functions

لنفرض أنك تريد تنفيذ شيفرة معينة عندما يطبع المستخدم بعض اﻷحرف في صندوق نصي. وﻹنجاز اﻷمر بإمكانك استدعاء الدالة ()addEventListener العائدة للصندوق النصي، والتي تتوقع أن تمرر لها على اﻷقل معاملين:

  • اسم الحدث الذي تترصده وهو في حالتنا الضغط على المفتاح keydown.
  • دالة كي تُنفَّذ عندما يقع الحدث.

عندما يضغط المستخدم على مفتاح، يستدعي المتصفح الدالة التي استخدمتها كمعامل ويمرر لها على شكل معامل أيًضًا معلومات عن الحدث بما في ذلك المفتاح الي ضغطه المستخدم:

function logKey(event) {
  console.log(`You pressed "${event.key}".`);
}

textBox.addEventListener("keydown", logKey);

وبدلًا من استخدام دالة منفصلة مثل ()logkey، بإمكانك تمرير دالة غير مسماة إلى ()addEventListener على النحو التالي:

textBox.addEventListener("keydown", function (event) {
  console.log(`You pressed "${event.key}".`);
});

الدالة السهمية Arrow functions

إن مررت دالة إلى دالة أخرى على شكل دالة غير مسماة، ستجد طريقة أخرى لذلك تُدعى الدالة السهمية <=() بدلًا من ()function:

textBox.addEventListener("keydown", (event) => {
  console.log(`You pressed "${event.key}".`);
});

فإن أخذت الدالة السهمية معاملًا واحدًا، تستطيع حينها حذف اﻷقواس المحيطة بالمعامل:

textBox.addEventListener("keydown", event => {
  console.log(`You pressed "${event.key}".`);
});

وأخيرًا إن احتوت الدالة على سطر واحد فقط يتضمن العبارة return، بإمكانك عندها حذف اﻷقواس المعقوصة للدالة والتعليمة return. لاحظ كيف استخدمنا تابع المصفوفة ()map لمضاعفة كل عدد في المصفوفة اﻷصلية:

const originals = [1, 2, 3];

const doubled = originals.map(item => item * 2);

console.log(doubled); // [2, 4, 6]

يأخذ التابع ()map كل عنصر من عناصر المصفوفة بدوره ويمرر إلى دالة محددة ثم يعيد نتيجة تنفيذ هذه الدالة ويضيفها إلى المصفوفة الجديدة. فالعبارة البرمجية item => item * 2 إذًا هو شكل مختصر للدالة السهمية ويكافئ تمامًا الدالة التصريحية التالية:

function doubleItem(item) {
  return item * 2;
}

بإمكانك استخدام اﻷسلوب السابق لإعادة كتابة الدالة addEventListener:

textBox.addEventListener("keydown", (event) =>
  console.log(`You pressed "${event.key}".`),
);

تُعاد قيمة التابع ()console.log (والتي هي قيمة غير محددة undefined) ضمنًا كنتيجة لاستدعاء الدالة.

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

مثال مباشر عن استخدام الدالة السهمية

إلك مثالنا السابق عن التقاط ضغطة مفتاح:

  • شيفرة HTML:
<input id="textBox" type="text" />
<div id="output"></div>
  • شيفرة جافا سكريبت:
const textBox = document.querySelector("#textBox");
const output = document.querySelector("#output");

textBox.addEventListener("keydown", (event) => {
  output.textContent = `You pressed "${event.key}".`;
});

إليك النتيجة:

نطاق الدوال والتعارضات عند تفسير الشيفرة

لنلق نظرة أقرب إلى موضوع مجال أو نطاق دالة scope، وهو مفهوم هام جدًا عند التعامل مع الدوال. فعندما تنشئ دالة ستكون المتغيرات وغيرها من اﻷشياء المعرّفة داخل الدالة ضمن نطاق مخصص لها، أي أنها غير مرئية ولا معروفة من قبل الشيفرة الموجودة خارج الدالة. بينما يكون كل ما يُعرّف خارج نطاق الدالة ضمن النطاق العام global scope ويمكن الوصول إليها من أي مكان في الشيفرة.

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

لنقل مثلًا أن لديك ملف HTML يستدعي ملفي جافا سكريبت خارجيين، ويضم كلا الملفين متغيرًا ودالة لهما الاسم ذاته:

  • ملف HTML:
<!-- Excerpt from my HTML -->
<script src="first.js"></script>
<script src="second.js"></script>
<script>
  greeting();
</script>
  • ملف جافا سكريبت الأول:
// first.js
const name = "Chris";
function greeting() {
  alert(`Hello ${name}: welcome to our company.`);
}
  • ملف جافا سكريبت الثاني:
// second.js
const name = "Zaptec";
function greeting() {
  alert(`Our company is called ${name}.`);
}

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

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

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

01 animal cages lime functions scope

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

تطبيق عملي: التعامل مع نطاقات الرؤية

لنلق نظرة على مثال واقعي يوضح مفهوم المجالات.

  1. احفظ نسخة من ملف التمرين على حاسوبك. ويتضمن الملف دالتين هما ()a و ()b وثلاثة متغيرات x و y و z اثنان منهما معرّفان ضمن الدالتين واﻷخير متغير عام. كما يتضمن الملف دالة ثالثة تُدعى تأخذ معاملًا واحدًا وتطبعه ضمن فقرة نصية في الصفحة.
  2. افتح التمرين ضمن المتصفح وضمن المحرر النصي.
  3. افتح طرفية جافا سكريبت ضمن أدوات مطوري ويب الخاصة بالمتصفح، ثم اكتب في الطرفية اﻷمر التالي:
output(x);

ينبغي أن ترى قيمة المتغير x قد طُبعت على الشاشة.

  1. حاول اﻵن إدخال التالي في الطرفية:
output(y);
output(z);

ينبغي أن ترمي كلا اﻷمرين خطأً (y غير معرّف y is not defined ). إن السبب في ذلك هو مجال الدالة. فكل من y و Z معرفان ضمن الدالة ()a و ()b فلا يمكن للدالة ()output الوصول إليهما من النطاق العام.

  1. لكن ما الذي سيحدث عندما نستدعيهما من داخل الدالة؟ حاول أن تغيّر الدالتين ()a و ()b كالتالي:
function a() {
  const y = 2;
  output(y);
}

function b() {
  const z = 3;
  output(z);
}

احفظ الشيفرة وأعد تحميل الصفحة ضمن المتصفح وحاول بعدها استدعاء الدالتين ()a و ()b من الطرفية:

a();
b();

من المفترض أن ترى قيمتي y و z قد طبعتا على شاشة المتصفح، وسيعمل اﻷمر طالما أن الدالة ()output قد استدعيت من داخل الدالتين ()a و ()b أي في نفس النطاق الذي عُرفت فيه المتغيرات وتبقى ()output متاحة فيأي مكان طالما أنها ضمن النطاق العام

  1. جرّب أن تغيّر الشيفرة لتصبح كالتالي:
function a() {
  const y = 2;
  output(x);
}

function b() {
  const z = 3;
  output(x);
}
  1. احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية:
a();
b();

ستطبع الدالين الدالتين ()a و ()b قيمة x في المتصفح. سيعمل اﻷمر جيدًا لأنه وعلى الرغم من أن استدعائي الدالة لا ينتميان إلى نفس مجال x لكن x معرّف كمتغير عام، فهو متاح في أي مكان من الشيفرة

  1. جرّب أخيرًا تحديث الشيفرة لتصبح كالتالي:
function a() {
  const y = 2;
  output(z);
}

function b() {
  const z = 3;
  output(y);
}
  1. احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية:
a();
b();

سيلقي اﻵن استدعاء الدالتين ()a و ()b نفس الخطأ (ReferenceError: variable name is not defined) في الطرفية لأن استدعاءات الدالة ()output والمتغيرات التي تريد طباعة قيمتها لا ينتميان إلى نفس مجال أو نطاق الرؤية للدالة فهما غير مرئيان للاستدعاءات.

ملاحظة: لا تُطبق نفس قواعد النطاق على الحلقات (مثل {}()for) والجمل الشرطية (مثل {}()if) فقد تبدوان مشابهتين للدوال لكنهما أمران مختلفان. انتبه إلى ذلك.

ملاحظة: الخطأ ReferenceError: "x" is not defined من أكثر اﻷخطاء شيوعًا. ففي حال واجهت هذا الخطأ وأنت متأكد من أنك عرّفت المتغير، عليك في هذه الحالة مراجعة مجالات الرؤية.

الخلاصة

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

ترجمة -وبتصرف- للمقال Functions- reusable bloacks of code

اقرأ أيضًا


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

أفضل التعليقات

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



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...