JavaScript Fungsional: apa fungsi tingkat tinggi dan mengapa diperlukan?


"Functions of a order lebih tinggi" adalah salah satu ungkapan yang sering tersebar. Tetapi jarang ada yang bisa berhenti dan menjelaskan apa itu. Anda mungkin sudah tahu apa yang disebut fungsi tingkat tinggi. Tetapi bagaimana kita menggunakannya dalam proyek nyata? Kapan dan mengapa mereka membantu? Bisakah kita memanipulasi DOM dengan bantuan mereka? Atau apakah orang yang menggunakan fitur ini hanya pamer? Mungkin mereka memperumit kodenya tanpa alasan?

Dulu saya berpikir bahwa fungsi tingkat tinggi berguna. Sekarang saya menganggap mereka sebagai properti paling penting dari JavaScript sebagai bahasa. Tapi sebelum kita membahas ini, mari kita cari tahu apa sebenarnya fungsi tingkat tinggi. Dan kita akan mulai dengan fungsi sebagai variabel.

Berfungsi sebagai Objek Kelas Pertama


Dalam JavaScript, setidaknya ada tiga cara (ada lebih banyak total) untuk menulis fungsi baru. Pertama, Anda dapat menulis deklarasi fungsi :

// 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; } 

Saya harap Anda mengerti segalanya. Juga, Anda mungkin tahu bahwa Anda dapat menulis ekspresi fungsi :

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

Dan akhirnya, ada cara lain untuk menulis fungsi yang sama - sebagai fungsi panah :

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

Dalam hal ini, ketiga metode tersebut setara. Meskipun ini tidak selalu terjadi, dalam praktiknya, setiap metode memiliki perbedaan kecil terkait dengan apa yang terjadi pada keajaiban kata kunci tertentu dan label dalam jejak tumpukan.

Tetapi perhatikan bahwa dua contoh terakhir menetapkan fungsi ke variabel. Itu terlihat seperti hal sepele. Mengapa tidak menetapkan fungsi ke variabel? Tetapi ini sangat penting. Fungsi dalam JavaScript termasuk dalam " kelas pertama ". Karena itu, kita dapat:

  • Tetapkan fungsi ke variabel.
  • Lewati fungsi sebagai argumen ke fungsi lain.
  • Kembalikan fungsi dari fungsi lain.

Ini luar biasa, tetapi apa hubungannya semua ini dengan fungsi tingkat tinggi? Perhatikan dua poin terakhir. Kami akan segera kembali kepada mereka, tetapi untuk sekarang mari kita lihat beberapa contoh.

Kami melihat penugasan fungsi ke variabel. Bagaimana dengan meneruskannya sebagai parameter? Mari kita menulis fungsi yang dapat digunakan dengan elemen DOM. Jika kita menjalankan document.querySelectorAll() , maka sebagai imbalannya kita tidak mendapatkan sebuah array, tetapi sebuah NodeList . NodeList tidak memiliki metode .map() , seperti array, jadi kami menulis ini:

 // 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); 

Di sini kita meneruskan fungsi itemise sebagai argumen ke fungsi elListMap . Tetapi kita dapat menggunakan elListMap tidak hanya untuk membuat daftar. Misalnya, dengan bantuannya, Anda bisa menambahkan kelas ke sekumpulan elemen:

 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 mengambil fungsi lain sebagai parameter dan mengkonversi. Artinya, kita dapat menggunakan elListMap untuk menyelesaikan masalah yang berbeda.

Kami melihat contoh fungsi lewat sebagai parameter. Sekarang mari kita bicara tentang mengembalikan suatu fungsi dari suatu fungsi. Seperti apa bentuknya?

Pertama, kita menulis fungsi lama yang biasa. Kita perlu mengambil daftar elemen li dan membungkusnya dengan ul . Mudah:

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

Dan jika kemudian kita memiliki banyak elemen paragraf yang ingin kita bungkus dalam div ? Tidak masalah, kami akan menulis satu fungsi lagi untuk ini:

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

Ini bekerja dengan baik. Namun, kedua fungsi ini sangat mirip, satu-satunya perbedaan adalah pada elemen induk yang kami buat.

Sekarang kita bisa menulis fungsi yang mengambil dua parameter: tipe elemen induk dan daftar elemen turunan. Tetapi ada opsi lain. Kita dapat membuat fungsi yang mengembalikan fungsi. Sebagai contoh:

 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); } } 

Pada awalnya mungkin terlihat sedikit rumit, jadi mari kita pisahkan kodenya. Kami membuat fungsi yang baru saja mengembalikan fungsi lain. Tetapi fungsi pengembalian ini mengingat parameter elementType . Dan kemudian, ketika kita memanggil fungsi yang dikembalikan, ia sudah tahu elemen mana yang harus dibuat. Oleh karena itu, Anda dapat membuat wrapWithUl dan 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. 

