JavaScript المتقدم (ES6+)

الدوال عالية الرتبة

13 دقيقة الدرس 10 من 40

الدوال عالية الرتبة

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

الدوال كمواطنين من الدرجة الأولى

في JavaScript، الدوال هي مواطنون من الدرجة الأولى، مما يعني أنه يمكن التعامل معها مثل أي قيمة أخرى:

// يمكن تعيين الدوال للمتغيرات const greet = function(name) { return `Hello, ${name}!`; }; // يمكن تخزين الدوال في المصفوفات const operations = [ x => x + 1, x => x * 2, x => x ** 2 ]; console.log(operations[0](5)); // 6 console.log(operations[1](5)); // 10 console.log(operations[2](5)); // 25 // يمكن تخزين الدوال في الكائنات const math = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b }; console.log(math.add(3, 4)); // 7 // يمكن تمرير الدوال كمعاملات function applyOperation(x, operation) { return operation(x); } console.log(applyOperation(5, x => x * 2)); // 10
نصيحة: معاملة الدوال كقيم تفتح أنماط برمجة قوية. يمكنك بناء كود مرن وقابل لإعادة الاستخدام عن طريق تمرير السلوك (الدوال) كبيانات.

الدوال التي ترجع دوال

يمكن للدوال عالية الرتبة إرجاع دوال جديدة، مما يخلق تجريدات قوية:

// مصنع دوال أساسي function createMultiplier(multiplier) { return function(number) { return number * multiplier; }; } const double = createMultiplier(2); const triple = createMultiplier(3); const quadruple = createMultiplier(4); console.log(double(5)); // 10 console.log(triple(5)); // 15 console.log(quadruple(5)); // 20 // مع الدوال السهمية (أكثر إيجازاً) const createAdder = x => y => x + y; const add5 = createAdder(5); const add10 = createAdder(10); console.log(add5(3)); // 8 console.log(add10(3)); // 13

الإغلاقات والدوال عالية الرتبة

الدوال المرجعة لها وصول إلى متغيرات الدالة الخارجية (الإغلاق):

// عداد مع حالة خاصة function createCounter(initial = 0) { let count = initial; // متغير خاص return { increment() { return ++count; }, decrement() { return --count; }, getCount() { return count; }, reset() { count = initial; return count; } }; } const counter = createCounter(10); console.log(counter.increment()); // 11 console.log(counter.increment()); // 12 console.log(counter.decrement()); // 11 console.log(counter.getCount()); // 11 console.log(counter.reset()); // 10 // ملاحظة: count غير قابل للوصول مباشرة console.log(counter.count); // undefined
مفهوم رئيسي: تسمح الإغلاقات للدوال المرجعة "بتذكر" البيئة التي تم إنشاؤها فيها، مما يتيح الحالة الخاصة وتغليف البيانات.

تكوين الدوال

دمج دوال متعددة لإنشاء وظائف جديدة:

// التكوين الأساسي - من اليمين إلى اليسار function compose(...functions) { return function(initialValue) { return functions.reduceRight( (value, fn) => fn(value), initialValue ); }; } const addOne = x => x + 1; const double = x => x * 2; const square = x => x * x; // التكوين: square(double(addOne(x))) const compute = compose(square, double, addOne); console.log(compute(2)); // ((2 + 1) * 2)^2 = 36 // Pipe - من اليسار إلى اليمين (قراءة أكثر بديهية) function pipe(...functions) { return function(initialValue) { return functions.reduce( (value, fn) => fn(value), initialValue ); }; } // Pipe: square(double(addOne(x))) const process = pipe(addOne, double, square); console.log(process(2)); // ((2 + 1) * 2)^2 = 36

الكاري والتطبيق الجزئي

يحول الكاري دالة ذات معاملات متعددة إلى سلسلة من الدوال بمعاملات مفردة:

// الكاري اليدوي function curriedAdd(a) { return function(b) { return function(c) { return a + b + c; }; }; } console.log(curriedAdd(1)(2)(3)); // 6 // الكاري بالدالة السهمية (أكثر إيجازاً) const add = a => b => c => a + b + c; console.log(add(1)(2)(3)); // 6 // دالة كاري عامة function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } return function(...moreArgs) { return curried.apply(this, [...args, ...moreArgs]); }; }; } const sum = (a, b, c) => a + b + c; const curriedSum = curry(sum); console.log(curriedSum(1)(2)(3)); // 6 console.log(curriedSum(1, 2)(3)); // 6 console.log(curriedSum(1)(2, 3)); // 6 // التطبيق الجزئي function partial(fn, ...presetArgs) { return function(...laterArgs) { return fn(...presetArgs, ...laterArgs); }; } const multiply = (a, b, c) => a * b * c; const double = partial(multiply, 2); const triple = partial(multiply, 3); console.log(double(5, 2)); // 2 * 5 * 2 = 20 console.log(triple(5, 2)); // 3 * 5 * 2 = 30
الكاري مقابل التطبيق الجزئي: الكاري يُرجع دائماً دالة تأخذ معاملاً واحداً بالضبط في كل مرة. التطبيق الجزئي يمكن أن يأخذ معاملات متعددة ويرجع دالة تتوقع المعاملات المتبقية.

