"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 :
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:
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; }
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) {
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');
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) {
Alih-alih memahami kode, pertama mari kita lihat bagaimana ini dapat diterapkan. Mari kita lihat
elListMap()
fungsi
elListMap()
:
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);
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; return -1; }
Sekarang urutkan array:
let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums);
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);
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 .