جافا سكريبت الوظيفية: ما هي الوظائف العليا ولماذا تكون هناك حاجة إليها؟


"وظائف الرتبة العليا" هي واحدة من العبارات التي تنتشر في كثير من الأحيان. ولكن نادرا ما يمكن لأي شخص أن يوقف وشرح ما هو عليه. قد تعرف بالفعل ما يسمى وظائف الترتيب العالي. ولكن كيف نستخدمها في مشاريع حقيقية؟ متى ولماذا هم مفيدة؟ هل يمكننا التعامل مع DOM بمساعدتهم؟ أو هل يستخدم الأشخاص الذين يستخدمون هذه الميزات؟ ربما هم تعقيد رمز؟

اعتدت أن أعتقد أن وظائف الترتيب العالي مفيدة. أنا الآن أعتبرهم أهم خاصية في JavaScript كلغة. ولكن قبل أن نناقش هذا الأمر ، دعونا أولاً نكتشف بالضبط ما هي الوظائف العليا. وسوف نبدأ بالوظائف كمتغيرات.

وظائف ككائنات من الدرجة الأولى


في جافا سكريبت ، هناك ثلاث طرق على الأقل (هناك أكثر في المجموع) لكتابة وظيفة جديدة. أولاً ، يمكنك كتابة إعلان دالة :

// Take a DOM element and wrap it in a list item element. function itemise(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

أتمنى أن تفهم كل شيء. أيضًا ، ربما تعرف أنه يمكنك كتابة تعبير دالة :

 const itemise = function(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

وأخيراً ، هناك طريقة أخرى لكتابة نفس الوظيفة - كدالة سهم :

 const itemise = (el) => { const li = document.createElement('li'); li.appendChild(el); return li; } 

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

لكن لاحظ أن المثالين الأخيرين يعينان الدالة إلى متغير. يبدو وكأنه تافه. لماذا لا تقوم بتعيين دالة إلى متغير؟ ولكن هذا مهم جدا. وظائف في جافا سكريبت تنتمي إلى " الدرجة الأولى ". لذلك ، يمكننا:

  • تخصيص وظائف للمتغيرات.
  • تمرير وظائف كوسائط إلى وظائف أخرى.
  • عودة وظائف من وظائف أخرى.

هذا رائع ، لكن ما علاقة كل هذا بوظائف الرتب العليا؟ إيلاء الاهتمام لآخر نقطتين. قريباً سنعود إليهم ، ولكن الآن لنلقي نظرة على بعض الأمثلة.

رأينا احالة وظائف للمتغيرات. ماذا عن تمريرها كمعلمات؟ دعنا نكتب وظيفة يمكن استخدامها مع عناصر DOM. إذا قمنا بتنفيذ document.querySelectorAll() ، في المقابل ، لن نحصل على صفيف ، ولكننا NodeList . ليس لدى NodeList طريقة .map() ، مثل المصفوفات ، لذلك نكتب هذا:

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } // Grab all the spans on the page with the class 'for-listing'. const mySpans = document.querySelectorAll('span.for-listing'); // Wrap each one inside an <li> element. We re-use the // itemise() function from earlier. const wrappedList = elListMap(itemise, mySpans); 

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

 function addSpinnerClass(el) { el.classList.add('spinner'); return el; } // Find all the buttons with class 'loader' const loadButtons = document.querySelectorAll('button.loader'); // Add the spinner class to all the buttons we found. elListMap(addSpinnerClass, loadButtons); 

elLlistMap يأخذ وظيفة أخرى elLlistMap . وهذا هو ، يمكننا استخدام elListMap لحل مشاكل مختلفة.

نظرنا إلى مثال لتمرير الوظائف كمعلمات. الآن دعونا نتحدث عن إرجاع وظيفة من وظيفة. كيف تبدو؟

أولاً ، نكتب الوظيفة القديمة المعتادة. نحن بحاجة إلى اتخاذ قائمة من العناصر li والتفاف في ul . سهل:

 function wrapWithUl(children) { const ul = document.createElement('ul'); return [...children].reduce((listEl, child) => { listEl.appendChild(child); return listEl; }, ul); } 