Trik ini, ketika fungsi yang dikembalikan "mengingat" sesuatu, disebut penutupan . Anda dapat membaca lebih lanjut tentang mereka di sini . Penutupan sangat nyaman, tetapi untuk saat ini, kami tidak akan memikirkannya.

Jadi, kami beres:

  • Menetapkan fungsi ke variabel.
  • Melewati fungsi sebagai parameter.
  • Mengembalikan fungsi dari fungsi lain ...

Secara umum, fungsi kelas satu adalah hal yang menyenangkan. Tapi apa fungsi fungsi tingkat tinggi dengan itu? Mari kita lihat definisinya.

Apa itu fungsi tingkat tinggi?


Definisi : ini adalah fungsi yang mengambil fungsi sebagai argumen atau mengembalikan fungsi sebagai hasilnya.

Apakah itu familier? Dalam JavaScript, ini adalah fungsi kelas satu. Artinya, "fungsi tingkat tinggi" memiliki kelebihan yang persis sama. Dengan kata lain, itu hanya nama yang aneh untuk ide sederhana.

Contoh fungsi orde tinggi


Jika Anda mulai mencari, maka Anda mulai memperhatikan fungsi tingkat tinggi di mana-mana. Yang paling umum adalah fungsi yang mengambil fungsi lain sebagai parameter.

Fungsi yang mengambil fungsi lain sebagai parameter


Ketika Anda melewati panggilan balik, Anda menggunakan fungsi urutan yang lebih tinggi. Dalam pengembangan front-end, mereka ditemukan di mana-mana. Salah satu yang paling umum adalah metode .addEventListener() . Kami menggunakannya ketika kami ingin melakukan tindakan sebagai respons terhadap beberapa peristiwa. Misalnya, saya ingin membuat tombol yang menampilkan peringatan:

 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); 

Di sini kami membuat fungsi yang menampilkan peringatan, menambahkan tombol ke halaman, dan meneruskan fungsi showAlert() sebagai argumen ke btn.addEventListener() .

Kami juga menemukan fungsi tingkat tinggi ketika kami menggunakan metode iterasi array : misalnya, .map() , .filter() dan .reduce() . Seperti pada fungsi elListMap() :

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

Fungsi tingkat tinggi juga membantu bekerja dengan penundaan dan pengaturan waktu. Fungsi setTimeout() dan setInterval() membantu mengontrol kapan fungsi dijalankan. Misalnya, jika Anda perlu menghapus kelas highlight setelah 30 detik, Anda dapat melakukan ini seperti ini:

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

Sekali lagi, kami membuat fungsi dan meneruskannya ke fungsi lain sebagai argumen.

Seperti yang Anda lihat, JavaScript seringkali memiliki fungsi yang menerima fungsi lain. Dan Anda mungkin sudah menggunakannya.

Fungsi Mengembalikan Fungsi


Fungsi semacam ini tidak ditemukan sesering yang sebelumnya. Tetapi mereka juga berguna. Salah satu contoh terbaik adalah fungsi mungkin () . Saya mengadaptasi varian dari buku JavaScript Allongรฉ :

 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); } } 

Alih-alih memahami kode, pertama mari kita lihat bagaimana ini dapat diterapkan. Mari kita lihat elListMap() fungsi 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); } 

Apa yang terjadi jika saya secara tidak sengaja melewatkan null atau nilai yang tidak ditentukan ke elListMap() ? Kami akan mendapatkan TypeError dan jatuhnya operasi saat ini, apa pun itu. Ini dapat dihindari dengan fungsi maybe() :

 const safeElListMap = maybe(elListMap); safeElListMap(x => x, null); // ๏ฟฉ undefined 

Alih-alih jatuh, fungsi akan kembali undefined . Dan jika kita meneruskan ini ke fungsi lain yang dilindungi oleh maybe() , maka kita akan kembali undefined . maybe() dapat melindungi sejumlah fungsi, jauh lebih mudah untuk menulis satu miliar if .

Fungsi yang mengembalikan fungsi juga umum di dunia Bereaksi. Misalnya, connect() .

Jadi apa selanjutnya?


Kami melihat beberapa contoh penggunaan fungsi tingkat tinggi. Jadi apa selanjutnya? Apa yang bisa mereka berikan kepada kita apa yang tidak bisa kita dapatkan tanpa mereka?

Untuk menjawab pertanyaan ini, mari kita lihat contoh lain - metode array .sort() . Ya, dia memiliki kekurangan. Itu mengubah array bukannya mengembalikan yang baru. Tapi mari kita lupakan saja untuk saat ini. Metode .sort() adalah fungsi tingkat tinggi, mengambil fungsi lain sebagai salah satu parameter.

