تخطي إلى المحتوى الرئيسي

الدوال (Functions) في TypeScript

الدوال (Functions) في TypeScript

المقدمة والنظرة الشاملة: الدوال في TypeScript

تبدأ الرحلة في عالم البرمجة بفهم اللبنات الأساسية التي تُشكل أي تطبيق، وتأتي الدوال (Functions) في صدارة هذه اللبنات. هي ليست مجرد أسطر برمجية مجمعة، بل وسيلة لتنظيم المنطق البرمجي، وعزل المهام، وتحقيق مبدأ عدم تكرار الكود (DRY)، وتوفير الأمان النوعي الصارم (Type Safety) الذي يميز لغة TypeScript.

في TypeScript، يمكن تعريف الدوال باستخدام الكلمة المفتاحية function مع تحديد أنواع المعاملات والقيم المُرجعة بدقة.

المفاهيم الأساسية وتصنيف الدوال

في TypeScript، يمكن تصنيف الدوال بناءً على طريقة استدعائها، وجود المعاملات، ونوع القيمة العائدة:

  • الدوال العادية (بدون معاملات ولا قيمة مرجعة): تنفذ أوامر إجرائية مباشرة.
  • الدوال ذات المعاملات (Parameterized Functions): تقبل مدخلات وتحدد أنواعها بدقة.
  • الدوال ذات القيم المُرجعة (Functions with Return Types): تعيد قيمة معينة محددة النوع.
// دالة عادية بدون معاملات
function greetUser(): void {
  console.log("مرحبا!");
}

// دالة ذات معامل
function greetUserWithName(name: string): void {
  console.log(`مرحبا، اسمي ${name}!`);
}

// دالة ذات معامل وقيمة مُرجعة
function greetUserWithReturnValue(name: string): string {
  return `مرحبا، اسمي ${name}`;
}

أمثلة على التعامل مع المعاملات الحسابية

function calculateArea(width: number, length: number): number {
  const area: number = width * length;
  return area;
}

function addNumbers(a: number, b: number): number {
  return a + b;
}

function multiplyNumbers(a: number, b: number): number {
  return a * b;
}

console.log(calculateArea(5, 10)); // الناتج: 50
console.log(addNumbers(3, 4));      // الناتج: 7
console.log(multiplyNumbers(3, 4)); // الناتج: 12

دوال الإغلاق والاستدعاء الذاتي (Closures)

تُعتبر دوال الإغلاق (Closures) حالة مميزة تحتفظ فيها الدالة الداخلية بالوصول إلى المتغيرات الموجودة في نطاق الدالة الخارجية حتى بعد انتهاء تنفيذها:

function outerFunction() {
  let counter = 0;
  return function innerFunction(): number {
    counter++;
    return counter;
  };
}

const closure = outerFunction();
console.log(closure()); // الناتج: 1
console.log(closure()); // الناتج: 2
console.log(closure()); // الناتج: 3
console.log(closure()); // الناتج: 4

استخدام الدوال في بناء الأكواد المتقدمة والتحقق الشرطي

function multiplyAndCheckNumber(number: number, multiplier: number, limit: number): boolean {
  const total = number * multiplier;
  return total > limit;
}

console.log(multiplyAndCheckNumber(5, 4, 15)); // الناتج: true

أفضل الممارسات والنصائح التطبيقية

لكتابة دوال احترافية وسهلة الصيانة في TypeScript، يُفضل اتباع الإرشادات التالية:

  • الوصف والتسمية الواضحة: اختيار أسماء معبرة تصف ما تفعله الدالة بدقة لتسهيل القراءة.
  • تقسيم الدوال الكبيرة: جعل كل دالة مسؤولة عن مهمة واحدة محددة (Single Responsibility Principle).
  • الحد الأدنى من المعاملات: تجنب تمرير معاملات كثيرة؛ واستبدالها بكائن يحمل الإعدادات إذا زادت عن 3 معاملات.
  • استخدام الدوال اللامتزامنة (Async/Await): عند التعامل مع طلبات الشبكة أو العمليات الطويلة لتحسين أداء البرنامج.
  • تجنب نسخ ولصق الكود: إعادة استخدام الدوال العامة لتقليل الأخطاء وتسهيل الصيانة.
  • فحص واختبار الدوال: اختبار الدوال دورياً للتأكد من سلوكها عند الحالات الحدية (Edge Cases).

مقارنة: الدوال المبنية يدوياً مقابل الدوال الجاهزة (Built-in)

يوفر نظام JavaScript و TypeScript مئات الدوال المعرفة مسبقاً (مثل دوال كائن Math)، وفهم الفارق بين إنشائها يدوياً أو استدعائها مباشرة يساعدك في اتخاذ القرار الأمثل:

// دالة مبنية يدوياً (مخصصة للتحكم بالأنواع أو إضافة شروط معينة)
function mySquareRoot(number: number): number {
  if (number < 0) {
    throw new Error("لا يمكن حساب الجذر لعدد سالب");
  }
  return Math.sqrt(number);
}

// دالة جاهزة في اللغة مباشرة
const directRoot = Math.sqrt(9);

console.log(mySquareRoot(9)); // النتيجة: 3
console.log(directRoot);       // النتيجة: 3

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

أخطاء شائعة في كتابة الدوال وحلولها

1. أخطاء التعيين النوعي غير الصحيح (Incorrect Type Annotations)

المشكلة: تمرير مدخلات بنوع مخالف للتوقيع، أو إرجاع قيمة لا تطابق النوع المحدد للدالة:

function greet(name: string): string {
    return `مرحباً، ${name}!`;
}