وإذا كان لدينا بعد ذلك مجموعة من عناصر الفقرة التي نريد لفها في div ؟ لا مشكلة ، سنكتب وظيفة أخرى لهذا:

 function wrapWithDiv(children) { const div = document.createElement('div'); return [...children].reduce((divEl, child) => { divEl.appendChild(child); return divEl; }, div); } 

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

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

 function createListWrapperFunction(elementType) { // Straight away, we return a function. return function wrap(children) { // Inside our wrap function, we can 'see' the elementType parameter. const parent = document.createElement(elementType); return [...children].reduce((parentEl, child) => { parentEl.appendChild(child); return parentEl; }, parent); } } 

قد تبدو معقدة بعض الشيء في البداية ، لذلك دعونا نقسم الشفرة. لقد أنشأنا وظيفة ترجع فقط وظيفة أخرى. لكن دالة الإرجاع هذه تتذكر معلمة elementType . وبعد ذلك ، عندما نسمي الوظيفة التي تم إرجاعها ، فإنها تعرف بالفعل أي عنصر سيتم إنشاؤه. لذلك ، يمكنك إنشاء wrapWithUl و wrapWithDiv :

 const wrapWithUl = createListWrapperFunction('ul'); // Our wrapWithUl() function now 'remembers' that it creates a ul element. const wrapWithDiv = createListWreapperFunction('div'); // Our wrapWithDiv() function now 'remembers' that it creates a div element. 

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

لذلك ، نحن فرزها:

  • تعيين دالة إلى متغير.
  • تمرير وظيفة كمعلمة.
  • إرجاع دالة من وظيفة أخرى ...

بشكل عام ، وظائف الطبقة الأولى هي شيء لطيف. ولكن ما علاقة الأمر بالترتيب الأعلى ؟ دعنا ننظر إلى التعريف.

ما هي وظيفة النظام العالي؟


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

هل هذا مألوف؟ في JavaScript ، هذه وظائف من الدرجة الأولى. وهذا هو ، "وظائف ترتيب أعلى" لديها بالضبط نفس المزايا. بمعنى آخر ، إنه مجرد اسم خيالي لفكرة بسيطة.

أمثلة الدالة العليا


إذا بدأت في البحث ، فإنك تبدأ في ملاحظة وظائف الترتيب العالي في كل مكان. الأكثر شيوعًا هي الوظائف التي تأخذ وظائف أخرى كمعلمات.

الوظائف التي تأخذ وظائف أخرى كمعلمات


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

 function showAlert() { alert('Fallacies do not cease to be fallacies because they become fashions'); } document.body.innerHTML += `<button type="button" class="js-alertbtn"> Show alert </button>`; const btn = document.querySelector('.js-alertbtn'); btn.addEventListener('click', showAlert); 

أنشأنا هنا دالة تعرض تحذيرًا ، showAlert() زرًا إلى الصفحة ، showAlert() دالة showAlert() كوسيطة إلى btn.addEventListener() .

نواجه أيضًا وظائف ذات ترتيب أعلى عندما نستخدم أساليب تكرار الصفيف : على سبيل المثال .map() و .filter() و .reduce() . كما هو الحال في وظيفة elListMap() :

 function elListMap(transform, list) { return [...list].map(transform); } 

تساعد الوظائف العليا أيضًا في العمل مع التأخير والتوقيت. تساعد setTimeout() و setInterval() على التحكم في تنفيذ الدالات. على سبيل المثال ، إذا كنت بحاجة إلى إزالة فئة highlight بعد 30 ثانية ، يمكنك القيام بذلك مثل هذا:

 function removeHighlights() { const highlightedElements = document.querySelectorAll('.highlighted'); elListMap(el => el.classList.remove('highlighted'), highlightedElements); } setTimeout(removeHighlights, 30000); 

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

كما ترون ، غالبًا ما تحتوي JavaScript على وظائف تقبل وظائف أخرى. وربما كنت بالفعل استخدامها.

وظائف العودة وظائف


لم يتم العثور على وظائف من هذا النوع في كثير من الأحيان مثل الوظائف السابقة. لكنها مفيدة أيضا. واحدة من أفضل الأمثلة هي الدالة ربما () . لقد عدلت شكلًا مختلفًا عن كتاب Allongé JavaScript :

 function maybe(fn) return function _maybe(...args) { // Note that the == is deliberate. if ((args.length === 0) || args.some(a => (a == null)) { return undefined; } return fn.apply(this, args); } } 

بدلاً من فهم الكود ، دعونا أولاً نرى كيف يمكن تطبيقه. دعنا ننظر إلى وظيفة elListMap() :

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } 

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

 const safeElListMap = maybe(elListMap); safeElListMap(x => x, null); // ← undefined 

بدلاً من السقوط ، ستعود الوظيفة undefined . وإذا قمنا بتمرير هذا إلى وظيفة أخرى محمية بواسطة maybe() ، undefined مرة أخرى على undefined . maybe() يمكن أن تحمي أي عدد من الوظائف ، من الأسهل بكثير كتابة مليار كلمة.

وظائف التي ترجع وظائف شائعة أيضا في عالم رد الفعل. على سبيل المثال ، connect() .

إذن ماذا بعد؟


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

للإجابة على هذا السؤال ، دعونا نلقي نظرة على مثال آخر - طريقة المصفوفة .sort() . نعم ، لديه عيوب. يغير الصفيف بدلاً من إرجاع واحدة جديدة. ولكن دعنا ننسى الأمر الآن. طريقة .sort() هي وظيفة ذات ترتيب عالي ؛ وهي تأخذ وظيفة أخرى كأحد المعلمات.

كيف يعمل؟ إذا أردنا فرز مجموعة من الأرقام ، نحتاج أولاً إلى إنشاء دالة مقارنة:

 function compareNumbers(a, b) { if (a === b) return 0; if (a > b) return 1; /* else */ return -1; } 

الآن فرز مجموعة:

 let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums); // 〕[1, 2, 3, 4, 5, 6, 7, 8, 9] 