أنماط الحفظ المؤقت

تخزين نتائج الدوال مؤقتاً لتحسين الأداء:

// الحفظ المؤقت الأساسي function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { console.log('Returning cached result'); return cache.get(key); } console.log('Computing result'); const result = fn.apply(this, args); cache.set(key, result); return result; }; } // حساب فيبوناتشي مكلف const fibonacci = memoize(function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }); console.log(fibonacci(10)); // Computing result (عدة مرات) console.log(fibonacci(10)); // Returning cached result console.log(fibonacci(20)); // أسرع بكثير بسبب الحفظ المؤقت // الحفظ المؤقت مع مولد مفاتيح مخصص function memoizeWith(fn, keyGenerator) { const cache = new Map(); return function(...args) { const key = keyGenerator(...args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; } const fetchUser = memoizeWith( (id) => ({ id, name: `User ${id}`, fetched: Date.now() }), (id) => `user_${id}` // مفتاح مخصص );

أمثلة عملية على الدوال عالية الرتبة

// 1. التأخير - تأخير التنفيذ حتى لا توجد مكالمات أخرى function debounce(fn, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { fn.apply(this, args); }, delay); }; } // الاستخدام: البحث أثناء كتابة المستخدم const search = debounce((query) => { console.log(`Searching for: ${query}`); }, 500); search('j'); // لن يتم التنفيذ search('ja'); // لن يتم التنفيذ search('jav'); // لن يتم التنفيذ search('java'); // يتم التنفيذ بعد 500ms // 2. التقييد - تحديد تردد التنفيذ function throttle(fn, limit) { let inThrottle; return function(...args) { if (!inThrottle) { fn.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // الاستخدام: معالجة حدث التمرير const handleScroll = throttle(() => { console.log('Scroll event handled'); }, 1000); // 3. مرة واحدة - دالة يمكن استدعاؤها مرة واحدة فقط function once(fn) { let called = false; let result; return function(...args) { if (!called) { called = true; result = fn.apply(this, args); } return result; }; } const initialize = once(() => { console.log('Initializing...'); return { initialized: true }; }); console.log(initialize()); // يسجل "Initializing..." ويُرجع الكائن console.log(initialize()); // يُرجع الكائن المخزن مؤقتاً بدون تسجيل // 4. منطق إعادة المحاولة function retry(fn, maxAttempts = 3, delay = 1000) { return async function(...args) { let lastError; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn.apply(this, args); } catch (error) { lastError = error; console.log(`Attempt ${attempt} failed:`, error.message); if (attempt < maxAttempts) { await new Promise(resolve => setTimeout(resolve, delay)); } } } throw new Error(`Failed after ${maxAttempts} attempts: ${lastError.message}`); }; } // الاستخدام const unreliableAPI = retry(async () => { if (Math.random() > 0.7) { return 'Success!'; } throw new Error('Network error'); }, 5, 500);

دوال المصفوفة عالية الرتبة

// دوال المصفوفة عالية الرتبة المدمجة const numbers = [1, 2, 3, 4, 5]; // map - تحويل كل عنصر const doubled = numbers.map(x => x * 2); console.log(doubled); // [2, 4, 6, 8, 10] // filter - اختيار العناصر const evens = numbers.filter(x => x % 2 === 0); console.log(evens); // [2, 4] // reduce - تجميع القيم const sum = numbers.reduce((acc, x) => acc + x, 0); console.log(sum); // 15 // تسلسل عمليات متعددة const result = numbers .filter(x => x % 2 !== 0) // أرقام فردية: [1, 3, 5] .map(x => x ** 2) // تربيع: [1, 9, 25] .reduce((acc, x) => acc + x, 0); // جمع: 35 console.log(result); // 35 // دالة مصفوفة عالية الرتبة مخصصة function mapObject(obj, fn) { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [key, fn(value, key)]) ); } const prices = { apple: 1, banana: 2, orange: 3 }; const discounted = mapObject(prices, price => price * 0.8); console.log(discounted); // { apple: 0.8, banana: 1.6, orange: 2.4 }

حالات الاستخدام في العالم الحقيقي