// خطأ ترجمة (Compile Error):
// greet(123); // Argument of type 'number' is not assignable to parameter of type 'string'.

// خطأ في القيمة العائدة:
// function subtract(a: number, b: number): number {
//     return `${a - b}`; // Type 'string' is not assignable to type 'number'.
// }

الحل: الالتزام بالتعيين الصارم للأنواع (Strict Typing)، وعند الحاجة لقبول أنواع متعددة، يُفضل استخدام أنواع الاتحاد (Union Types) أو القوالب العامة (Generics) بدلاً من اللجوء للنوع any.

دراسات حالة وأمثلة واقعية

دراسة حالة 1: التحقق من مدخل رقمي عبر التعبيرات القياسية (RegEx)

function validateUserInput(input: string): boolean {
    const numberRegex = /^[1-9][0-9]*$/;
    return numberRegex.test(input);
}

console.log(validateUserInput("10")); // true
console.log(validateUserInput("-5")); // false

دراسة حالة 2: التحقق من طول اسم المستخدم

function validateUsername(username: string): boolean {
    if (username.length < 3 || username.length > 30) {
        return false;
    }
    return true;
}

console.log(validateUsername("أحمد")); // true
console.log(validateUsername("ع"));    // false

دراسة حالة 3: حساب عمر المستخدم وسنة الميلاد مع معالجة الأخطاء

function calculateUserBirthYear(birthYear: number): number {
    const currentYear = new Date().getFullYear();
    if (birthYear < 1900 || birthYear > currentYear) {
        return NaN; // سنة غير صالحة
    }
    return birthYear;
}

console.log(calculateUserBirthYear(1980)); // 1980
console.log(calculateUserBirthYear(2100)); // NaN

دراسة حالة 4: حساب عدد الأيام بين تاريخين بدقة

function calculateDaysBetweenDates(date1: Date, date2: Date): number {
    const differenceInMilliseconds = Math.abs(date2.getTime() - date1.getTime());
    const millisecondsPerDay = 1000 * 60 * 60 * 24;
    return Math.floor(differenceInMilliseconds / millisecondsPerDay);
}

const date1 = new Date("2024-04-15");
const date2 = new Date("2024-10-01");
console.log(calculateDaysBetweenDates(date1, date2)); // الناتج: 169 يوماً

دراسة حالة 5: دوال التقريب الحسابي للأعداد العشرية

function roundToNextInteger(value: number): number {
    return Math.ceil(value);
}

console.log(roundToNextInteger(4.2)); // الناتج: 5

الأسئلة الشائعة حول الدوال في TypeScript

1. ما الفرق بين void و never كنوع إرجاع للدالة؟

النوع void يعني أن الدالة تكتمل بنجاح ولكنها لا تُرجع أي قيمة ذات معنى، بينما النوع never يعني أن تنفيذ الدالة لن يكتمل أو ينتهي أبداً؛ إما لأنها تُلقي استثناءً دائماً أو تدخل في حلقة لا نهائية:

function logMessage(message: string): void {
    console.log(message);
}

function throwError(errorMessage: string): never {
    throw new Error(errorMessage);
}

function infiniteLoop(): never {
    while (true) {
        // حلقة مستمرة بدون توقف
    }
}

2. هل يمكن استخدام معاملات اختيارية مع قيم افتراضية في نفس الدالة؟

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

function greet(name: string, greeting?: string, punctuation: string = "!"): string {
    return `${greeting || "مرحبا"} ${name}${punctuation}`;
}

console.log(greet("أحمد"));                  // مرحبا أحمد!
console.log(greet("أحمد", "صباح الخير"));     // صباح الخير أحمد!
console.log(greet("أحمد", undefined, "؟"));   // مرحبا أحمد؟

3. ما هي دوال التحميل الزائد (Function Overloads)؟

التحميل الزائد يسمح لك بتعريف عدة توقيعات (Signatures) لنفس الدالة مع تنفيذ واحد، مما يمنح المترجم القدرة على تحديد نوع النتيجة بدقة وفقاً لنوع المدخلات:

function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
    if (typeof value === "string") {
        return value.toUpperCase();
    }
    return value * 2;
}

const result1 = process("hello");  // نوع الإرجاع: string
const result2 = process(42);       // نوع الإرجاع: number

4. ما الفروقات الجوهرية بين الدوال السهمية (Arrow Functions) والدوال التقليدية؟

  • سياق this: الدوال السهمية لا تملك سياق this خاصاً بها، بل ترثه من النطاق المعجمي المحيط (Lexical Scope)، مما يحل مشكلات الربط التلقائي في الكائنات والفئات.
  • غياب كائن arguments: لا تحتوي الدوال السهمية على كائن arguments، ويتم تعويضه بمعاملات الباقي (Rest Parameters: ...args).
  • عدم استخدامها كدوال بنائية: لا يمكن استدعاء الدالة السهمية باستخدام الكلمة المفتاحية new ولا تحتوي على خاصية prototype.

الخلاصة والخطوات القادمة

تعرفنا في هذا الدرس على جوهر الدوال في TypeScript، بدءاً من التعريف الأساسي وتحديد أنواع المعاملات والقيم العائدة، إلى دوال الإغلاق، وأفضل الممارسات، ودراسات الحالة العملية، والفروقات المتقدمة بين void و never والتحميل الزائد والدوال السهمية.

في الدروس القادمة سننتقل للتعامل مع الدوال المساعدة (Helper Functions) وتطبيق القوالب والأنواع العامة (Generic Functions) لزيادة مرونة الكود وقابليته للتطوير في المشاريع الضخمة.


اكتشاف المزيد من كود التطور

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

MOHAMMAD ABU KASHREEF

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

اترك رد