Seperti yang sudah kita ketahui, fungsi dalam JavaScript adalah sebuah nilai.
Setiap nilai dalam JavaScript memiliki tipe. Tipe apakah suatu fungsi?
Dalam JavaScript, fungsi adalah objek.
Cara yang baik untuk membayangkan fungsi adalah sebagai “objek tindakan” yang dapat dipanggil. Kita tidak hanya bisa memanggilnya, tapi juga memperlakukannya sebagai objek: menambah/menghapus properti, meneruskan referensi, dll.
Objek fungsi berisi beberapa properti yang bisa digunakan.
Misalnya, nama fungsi dapat diakses sebagai properti “nama”:
fungsi ucapkan Hai() { peringatan("Hai"); } alert(sayHi.name); // sapa
Lucunya, logika pemberian nama itu cerdas. Itu juga memberikan nama yang benar ke suatu fungsi meskipun itu dibuat tanpa fungsi, dan kemudian segera ditetapkan:
katakanlah Hai = fungsi() { peringatan("Hai"); }; alert(sayHi.name); // sayHi (ada nama!)
Ini juga berfungsi jika penugasan dilakukan melalui nilai default:
fungsi f(sayHi = fungsi() {}) { alert(sayHi.name); // sayHi (berhasil!) } F();
Dalam spesifikasinya, fitur ini disebut “nama kontekstual”. Jika fungsi tidak menyediakannya, maka dalam suatu penugasan hal itu ditentukan dari konteksnya.
Metode objek juga mempunyai nama:
biarkan pengguna = { ucapkan Hai() { // ... }, ucapkan selamat tinggal: fungsi() { // ... } } alert(pengguna.sayHi.name); // sapa alert(pengguna.sayBye.nama); // ucapkan selamat tinggal
Tapi tidak ada keajaiban. Ada kalanya tidak ada cara untuk mengetahui nama yang tepat. Dalam hal ini, properti name kosong, seperti di sini:
// fungsi dibuat di dalam array biarkan arr = [fungsi() {}]; peringatan( arr[0].nama ); // <string kosong> // mesin tidak mempunyai cara untuk mengatur nama yang tepat, jadi tidak ada
Namun dalam praktiknya, sebagian besar fungsi memiliki nama.
Ada properti “panjang” bawaan lainnya yang mengembalikan jumlah parameter fungsi, misalnya:
fungsi f1(a) {} fungsi f2(a, b) {} fungsi banyak(a, b, ...lebih lanjut) {} peringatan(f1.panjang); // 1 alert(f2.panjang); // 2 alert(banyak.panjang); // 2
Di sini kita dapat melihat bahwa parameter istirahat tidak dihitung.
Properti length
terkadang digunakan untuk introspeksi pada fungsi yang beroperasi pada fungsi lain.
Misalnya, pada kode di bawah, fungsi ask
menerima question
untuk ditanyakan dan sejumlah fungsi handler
yang dapat dipanggil.
Setelah pengguna memberikan jawabannya, fungsi tersebut memanggil penangan. Kita dapat melewati dua jenis penangan:
Fungsi argumen nol, yang hanya dipanggil ketika pengguna memberikan jawaban positif.
Sebuah fungsi dengan argumen, yang dipanggil dalam kedua kasus dan mengembalikan jawaban.
Untuk memanggil handler
dengan cara yang benar, kita memeriksa properti handler.length
.
Idenya adalah kami memiliki sintaks penangan yang sederhana dan tanpa argumen untuk kasus positif (varian paling sering), namun mampu mendukung penangan universal juga:
fungsi bertanya(pertanyaan, ...penanganan) { biarkan isYes = konfirmasi(pertanyaan); for(biarkan penangan dari penangan) { if (penangan.panjang == 0) { jika (ya) handler(); } kalau tidak { pawang(adalahYa); } } } // untuk jawaban positif, kedua handler dipanggil // untuk jawaban negatif, hanya yang kedua bertanya("Pertanyaan?", () => alert('Anda menjawab ya'), result => alert(hasil));
Ini adalah kasus khusus yang disebut polimorfisme – memperlakukan argumen secara berbeda bergantung pada tipenya atau, dalam kasus kami, bergantung pada length
. Idenya memang berguna di perpustakaan JavaScript.
Kita juga dapat menambahkan properti kita sendiri.
Di sini kita menambahkan properti counter
untuk melacak jumlah total panggilan:
fungsi ucapkan Hai() { peringatan("Hai"); // mari kita hitung berapa kali kita berlari sayHi.counter++; } sayHi.counter = 0; // nilai awal sayHai(); // Hai sayHai(); // Hai alert( `Dipanggil ${sayHi.counter} kali` ); // Ditelepon 2 kali
Properti bukanlah variabel
Properti yang ditugaskan ke fungsi seperti sayHi.counter = 0
tidak mendefinisikan variabel lokal counter
di dalamnya. Dengan kata lain, counter
properti dan let counter
adalah dua hal yang tidak berhubungan.
Kita dapat memperlakukan fungsi sebagai objek, menyimpan properti di dalamnya, tapi itu tidak berpengaruh pada eksekusinya. Variabel bukan merupakan properti fungsi dan sebaliknya. Ini hanyalah dunia paralel.
Properti fungsi terkadang dapat menggantikan penutupan. Misalnya, kita dapat menulis ulang contoh fungsi penghitung dari bab Cakupan variabel, penutupan untuk menggunakan properti fungsi:
fungsi makeCounter() { // alih-alih: // biarkan hitungan = 0 penghitung fungsi() { return counter.count++; }; counter.hitungan = 0; konter pengembalian; } biarkan penghitung = makeCounter(); waspada( counter() ); // 0 waspada( counter() ); // 1
count
kini disimpan di fungsi secara langsung, bukan di Lingkungan Leksikal bagian luarnya.
Apakah lebih baik atau lebih buruk daripada menggunakan penutupan?
Perbedaan utamanya adalah jika nilai count
berada di variabel luar, maka kode eksternal tidak dapat mengaksesnya. Hanya fungsi bersarang yang dapat mengubahnya. Dan jika itu terikat pada suatu fungsi, maka hal seperti itu mungkin terjadi:
fungsi makeCounter() { penghitung fungsi() { return counter.count++; }; counter.hitungan = 0; konter pengembalian; } biarkan penghitung = makeCounter(); counter.hitungan = 10; waspada( counter() ); // 10
Jadi pilihan penerapannya tergantung pada tujuan kita.
Ekspresi Fungsi Bernama, atau NFE, adalah istilah untuk Ekspresi Fungsi yang memiliki nama.
Sebagai contoh, mari kita ambil Ekspresi Fungsi biasa:
misalkan sayHi = fungsi(siapa) { alert(`Halo, ${siapa}`); };
Dan tambahkan nama padanya:
misalkan sayHi = fungsi func(siapa) { alert(`Halo, ${siapa}`); };
Apakah kita mencapai sesuatu di sini? Apa tujuan dari nama "func"
tambahan itu?
Pertama mari kita perhatikan, bahwa kita masih memiliki Ekspresi Fungsi. Menambahkan nama "func"
setelah function
tidak menjadikannya Deklarasi Fungsi, karena masih dibuat sebagai bagian dari ekspresi penugasan.
Menambahkan nama seperti itu juga tidak merusak apa pun.
Fungsi ini masih tersedia sebagai sayHi()
:
misalkan sayHi = fungsi func(siapa) { alert(`Halo, ${siapa}`); }; sayHi("Yohanes"); // Halo, John
Ada dua hal khusus tentang nama func
, itulah alasannya:
Hal ini memungkinkan fungsi untuk mereferensikan dirinya sendiri secara internal.
Itu tidak terlihat di luar fungsinya.
Misalnya, fungsi sayHi
di bawah memanggil dirinya lagi dengan "Guest"
jika tidak who
yang disediakan:
misalkan sayHi = fungsi func(siapa) { jika (siapa) { alert(`Halo, ${siapa}`); } kalau tidak { func("Tamu"); // gunakan func untuk memanggil ulang dirinya sendiri } }; sayHai(); // Halo, Tamu // Tapi ini tidak akan berhasil: fungsi(); // Kesalahan, fungsi tidak ditentukan (tidak terlihat di luar fungsi)
Mengapa kita menggunakan func
? Mungkin cukup gunakan sayHi
untuk panggilan bersarang?
Sebenarnya, dalam banyak kasus kita dapat:
misalkan sayHi = fungsi(siapa) { jika (siapa) { alert(`Halo, ${siapa}`); } kalau tidak { sayHi("Tamu"); } };
Masalah dengan kode tersebut adalah sayHi
dapat berubah pada kode luarnya. Jika fungsi tersebut ditugaskan ke variabel lain, kode akan mulai memberikan kesalahan:
misalkan sayHi = fungsi(siapa) { jika (siapa) { alert(`Halo, ${siapa}`); } kalau tidak { sayHi("Tamu"); // Kesalahan: sayHi bukan sebuah fungsi } }; biarkan selamat datang = sayHi; sayHi = null; selamat datang(); // Kesalahan, panggilan sayHi yang disarangkan tidak berfungsi lagi!
Hal ini terjadi karena fungsi tersebut mengambil sayHi
dari lingkungan leksikal luarnya. Tidak ada sayHi
lokal, jadi variabel luar digunakan. Dan pada saat panggilan, sayHi
bagian luar itu adalah null
.
Nama opsional yang dapat kita masukkan ke dalam Ekspresi Fungsi dimaksudkan untuk menyelesaikan masalah seperti ini.
Mari kita gunakan untuk memperbaiki kode kita:
misalkan sayHi = fungsi func(siapa) { jika (siapa) { alert(`Halo, ${siapa}`); } kalau tidak { func("Tamu"); // Sekarang semuanya baik-baik saja } }; biarkan selamat datang = sayHi; sayHi = null; selamat datang(); // Halo, Tamu (panggilan bersarang berfungsi)
Sekarang berfungsi, karena nama "func"
adalah fungsi-lokal. Itu tidak diambil dari luar (dan tidak terlihat di sana). Spesifikasinya menjamin bahwa ia akan selalu mereferensikan fungsi saat ini.
Kode luarnya masih memiliki variabel sayHi
atau welcome
. Dan func
adalah "nama fungsi internal", cara agar fungsi tersebut dapat memanggil dirinya sendiri dengan andal.
Tidak ada yang namanya Deklarasi Fungsi
Fitur “nama internal” yang dijelaskan di sini hanya tersedia untuk Ekspresi Fungsi, bukan untuk Deklarasi Fungsi. Untuk Deklarasi Fungsi, tidak ada sintaks untuk menambahkan nama “internal”.
Terkadang, ketika kita membutuhkan nama internal yang dapat diandalkan, itulah alasannya untuk menulis ulang Deklarasi Fungsi menjadi formulir Ekspresi Fungsi Bernama.
Fungsi adalah objek.
Di sini kami membahas propertinya:
name
– nama fungsi. Biasanya diambil dari definisi fungsi, tetapi jika tidak ada, JavaScript mencoba menebaknya dari konteksnya (misalnya tugas).
length
– jumlah argumen dalam definisi fungsi. Parameter istirahat tidak dihitung.
Jika fungsi dideklarasikan sebagai Ekspresi Fungsi (bukan dalam alur kode utama), dan memiliki nama, maka disebut Ekspresi Fungsi Bernama. Nama tersebut dapat digunakan di dalam untuk merujuk dirinya sendiri, untuk panggilan rekursif atau semacamnya.
Selain itu, fungsi mungkin membawa properti tambahan. Banyak perpustakaan JavaScript terkenal yang memanfaatkan fitur ini dengan baik.
Mereka membuat fungsi “utama” dan melampirkan banyak fungsi “pembantu” lainnya ke dalamnya. Misalnya, perpustakaan jQuery membuat fungsi bernama $
. Pustaka lodash membuat fungsi _
, lalu menambahkan _.clone
, _.keyBy
dan properti lainnya ke dalamnya (lihat dokumen jika Anda ingin mempelajarinya lebih lanjut). Sebenarnya, mereka melakukannya untuk mengurangi polusi terhadap ruang global, sehingga satu perpustakaan hanya memberikan satu variabel global. Hal ini mengurangi kemungkinan penamaan konflik.
Jadi, suatu fungsi dapat melakukan pekerjaan yang berguna dengan sendirinya dan juga membawa banyak fungsi lainnya di properti.
pentingnya: 5
Ubah kode makeCounter()
agar penghitungnya juga bisa mengecil dan mengatur angkanya:
counter()
harus mengembalikan nomor berikutnya (seperti sebelumnya).
counter.set(value)
harus menyetel penghitung ke value
.
counter.decrease()
harus mengurangi penghitung sebesar 1.
Lihat kode sandbox untuk contoh penggunaan lengkap.
PS Anda dapat menggunakan properti penutupan atau fungsi untuk menyimpan hitungan saat ini. Atau tulis kedua variannya.
Buka kotak pasir dengan tes.
Solusinya menggunakan count
dalam variabel lokal, tetapi metode penambahan ditulis langsung ke counter
. Mereka berbagi lingkungan leksikal luar yang sama dan juga dapat mengakses count
saat ini.
fungsi makeCounter() { biarkan hitung = 0; penghitung fungsi() { jumlah pengembalian++; } counter.set = nilai => hitungan = nilai; counter.penurunan = () => hitungan--; konter pengembalian; }
Buka solusi dengan pengujian di kotak pasir.
pentingnya: 2
Tulis sum
fungsi yang akan berfungsi seperti ini:
jumlah(1)(2) == 3; // 1 + 2 jumlah(1)(2)(3) == 6; // 1 + 2 + 3 jumlah(5)(-1)(2) == 6 jumlah(6)(-1)(-2)(-3) == 0 jumlah(0)(1)(2)(3)(4)(5) == 15
Petunjuk PS: Anda mungkin perlu menyiapkan objek khusus ke konversi primitif untuk fungsi Anda.
Buka kotak pasir dengan tes.
Agar semuanya dapat berfungsi, hasil sum
harus berupa fungsi.
Fungsi itu harus menyimpan nilai saat ini di antara panggilan di memori.
Menurut tugasnya, fungsi tersebut harus menjadi angka ketika digunakan dalam ==
. Fungsi adalah objek, sehingga konversi terjadi seperti yang dijelaskan dalam bab Konversi objek ke primitif, dan kami dapat menyediakan metode kami sendiri yang mengembalikan nomor tersebut.
Sekarang kodenya:
jumlah fungsi(a) { misalkan Jumlah Saat Ini = a; fungsi f(b) { Jumlah saat ini += b; kembali f; } f.toString = fungsi() { kembalikan Jumlah Saat Ini; }; kembali f; } peringatan( jumlah(1)(2) ); // 3 peringatan( jumlah(5)(-1)(2) ); // 6 peringatan( jumlah(6)(-1)(-2)(-3) ); // 0 peringatan( jumlah(0)(1)(2)(3)(4)(5) ); // 15
Harap dicatat bahwa fungsi sum
sebenarnya hanya berfungsi sekali. Ini mengembalikan fungsi f
.
Kemudian, pada setiap panggilan berikutnya, f
menambahkan parameternya ke jumlah currentSum
, dan mengembalikan parameternya sendiri.
Tidak ada rekursi di baris terakhir f
.
Berikut tampilan rekursinya:
fungsi f(b) { Jumlah saat ini += b; kembalikan f(); // <-- panggilan rekursif }
Dan dalam kasus kami, kami hanya mengembalikan fungsinya, tanpa memanggilnya:
fungsi f(b) { Jumlah saat ini += b; kembali f; // <-- tidak memanggil dirinya sendiri, mengembalikan dirinya sendiri }
f
ini akan digunakan pada panggilan berikutnya, kembali lagi, sebanyak yang diperlukan. Kemudian, ketika digunakan sebagai angka atau string – toString
mengembalikan currentSum
. Kita juga bisa menggunakan Symbol.toPrimitive
atau valueOf
di sini untuk konversi.
Buka solusi dengan pengujian di kotak pasir.