We are still cooking the magic in the way!
الدوال: التصريح والتعبير
ما هي الدوال؟
الدوال هي واحدة من اهم اللبنات الاساسية في JavaScript. الدالة هي كتلة كود قابلة لاعادة الاستخدام مصممة لاداء مهمة محددة. بدلا من كتابة نفس المنطق مرارا وتكرارا في برنامجك، تكتبه مرة واحدة داخل دالة ثم تستدعي تلك الدالة كلما احتجت اليها. الدوال تجعل كودك اكثر تنظيما واكثر قابلية للقراءة واسهل في الاختبار واسهل في الصيانة. كل برنامج JavaScript، من مدقق نماذج بسيط الى تطبيق معقد من صفحة واحدة، مبني من دوال تعمل معا.
فكر في الدالة كوصفة طبخ. تعرف الوصفة مرة واحدة (المكونات والخطوات)، ثم يمكنك اتباع تلك الوصفة في اي وقت تريد طهي ذلك الطبق. لا تعيد كتابة الوصفة في كل مرة تطبخ. بالمثل، تعرف الدالة مرة واحدة وتستدعيها عدة مرات حسب الحاجة.
تصريحات الدوال
تصريح الدالة (يسمى ايضا عبارة الدالة) هو الطريقة الاكثر تقليدية لتعريف دالة في JavaScript. يبدا بكلمة function المفتاحية، متبوعة باسم، ثم قائمة من المعاملات بين اقواس، وكتلة كود بين اقواس معقوفة. تصريحات الدوال هي الطريقة الاكثر مباشرة وسهولة في التعرف لانشاء الدوال.
مثال: تصريح دالة اساسي
// تعريف دالة
function greet() {
console.log('مرحبا! اهلا بك في JavaScript.');
}
// استدعاء الدالة
greet();
// المخرجات: مرحبا! اهلا بك في JavaScript.
// يمكنك استدعاؤها عدة مرات
greet();
greet();
// المخرجات:
// مرحبا! اهلا بك في JavaScript.
// مرحبا! اهلا بك في JavaScript.
مثال: دالة مع معاملات
function greetUser(name) {
console.log('مرحبا ' + name + '! اهلا بك.');
}
greetUser('اليس');
// المخرجات: مرحبا اليس! اهلا بك.
greetUser('بوب');
// المخرجات: مرحبا بوب! اهلا بك.
// معاملات متعددة
function introduce(name, age, city) {
console.log(name + ' عمره ' + age + ' سنة ويعيش في ' + city + '.');
}
introduce('تشارلي', 28, 'نيويورك');
// المخرجات: تشارلي عمره 28 سنة ويعيش في نيويورك.
calculateTotal، validateEmail، fetchUserData. اسم الدالة الجيد يجعل كودك موثقا ذاتيا.تعبيرات الدوال
تعبير الدالة ينشئ دالة ويسندها الى متغير. الدالة نفسها يمكن ان تكون مسماة او مجهولة (بدون اسم). تعبيرات الدوال لا يتم رفعها (hoisted)، مما يعني انه لا يمكنك استدعاؤها قبل تعريفها في كودك. هذا فرق جوهري عن تصريحات الدوال وله تأثيرات مهمة على كيفية تنظيم كودك.
مثال: تعبير دالة (مجهول)
// الدالة ليس لها اسم -- هي مجهولة
let sayGoodbye = function() {
console.log('مع السلامة! اراك المرة القادمة.');
};
sayGoodbye();
// المخرجات: مع السلامة! اراك المرة القادمة.
// تعبير دالة مع معاملات
let multiply = function(a, b) {
return a * b;
};
let result = multiply(6, 7);
console.log('6 x 7 = ' + result);
// المخرجات: 6 x 7 = 42
مثال: مقارنة التصريح مقابل التعبير
// تصريح الدالة -- يمكن استدعاؤها قبل تعريفها
console.log(add(3, 4)); // المخرجات: 7
function add(a, b) {
return a + b;
}
// تعبير الدالة -- لا يمكن استدعاؤه قبل التعريف
// console.log(subtract(10, 5)); // خطا: subtract غير معرف
let subtract = function(a, b) {
return a - b;
};
console.log(subtract(10, 5)); // المخرجات: 5
الدوال المجهولة
الدالة المجهولة هي دالة بدون اسم. رأيت واحدة بالفعل في مثال تعبير الدالة اعلاه. الدوال المجهولة تستخدم بشكل شائع كمعاملات تمرر الى دوال اخرى (callbacks) وفي معالجات الاحداث وفي تعبيرات الدوال المستدعاة فوريا (IIFEs). انها في كل مكان في JavaScript.
مثال: الدوال المجهولة في الممارسة
// دالة مجهولة كمعالج حدث (مثال مفاهيمي)
// document.getElementById('btn').addEventListener('click', function() {
// console.log('تم النقر على الزر!');
// });
// دالة مجهولة مع setTimeout
setTimeout(function() {
console.log('هذه الرسالة تظهر بعد ثانيتين');
}, 2000);
// دالة مجهولة مع طرق المصفوفة
let numbers = [1, 2, 3, 4, 5];
let doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
console.log('المضاعفة: ' + doubled);
// المخرجات: المضاعفة: 2,4,6,8,10
تعبيرات الدوال المسماة
تعبير الدالة المسماة هو تعبير دالة حيث الدالة لها ايضا اسمها الخاص. هذا الاسم يمكن الوصول اليه فقط داخل الدالة نفسها (مفيد للتكرار الذاتي) ويظهر في تتبع المكدس لتسهيل تصحيح الاخطاء. اسم المتغير الخارجي هو ما تستخدمه لاستدعاء الدالة من الخارج.
مثال: تعبير دالة مسماة
// الدالة لها اسمان:
// 'factorial' (داخلي) و 'calculateFactorial' (خارجي)
let calculateFactorial = function factorial(n) {
if (n <= 1) {
return 1;
}
// استخدام الاسم الداخلي للتكرار الذاتي
return n * factorial(n - 1);
};
console.log(calculateFactorial(5)); // المخرجات: 120
console.log(calculateFactorial(7)); // المخرجات: 5040
// 'factorial' غير متاح خارج الدالة
// console.log(factorial(5)); // خطا: factorial غير معرف
مثال: فائدة التسمية في تصحيح الاخطاء
// مجهولة -- اصعب في تصحيح الاخطاء
let processA = function(data) {
// اذا حدث خطا هنا، تتبع المكدس يظهر "anonymous"
return data.toUpperCase();
};
// مسماة -- اسهل في تصحيح الاخطاء
let processB = function processData(data) {
// اذا حدث خطا هنا، تتبع المكدس يظهر "processData"
return data.toUpperCase();
};
console.log(processA('hello')); // المخرجات: HELLO
console.log(processB('world')); // المخرجات: WORLD
تعبيرات الدوال المستدعاة فوريا (IIFE)
IIFE هي دالة تعمل فورا بعد تعريفها. تغلف الدالة بين اقواس لجعلها تعبيرا، ثم تضيف زوجا اخر من الاقواس لاستدعائها فورا. استخدمت IIFEs تاريخيا لانشاء نطاقات خاصة وتجنب تلويث النطاق العام. بينما JavaScript الحديثة لديها تحديد نطاق الكتل مع let و const، لا تزال IIFEs تستخدم في المكتبات وسكربتات التهيئة وكود التشغيل الاولي.
مثال: صيغة IIFE الاساسية
// IIFE بدون معاملات
(function() {
let message = 'انا اعمل فورا!';
console.log(message);
})();
// المخرجات: انا اعمل فورا!
// المتغير 'message' غير متاح هنا
// console.log(message); // خطا: message غير معرف
مثال: IIFE مع معاملات
// IIFE تقبل معاملات
(function(name, version) {
console.log('التطبيق: ' + name + ' v' + version);
console.log('جاري التهيئة...');
})('MyApp', '1.0.0');
// المخرجات:
// التطبيق: MyApp v1.0.0
// جاري التهيئة...
مثال: IIFE لانشاء نطاق خاص
// وحدة عداد باستخدام IIFE
let counter = (function() {
let count = 0; // متغير خاص -- غير متاح من الخارج
return {
increment: function() {
count++;
console.log('العدد: ' + count);
},
decrement: function() {
count--;
console.log('العدد: ' + count);
},
getCount: function() {
return count;
}
};
})();
counter.increment(); // العدد: 1
counter.increment(); // العدد: 2
counter.increment(); // العدد: 3
counter.decrement(); // العدد: 2
console.log('العدد الحالي: ' + counter.getCount()); // العدد الحالي: 2
// console.log(count); // خطا: count غير معرف
المعاملات مقابل المتغيرات الممررة
هذان المصطلحان يستخدمان بالتبادل غالبا، لكن لهما معاني مختلفة. المعاملات (Parameters) هي اسماء المتغيرات المدرجة في تعريف الدالة. هي عناصر نائبة تعمل كمتغيرات محلية داخل الدالة. المتغيرات الممررة (Arguments) هي القيم الفعلية التي تمررها للدالة عند استدعائها. المعاملات تستقبل المتغيرات الممررة.
مثال: المعاملات مقابل المتغيرات الممررة
// 'width' و 'height' هي معاملات (معرفة في توقيع الدالة)
function calculateArea(width, height) {
return width * height;
}
// 10 و 5 هي متغيرات ممررة (تمرر عند استدعاء الدالة)
let area = calculateArea(10, 5);
console.log('المساحة: ' + area); // المخرجات: المساحة: 50
// متغيرات ممررة مختلفة، نفس الدالة
let area2 = calculateArea(7, 3);
console.log('المساحة: ' + area2); // المخرجات: المساحة: 21
مثال: ماذا يحدث مع متغيرات ممررة مفقودة او زائدة
function showInfo(name, age, city) {
console.log('الاسم: ' + name);
console.log('العمر: ' + age);
console.log('المدينة: ' + city);
}
// جميع المتغيرات الممررة متوفرة
showInfo('اليس', 25, 'لندن');
// الاسم: اليس
// العمر: 25
// المدينة: لندن
// متغير ممرر مفقود -- يصبح undefined
showInfo('بوب', 30);
// الاسم: بوب
// العمر: 30
// المدينة: undefined
// المتغيرات الممررة الزائدة تتجاهل بصمت
showInfo('تشارلي', 35, 'باريس', 'اضافي', 'قيم');
// الاسم: تشارلي
// العمر: 35
// المدينة: باريس
المعاملات الافتراضية
المعاملات الافتراضية، التي قدمت في ES6، تسمح لك بتحديد قيم احتياطية لمعاملات الدالة. اذا لم يتم توفير متغير ممرر (او كان undefined صراحة)، يتم استخدام القيمة الافتراضية بدلا من ذلك. هذا يزيل الحاجة للتحقق اليدوي داخل جسم الدالة ويجعل توقيعات دوالك اكثر تعبيرا وتوثيقا ذاتيا.
مثال: المعاملات الافتراضية
function createUser(name, role = 'member', isActive = true) {
console.log('الاسم: ' + name);
console.log('الدور: ' + role);
console.log('نشط: ' + isActive);
console.log('---');
}
// استخدام جميع القيم الافتراضية
createUser('اليس');
// الاسم: اليس
// الدور: member
// نشط: true
// تجاوز المعامل الثاني
createUser('بوب', 'admin');
// الاسم: بوب
// الدور: admin
// نشط: true
// تجاوز جميع المعاملات
createUser('تشارلي', 'moderator', false);
// الاسم: تشارلي
// الدور: moderator
// نشط: false
مثال: المعاملات الافتراضية مع التعبيرات
// القيم الافتراضية يمكن ان تكون تعبيرات وليس فقط قيم حرفية
function generateId(prefix = 'ID', number = Math.floor(Math.random() * 10000)) {
return prefix + '-' + number;
}
console.log(generateId()); // مثلا ID-4827
console.log(generateId('USER')); // مثلا USER-9153
console.log(generateId('ORDER', 1)); // ORDER-1
// القيم الافتراضية يمكن ان تشير الى معاملات سابقة
function createEmail(username, domain = 'gmail.com', email = username + '@' + domain) {
return email;
}
console.log(createEmail('john')); // john@gmail.com
console.log(createEmail('jane', 'company.com')); // jane@company.com
مثال: قبل ES6 -- القيم الافتراضية اليدوية
// الطريقة القديمة (لا تزال موجودة في الكود القديم)
function oldGreet(name, greeting) {
name = name || 'العالم';
greeting = greeting || 'مرحبا';
console.log(greeting + '، ' + name + '!');
}
oldGreet(); // مرحبا، العالم!
oldGreet('اليس'); // مرحبا، اليس!
oldGreet('بوب', 'اهلا'); // اهلا، بوب!
// مشكلة الطريقة القديمة:
oldGreet('', 'مرحبا'); // مرحبا، العالم! (السلسلة الفارغة قيمة زائفة!)
oldGreet(0); // مرحبا، العالم! (0 قيمة زائفة!)
// طريقة ES6 تتعامل مع هذه بشكل صحيح
function newGreet(name = 'العالم', greeting = 'مرحبا') {
console.log(greeting + '، ' + name + '!');
}
newGreet('', 'مرحبا'); // مرحبا، ! (السلسلة الفارغة محفوظة)
newGreet(0); // مرحبا، 0! (0 محفوظ)
undefined (او غير متوفر). تمرير null لن يفعل القيمة الافتراضية. هذا فرق مهم: undefined تعني "غير متوفر" بينما null تعني "فارغ عمدا".كائن arguments
كل دالة غير سهمية في JavaScript لديها وصول الى كائن arguments الخاص. هو كائن شبيه بالمصفوفة يحتوي على جميع المتغيرات الممررة للدالة، بغض النظر عن عدد المعاملات المعرفة. بينما JavaScript الحديثة توفر معاملات الراحة (...args) كبديل افضل، فهم كائن arguments مهم لانك ستصادفه في قواعد الكود الموجودة.
مثال: استخدام كائن arguments
function showArguments() {
console.log('عدد المتغيرات الممررة: ' + arguments.length);
for (let i = 0; i < arguments.length; i++) {
console.log(' المتغير ' + i + ': ' + arguments[i]);
}
}
showArguments('مرحبا', 42, true, [1, 2, 3]);
// المخرجات:
// عدد المتغيرات الممررة: 4
// المتغير 0: مرحبا
// المتغير 1: 42
// المتغير 2: true
// المتغير 3: 1,2,3
مثال: دالة جمع مرنة باستخدام arguments
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(10, 20, 30)); // 60
console.log(sum(1, 2, 3, 4, 5, 6)); // 21
console.log(sum()); // 0
arguments ليس مصفوفة حقيقية. يفتقر الى طرق المصفوفة مثل map و filter و forEach. اذا احتجت تلك الطرق، حوله اولا: let args = Array.from(arguments) او استخدم عامل الانتشار let args = [...arguments]. في الكود الحديث، فضل معاملات الراحة (...args) التي تعطيك مصفوفة حقيقية من البداية.القيم المرجعة
عبارة return تحدد القيمة التي ترسلها الدالة الى الكود الذي استدعاها. يمكن للدالة ان ترجع اي نوع من القيم: رقم او سلسلة نصية او قيمة منطقية او كائن او مصفوفة او حتى دالة اخرى. عندما يتم تنفيذ عبارة return، تتوقف الدالة فورا -- اي كود بعد عبارة return لا يتم تنفيذه. اذا لم يكن للدالة عبارة return، فانها ترجع ضمنيا undefined.
مثال: دوال مع قيم مرجعة
// ارجاع رقم
function square(n) {
return n * n;
}
console.log(square(5)); // 25
console.log(square(12)); // 144
// ارجاع سلسلة نصية
function formatPrice(amount, currency) {
return currency + amount.toFixed(2);
}
console.log(formatPrice(29.999, '$')); // $30.00
// ارجاع قيمة منطقية
function isEven(number) {
return number % 2 === 0;
}
console.log(isEven(4)); // true
console.log(isEven(7)); // false
// ارجاع كائن
function createPerson(name, age) {
return {
name: name,
age: age,
isAdult: age >= 18
};
}
let person = createPerson('اليس', 25);
console.log(person.name); // اليس
console.log(person.isAdult); // true
مثال: نمط الارجاع المبكر
// استخدام return للخروج مبكرا (نمط شرط الحماية)
function divide(a, b) {
if (b === 0) {
return 'خطا: لا يمكن القسمة على صفر';
}
return a / b;
}
console.log(divide(10, 3)); // 3.3333333333333335
console.log(divide(10, 0)); // خطا: لا يمكن القسمة على صفر
// شروط الحماية تحسن القراءة
function processAge(age) {
if (typeof age !== 'number') {
return 'خطا: العمر يجب ان يكون رقما';
}
if (age < 0) {
return 'خطا: العمر لا يمكن ان يكون سالبا';
}
if (age > 150) {
return 'خطا: العمر يبدو غير واقعي';
}
// المنطق الرئيسي -- يصل اليه فقط اذا نجحت جميع التحققات
if (age < 13) return 'طفل';
if (age < 18) return 'مراهق';
if (age < 65) return 'بالغ';
return 'كبير السن';
}
console.log(processAge(8)); // طفل
console.log(processAge(16)); // مراهق
console.log(processAge(35)); // بالغ
console.log(processAge(-5)); // خطا: العمر لا يمكن ان يكون سالبا
مثال: ارجاع قيم متعددة عبر الكائنات او المصفوفات
// دوال JavaScript يمكنها ارجاع قيمة واحدة فقط،
// لكن يمكنك ارجاع كائن او مصفوفة لتجميع قيم متعددة
// ارجاع كائن
function getMinMax(arr) {
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return { min: min, max: max };
}
let stats = getMinMax([34, 12, 78, 3, 56, 91, 23]);
console.log('الادنى: ' + stats.min + '، الاقصى: ' + stats.max);
// المخرجات: الادنى: 3، الاقصى: 91
// ارجاع مصفوفة
function splitName(fullName) {
let parts = fullName.split(' ');
return [parts[0], parts[parts.length - 1]];
}
let nameParts = splitName('جون مايكل سميث');
console.log('الاول: ' + nameParts[0] + '، الاخير: ' + nameParts[1]);
// المخرجات: الاول: جون، الاخير: سميث
رفع الدوال (Hoisting)
الرفع هو آلية في JavaScript حيث يتم نقل تصريحات الدوال الى اعلى نطاقها خلال مرحلة التجميع، قبل تنفيذ الكود. هذا يعني انه يمكنك استدعاء تصريح دالة قبل ظهوره في كودك. تعبيرات الدوال، مع ذلك، لا يتم رفعها -- تتصرف مثل اسنادات المتغيرات العادية. فهم الرفع امر حاسم لتجنب الاخطاء الدقيقة وتنظيم كودك بفعالية.
مثال: رفع تصريح الدالة
// هذا يعمل لان تصريحات الدوال يتم رفعها
let result1 = calculateTax(1000, 0.15);
console.log('الضريبة: $' + result1); // الضريبة: $150
function calculateTax(amount, rate) {
return amount * rate;
}
// هذا يعمل ايضا
console.log('الضريبة: $' + calculateTax(2000, 0.20)); // الضريبة: $400
مثال: تعبير الدالة لا يتم رفعه
// هذا سيسبب خطا!
// let result2 = computeDiscount(100, 0.1);
// TypeError: computeDiscount ليست دالة
let computeDiscount = function(price, discount) {
return price - (price * discount);
};
// هذا يعمل -- الدالة معرفة الآن
let result2 = computeDiscount(100, 0.1);
console.log('السعر بعد الخصم: $' + result2); // السعر بعد الخصم: $90
الدوال كقيم
في JavaScript، الدوال هي مواطنة من الدرجة الاولى. هذا يعني ان الدوال يمكن معاملتها مثل اي قيمة اخرى: يمكنك اسنادها الى متغيرات وتخزينها في مصفوفات وتمريرها كمتغيرات ممررة لدوال اخرى وارجاعها من دوال. هذه واحدة من اقوى ميزات JavaScript وهي اساس انماط البرمجة الوظيفية.
مثال: اسناد الدوال الى متغيرات
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) { return b !== 0 ? a / b : 'خطا'; }
// تخزين الدوال في متغير
let operation = add;
console.log(operation(5, 3)); // 8
operation = multiply;
console.log(operation(5, 3)); // 15
// تخزين الدوال في كائن
let calculator = {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.subtract(10, 5)); // 5
console.log(calculator.multiply(10, 5)); // 50
console.log(calculator.divide(10, 5)); // 2
مثال: تخزين الدوال في مصفوفة
let transformations = [
function(x) { return x * 2; },
function(x) { return x + 10; },
function(x) { return x * x; },
function(x) { return -x; }
];
let value = 5;
for (let i = 0; i < transformations.length; i++) {
let transformed = transformations[i](value);
console.log('التحويل ' + (i + 1) + ': ' + value + ' يصبح ' + transformed);
}
// المخرجات:
// التحويل 1: 5 يصبح 10
// التحويل 2: 5 يصبح 15
// التحويل 3: 5 يصبح 25
// التحويل 4: 5 يصبح -5
مثال: دوال ترجع دوال
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
let times10 = createMultiplier(10);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(times10(5)); // 50
console.log(double(100)); // 200
// هذا النمط يسمى "الاغلاق" (closure) --
// الدالة الداخلية تتذكر متغير 'factor'
// من الدالة الخارجية حتى بعد ان ارجعت الدالة الخارجية
مقدمة الى دوال الاستدعاء الراجع (Callbacks)
دالة الاستدعاء الراجع هي دالة تمرر كمتغير ممرر الى دالة اخرى ويتم تنفيذها في وقت لاحق. دوال الاستدعاء الراجع اساسية في JavaScript، خاصة للتعامل مع العمليات غير المتزامنة مثل تحميل البيانات والاستجابة لاحداث المستخدم والعمل مع المؤقتات. الدالة التي تستقبل دالة الاستدعاء الراجع تقرر متى وكيف تستدعيها.
مثال: نمط دالة الاستدعاء الراجع الاساسي
function processData(data, callback) {
console.log('معالجة: ' + data);
let result = data.toUpperCase();
callback(result);
}
function displayResult(output) {
console.log('النتيجة: ' + output);
}
processData('hello world', displayResult);
// المخرجات:
// معالجة: hello world
// النتيجة: HELLO WORLD
// يمكنك ايضا تمرير دالة مجهولة كاستدعاء راجع
processData('javascript', function(output) {
console.log('الاستدعاء الراجع استقبل: ' + output);
});
// المخرجات:
// معالجة: javascript
// الاستدعاء الراجع استقبل: JAVASCRIPT
مثال: استدعاء راجع لمعالجة المصفوفات
function transformArray(arr, transformFn) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(transformFn(arr[i]));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let doubled = transformArray(numbers, function(n) {
return n * 2;
});
console.log('المضاعفة: ' + doubled); // المضاعفة: 2,4,6,8,10
let squared = transformArray(numbers, function(n) {
return n * n;
});
console.log('المربعات: ' + squared); // المربعات: 1,4,9,16,25
let asStrings = transformArray(numbers, function(n) {
return 'العنصر ' + n;
});
console.log('كنصوص: ' + asStrings);
// كنصوص: العنصر 1,العنصر 2,العنصر 3,العنصر 4,العنصر 5
مثال: استدعاء راجع للتصفية
function filterArray(arr, testFn) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (testFn(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
let ages = [12, 25, 8, 32, 17, 45, 14, 28];
let adults = filterArray(ages, function(age) {
return age >= 18;
});
console.log('البالغون: ' + adults); // البالغون: 25,32,45,28
let teenagers = filterArray(ages, function(age) {
return age >= 13 && age <= 19;
});
console.log('المراهقون: ' + teenagers); // المراهقون: 17,14
مثال: محاكاة العمليات غير المتزامنة مع دوال الاستدعاء الراجع
function fetchUser(userId, onSuccess, onError) {
console.log('جلب المستخدم ' + userId + '...');
// محاكاة تاخير الشبكة
setTimeout(function() {
if (userId > 0) {
let user = { id: userId, name: 'المستخدم ' + userId, role: 'عضو' };
onSuccess(user);
} else {
onError('معرف مستخدم غير صالح');
}
}, 1000);
}
// استخدام الدالة المبنية على الاستدعاء الراجع
fetchUser(42,
function(user) {
console.log('نجاح! وجدنا: ' + user.name);
},
function(error) {
console.log('خطا: ' + error);
}
);
// بعد ثانية: نجاح! وجدنا: المستخدم 42
fetchUser(-1,
function(user) {
console.log('نجاح! وجدنا: ' + user.name);
},
function(error) {
console.log('خطا: ' + error);
}
);
// بعد ثانية: خطا: معرف مستخدم غير صالح
مثال عملي: مدقق النماذج
مثال عملي على الدوال التي تعمل معا هو بناء مدقق نماذج. كل قاعدة تحقق هي دالة، والمدقق يركبها لفحص جميع الحقول. هذا يوضح تصريحات الدوال والقيم المرجعة ودوال الاستدعاء الراجع والدوال كقيم.
مثال: بناء مدقق نماذج
// دوال التحقق -- كل واحدة ترجع رسالة خطا او null
function validateRequired(value, fieldName) {
if (value === '' || value === null || value === undefined) {
return fieldName + ' مطلوب';
}
return null;
}
function validateMinLength(value, minLen, fieldName) {
if (value.length < minLen) {
return fieldName + ' يجب ان يكون ' + minLen + ' احرف على الاقل';
}
return null;
}
function validateEmail(value) {
if (value.indexOf('@') === -1 || value.indexOf('.') === -1) {
return 'الرجاء ادخال عنوان بريد الكتروني صحيح';
}
return null;
}
function validateAge(value) {
let age = parseInt(value);
if (isNaN(age) || age < 18 || age > 120) {
return 'العمر يجب ان يكون رقما بين 18 و 120';
}
return null;
}
// دالة التحقق الرئيسية
function validateForm(formData) {
let errors = [];
let nameError = validateRequired(formData.name, 'الاسم');
if (nameError) errors.push(nameError);
if (!nameError) {
let lengthError = validateMinLength(formData.name, 2, 'الاسم');
if (lengthError) errors.push(lengthError);
}
let emailError = validateRequired(formData.email, 'البريد الالكتروني');
if (emailError) errors.push(emailError);
if (!emailError) {
let formatError = validateEmail(formData.email);
if (formatError) errors.push(formatError);
}
let ageError = validateAge(formData.age);
if (ageError) errors.push(ageError);
return errors;
}
// اختبار المدقق
let testData1 = { name: 'اليس', email: 'alice@example.com', age: '25' };
let errors1 = validateForm(testData1);
console.log('اختبار 1: ' + (errors1.length === 0 ? 'لا اخطاء -- النموذج صالح!' : errors1.join('، ')));
// المخرجات: اختبار 1: لا اخطاء -- النموذج صالح!
let testData2 = { name: '', email: 'invalid', age: '15' };
let errors2 = validateForm(testData2);
console.log('اختبار 2: ' + errors2.join('؛ '));
// المخرجات: اختبار 2: الاسم مطلوب؛ الرجاء ادخال عنوان بريد الكتروني صحيح؛ العمر يجب ان يكون رقما بين 18 و 120
مثال عملي: منشئ الاعدادات
الدوال التي ترجع كائنات تستخدم بشكل شائع لانشاء منشئي اعدادات. هذا النمط منتشر في مكتبات واطر عمل JavaScript حيث تحتاج لاعداد اعدادات معقدة مع قيم افتراضية معقولة.
مثال: منشئ اعدادات مع معاملات افتراضية
function createConfig(options) {
let defaults = {
theme: 'light',
language: 'en',
fontSize: 16,
showSidebar: true,
maxResults: 10,
debug: false
};
// دمج خيارات المستخدم مع الافتراضية
let config = {};
for (let key in defaults) {
if (defaults.hasOwnProperty(key)) {
if (options && options.hasOwnProperty(key)) {
config[key] = options[key];
} else {
config[key] = defaults[key];
}
}
}
return config;
}
// استخدام مع خيارات مخصصة
let myConfig = createConfig({ theme: 'dark', language: 'ar', debug: true });
console.log('السمة: ' + myConfig.theme); // dark
console.log('اللغة: ' + myConfig.language); // ar
console.log('حجم الخط: ' + myConfig.fontSize); // 16 (افتراضي)
console.log('تصحيح الاخطاء: ' + myConfig.debug); // true
// استخدام مع جميع القيم الافتراضية
let defaultConfig = createConfig();
console.log('السمة الافتراضية: ' + defaultConfig.theme); // light
مثال عملي: خط انابيب البيانات مع دوال الاستدعاء الراجع
في التطبيقات الحقيقية، تحتاج غالبا لمعالجة البيانات عبر مراحل متعددة. الدوال ودوال الاستدعاء الراجع تسمح لك بانشاء خطوط انابيب معالجة مرنة حيث يمكن تخصيص كل مرحلة.
مثال: خط انابيب معالجة البيانات
function pipeline(data, steps) {
let result = data;
for (let i = 0; i < steps.length; i++) {
result = steps[i](result);
console.log('الخطوة ' + (i + 1) + ': ' + JSON.stringify(result));
}
return result;
}
// تعريف خطوات المعالجة كدوال
function removeEmpty(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== '' && arr[i] !== null && arr[i] !== undefined) {
result.push(arr[i]);
}
}
return result;
}
function trimAll(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].toString().trim());
}
return result;
}
function lowercaseAll(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].toLowerCase());
}
return result;
}
function removeDuplicates(arr) {
let unique = [];
for (let i = 0; i < arr.length; i++) {
let found = false;
for (let j = 0; j < unique.length; j++) {
if (unique[j] === arr[i]) {
found = true;
break;
}
}
if (!found) unique.push(arr[i]);
}
return unique;
}
// تشغيل خط الانابيب
let rawData = [' Alice ', 'bob', '', ' CHARLIE ', null, 'alice', 'Bob'];
let cleanData = pipeline(rawData, [removeEmpty, trimAll, lowercaseAll, removeDuplicates]);
console.log('النتيجة النهائية: ' + cleanData);
// النتيجة النهائية: alice,bob,charlie
افضل الممارسات لكتابة الدوال
- المسؤولية الواحدة -- كل دالة يجب ان تفعل شيئا واحدا وتفعله جيدا. اذا كانت الدالة تفعل اشياء كثيرة، قسمها الى دوال اصغر.
- اسماء وصفية -- استخدم اسماء واضحة ووصفية.
calculateTotalPriceافضل منcalcاوdoStuff. - ابق الدوال قصيرة -- استهدف دوال من 20 سطرا او اقل. الدوال الاقصر اسهل في القراءة والاختبار وتصحيح الاخطاء.
- حدد عدد المعاملات -- الدوال التي تحتوي على اكثر من 3 معاملات تصبح صعبة الاستخدام. اذا احتجت قيما كثيرة، مرر كائنا بدلا من ذلك.
- تجنب الآثار الجانبية -- يجب ان ترجع الدوال قيمة بشكل مثالي دون تعديل الحالة الخارجية. هذا يجعلها قابلة للتنبؤ والاختبار.
- استخدم المعاملات الافتراضية -- وفر قيما افتراضية معقولة حتى يحتاج المستدعون فقط لتحديد ما يختلف عن المعتاد.
- ارجع مبكرا -- استخدم شروط الحماية للتعامل مع الحالات الحدية في اعلى الدالة مما يبقي المنطق الرئيسي بمستوى مسافة بادئة اقل.
تمرين عملي
اكمل هذه التحديات الاربعة لتعزيز فهمك لدوال JavaScript:
التحدي 1: محول الحرارة. اكتب دالتين: celsiusToFahrenheit(celsius) و fahrenheitToCelsius(fahrenheit). ثم اكتب دالة ثالثة convertTemperature(value, fromUnit) تقبل درجة حرارة وسلسلة وحدة ("C" او "F") وتستدعي دالة التحويل المناسبة. اختبر بقيم متعددة.
التحدي 2: عداد الكلمات. اكتب دالة analyzeText(text) تستقبل سلسلة نصية وترجع كائنا يحتوي على: العدد الاجمالي للاحرف وعدد الكلمات وعدد الجمل (احسب النقاط وعلامات التعجب وعلامات الاستفهام) ومتوسط طول الكلمة. استخدم دوال مساعدة لكل حساب.
التحدي 3: عمليات مصفوفة مخصصة. اكتب ثلاث دوال ذات ترتيب اعلى: myMap(array, callback) تعيد مصفوفة جديدة حيث كل عنصر هو نتيجة استدعاء الاستدعاء الراجع مع العنصر الاصلي، و myFilter(array, callback) تعيد مصفوفة جديدة بالعناصر التي يرجع لها الاستدعاء الراجع true فقط، و myReduce(array, callback, initialValue) تختصر المصفوفة الى قيمة واحدة. اختبر كل دالة مع استدعاءين راجعين مختلفين على الاقل.
التحدي 4: آلة حاسبة صغيرة مع ذاكرة. اكتب IIFE تعيد كائن آلة حاسبة بطرق: add(n) و subtract(n) و multiply(n) و divide(n) و getResult() و reset() و getHistory(). يجب ان تحافظ الآلة الحاسبة على نتيجة جارية (تبدأ من 0)، و getHistory() يجب ان تعيد مصفوفة بجميع العمليات المنفذة (مثل "add 5" و "multiply 3"). اختبر بتسلسل عمليات متعددة.