// 1. نمط الوسيط (نمط Express.js) function createMiddleware() { const middlewares = []; return { use(fn) { middlewares.push(fn); }, execute(context) { return middlewares.reduce( (promise, middleware) => promise.then(() => middleware(context)), Promise.resolve() ); } }; } const app = createMiddleware(); app.use(ctx => { ctx.step1 = true; console.log('Middleware 1'); }); app.use(ctx => { ctx.step2 = true; console.log('Middleware 2'); }); // 2. نظام الإضافات function createPluginSystem() { const plugins = []; return { register(plugin) { plugins.push(plugin); }, applyPlugins(data) { return plugins.reduce( (result, plugin) => plugin(result), data ); } }; } const system = createPluginSystem(); system.register(data => ({ ...data, processed: true })); system.register(data => ({ ...data, timestamp: Date.now() })); const result = system.applyPlugins({ value: 42 }); console.log(result); // { value: 42, processed: true, timestamp: ... } // 3. سلسلة التحقق function createValidator() { const rules = []; return { addRule(fn, errorMessage) { rules.push({ fn, errorMessage }); return this; // قابل للتسلسل }, validate(value) { const errors = []; for (const { fn, errorMessage } of rules) { if (!fn(value)) { errors.push(errorMessage); } } return errors.length === 0 ? null : errors; } }; } const emailValidator = createValidator() .addRule(v => v && v.length > 0, 'Email is required') .addRule(v => v.includes('@'), 'Email must contain @') .addRule(v => v.length < 100, 'Email too long'); console.log(emailValidator.validate('test@example.com')); // null (صالح) console.log(emailValidator.validate('invalid')); // ['Email must contain @']
ملاحظة حول الأداء: بينما الدوال عالية الرتبة قوية وأنيقة، كن واعياً للأداء في مسارات الكود الحرجة. أحياناً حلقة بسيطة أسرع من عمليات متسلسلة متعددة.

تمرين تطبيقي:

المهمة: أنشئ دوال مساعدة عالية الرتبة:

  1. اكتب دالة logger تلف دالة أخرى وتسجل مدخلاتها/مخرجاتها
  2. أنشئ دالة cache مع دعم TTL (وقت البقاء)
  3. ابنِ دالة compose تعمل مع الدوال غير المتزامنة

الحل:

// 1. غلاف المسجل function logger(fn, name = fn.name || 'anonymous') { return function(...args) { console.log(`[${name}] Called with:`, args); const result = fn.apply(this, args); console.log(`[${name}] Returned:`, result); return result; }; } const add = logger((a, b) => a + b, 'add'); add(2, 3); // يسجل المدخلات والمخرجات // 2. ذاكرة التخزين المؤقت مع TTL function cache(fn, ttl = 60000) { const store = new Map(); return function(...args) { const key = JSON.stringify(args); const cached = store.get(key); if (cached && Date.now() - cached.timestamp < ttl) { return cached.value; } const value = fn.apply(this, args); store.set(key, { value, timestamp: Date.now() }); // تنظيف الإدخالات المنتهية الصلاحية setTimeout(() => store.delete(key), ttl); return value; }; } const fetchData = cache((id) => { console.log(`Fetching data for ${id}`); return { id, data: '....' }; }, 5000); // 3. التكوين غير المتزامن function composeAsync(...functions) { return async function(initialValue) { let result = initialValue; for (const fn of functions.reverse()) { result = await fn(result); } return result; }; } const asyncAdd = async (x) => x + 1; const asyncDouble = async (x) => x * 2; const asyncSquare = async (x) => x ** 2; const processAsync = composeAsync(asyncSquare, asyncDouble, asyncAdd); processAsync(2).then(console.log); // ((2 + 1) * 2)^2 = 36

الملخص

في هذا الدرس، تعلمت:

  • الدوال هي مواطنون من الدرجة الأولى في JavaScript
  • الدوال عالية الرتبة تأخذ دوال كمعاملات أو ترجع دوال
  • الإغلاقات تمكّن الحالة الخاصة وتغليف البيانات
  • تكوين الدوال يدمج دوال متعددة في خطوط أنابيب
  • الكاري والتطبيق الجزئي ينشئان دوال متخصصة
  • الحفظ المؤقت يخزن النتائج مؤقتاً لتحسين الأداء
  • الأنماط العملية: debounce و throttle و once و retry
  • الدوال عالية الرتبة تمكّن أنماط كود أنيقة وقابلة لإعادة الاستخدام
التالي: في الدرس التالي، سنأخذ نظرة عميقة على الإغلاقات ونستكشف أنماط وحالات استخدام الإغلاقات المتقدمة!

ES
Edrees Salih
منذ 18 ساعة

We are still cooking the magic in the way!