يمكنك فرز قوائم الأرقام. ولكن ما هو جيد؟ كم مرة لدينا قائمة من الأرقام لفرزها؟ ليس كثيرا. عادة ما أحتاج إلى فرز مجموعة من الكائنات:

 let typeaheadMatches = [ { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'bog', weight: 0.5, matchedChars: ['bog'], }, { keyword: 'boggle', weight: 0.3, matchedChars: ['bog'], }, { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'toboggan', weight: 0.15, matchedChars: ['bog'], }, { keyword: 'bag', weight: 0.1, matchedChars: ['b', 'g'], } ]; 

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

 function compareTypeaheadResult(word1, word2) { return -1 * compareNumbers(word1.weight, word2.weight); } typeaheadMatches.sort(compareTypeaheadResult); console.log(typeaheadMatches); // 〕[{keyword: "bog", weight: 0.5, matchedChars: ["bog"]}, … ] 

يمكنك كتابة وظيفة مقارنة لأي نوع من الصفيف. .sort() طريقة .sort() : "إذا أعطيتني دالة مقارنة ، فسوف أفرز أي صفيف. لا تقلق بشأن محتوياته. إذا أعطيت وظيفة الفرز ، فسوف أفرزها ". لذلك ، لا نحتاج إلى كتابة خوارزمية الفرز بمفردنا ، فسنركز على المهمة الأكثر بساطة وهي مقارنة عنصرين.

الآن تخيل أننا لا نستخدم وظائف الترتيب العالي. لا يمكننا تمرير دالة إلى الأسلوب .sort() . سيتعين علينا كتابة وظيفة فرز جديدة في كل مرة نحتاج فيها إلى فرز مجموعة من نوع مختلف. أو عليك إعادة اختراع نفس الشيء باستخدام مؤشرات الوظائف أو الكائنات. في أي حال ، سوف تتحول بشكل محرج للغاية.

ومع ذلك ، لدينا وظائف ترتيب أعلى تسمح لنا بفصل وظيفة الفرز عن وظيفة المقارنة. لنفترض أن مطور برامج المتصفح الذكي قام بتحديث .sort() لاستخدام خوارزمية أسرع. ثم سيفوز الرمز الخاص بك فقط ، بغض النظر عن ما هو داخل المصفوفات القابلة للفرز. وهذا المخطط صحيح بالنسبة لمجموعة كاملة من وظائف صفائف النظام العالي .

هذا يقودنا إلى هذه الفكرة. .sort() الأسلوب .sort() مهمة الفرز من محتويات الصفيف. وهذا ما يسمى فصل المخاوف. تسمح لك الدرجات العليا بإنشاء أعمال تجريدية والتي بدونها ستكون مرهقة للغاية أو حتى مستحيلة. وإنشاء حسابات تجريدية تمثل 80٪ من عمل مهندسي البرمجيات.

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

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

مواد إضافية حول وظائف الترتيب العالي:


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

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

Source: https://habr.com/ru/post/ar458896/


All Articles