Bagaimana cara kerjanya? Jika kita ingin mengurutkan array angka, pertama-tama kita perlu membuat fungsi perbandingan:

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

Sekarang urutkan array:

 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] 

Anda dapat mengurutkan daftar angka. Tapi apa gunanya itu? Seberapa sering kita memiliki daftar angka untuk disortir? Tidak sering. Biasanya saya perlu mengurutkan array objek:

 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'], } ]; 

Misalkan saya ingin mengurutkan array ini berdasarkan bobot dari setiap record. Saya bisa menulis fungsi sortir baru dari awal. Tetapi mengapa, jika Anda dapat membuat fungsi perbandingan baru:

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

Anda dapat menulis fungsi perbandingan untuk segala jenis array. Metode .sort() membantu kami: โ€œJika Anda memberi saya fungsi perbandingan, saya akan mengurutkan setiap array. Jangan khawatir tentang isinya. Jika Anda memberikan fungsi pengurutan, saya akan mengurutkannya. " Oleh karena itu, kita tidak perlu menulis algoritma penyortiran sendiri, kita akan fokus pada tugas yang lebih sederhana untuk membandingkan dua elemen.

Sekarang bayangkan kita tidak menggunakan fungsi tingkat tinggi. Kami tidak dapat meneruskan fungsi ke metode .sort() . Kita harus menulis fungsi sortir baru setiap kali kita perlu menyortir array yang berbeda. Atau Anda harus menemukan kembali hal yang sama dengan pointer fungsi atau objek. Bagaimanapun, itu akan menjadi sangat canggung.

Namun, kami memiliki fungsi urutan yang lebih tinggi yang memungkinkan kami untuk memisahkan fungsi pengurutan dari fungsi perbandingan. Katakanlah pengembang peramban yang cerdas telah memperbarui .sort() untuk menggunakan algoritma yang lebih cepat. Maka kode Anda hanya akan menang, terlepas dari apa yang ada di dalam array yang dapat diurutkan. Dan skema ini berlaku untuk seluruh rangkaian fungsi susunan tingkat tinggi .

Ini membawa kita ke ide semacam itu. Metode .sort() mengabstraksi tugas penyortiran dari isi array. Ini disebut pemisahan keprihatinan. Fungsi tingkat tinggi memungkinkan Anda untuk membuat abstraksi yang tanpanya akan sangat rumit atau bahkan tidak mungkin. Dan pembuatan abstraksi menyumbang 80% dari pekerjaan insinyur perangkat lunak.

Saat kami memperbaiki kode untuk menghapus pengulangan, kami membuat abstraksi. Kami melihat pola dan menggantinya dengan representasi abstrak. Akibatnya, kode menjadi lebih bermakna dan lebih mudah dipahami. Setidaknya itulah tujuannya.

Fungsi tingkat tinggi adalah alat yang ampuh untuk membuat abstraksi. Dan dengan abstraksi, seluruh cabang matematika, teori kategori, dikaitkan. Lebih tepatnya, teori kategori dikhususkan untuk mencari abstraksi abstraksi. Dengan kata lain, kita berbicara tentang menemukan pola pola. Dan selama 70 tahun terakhir, programmer yang pintar telah meminjam banyak ide dari sana, yang telah berubah menjadi properti bahasa dan perpustakaan. Jika kita mempelajari pola-pola ini, maka kadang-kadang kita dapat mengganti potongan kode yang besar. Atau sederhanakan masalah kompleks menjadi kombinasi elegan dari blok bangunan sederhana. Blok-blok ini adalah fungsi urutan yang lebih tinggi. Karena itu, mereka sangat penting, mereka memberi kita alat yang kuat untuk memerangi kompleksitas kode kita.

Materi tambahan tentang fungsi tingkat tinggi:


Anda mungkin sudah menggunakan fungsi tingkat tinggi. Ini sangat mudah dalam JavaScript sehingga kami bahkan tidak memikirkannya. Tetapi lebih baik untuk mengetahui apa yang dibicarakan orang ketika mereka mengucapkan kalimat ini. Ini tidak sulit. Namun di balik ide sederhana ada banyak kekuatan.

Jika Anda berpengalaman dalam pemrograman fungsional, Anda mungkin memperhatikan bahwa saya menggunakan bukan fungsi murni dan beberapa ... nama fungsi verbose. Ini bukan karena saya belum pernah mendengar tentang fungsi yang tidak bersih atau prinsip umum pemrograman fungsional. Dan saya tidak menulis kode seperti itu dalam produksi. Saya mencoba mengambil contoh-contoh praktis yang jelas bagi pemula. Terkadang saya harus berkompromi. Jika tertarik, saya sudah menulis tentang kebersihan fungsional dan prinsip umum pemrograman fungsional .

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


All Articles