JavaScript adalah bahasa yang sangat berorientasi pada fungsi. Ini memberi kita banyak kebebasan. Suatu fungsi dapat dibuat kapan saja, diteruskan sebagai argumen ke fungsi lain, dan kemudian dipanggil dari tempat kode yang berbeda.
Kita telah mengetahui bahwa suatu fungsi dapat mengakses variabel di luarnya (variabel “luar”).
Namun apa yang terjadi jika variabel luar berubah sejak suatu fungsi dibuat? Akankah fungsi tersebut mendapatkan nilai yang lebih baru atau yang lama?
Dan bagaimana jika suatu fungsi diteruskan sebagai argumen dan dipanggil dari tempat kode lain, apakah fungsi tersebut akan mendapatkan akses ke variabel luar di tempat baru?
Mari kita perluas pengetahuan kita untuk memahami skenario ini dan skenario yang lebih kompleks.
Kita akan berbicara tentang variabel let/const
di sini
Dalam JavaScript, ada 3 cara mendeklarasikan variabel: let
, const
(yang modern), dan var
(sisa dari masa lalu).
Pada artikel ini kita akan menggunakan variabel let
sebagai contoh.
Variabel yang dideklarasikan dengan const
, berperilaku sama, jadi artikel ini juga membahas tentang const
.
var
lama memiliki beberapa perbedaan penting, perbedaan tersebut akan dibahas dalam artikel "Var" lama.
Jika suatu variabel dideklarasikan di dalam blok kode {...}
, variabel tersebut hanya terlihat di dalam blok tersebut.
Misalnya:
{ // melakukan beberapa pekerjaan dengan variabel lokal yang tidak boleh terlihat di luar biarkan pesan = "Halo"; // hanya terlihat di blok ini peringatan (pesan); // Halo } peringatan (pesan); // Kesalahan: pesan tidak ditentukan
Kita dapat menggunakan ini untuk mengisolasi sepotong kode yang melakukan tugasnya sendiri, dengan variabel yang hanya dimilikinya:
{ // tampilkan pesan biarkan pesan = "Halo"; peringatan (pesan); } { // tampilkan pesan lain biarkan pesan = "Selamat tinggal"; peringatan (pesan); }
Akan ada kesalahan tanpa pemblokiran
Perlu diketahui, tanpa blok terpisah akan terjadi error, jika kita menggunakan let
dengan nama variabel yang ada:
// tampilkan pesan biarkan pesan = "Halo"; peringatan (pesan); // tampilkan pesan lain biarkan pesan = "Selamat tinggal"; // Error: variabel sudah dideklarasikan peringatan (pesan);
Untuk if
, for
, while
dan seterusnya, variabel yang dideklarasikan dalam {...}
juga hanya terlihat di dalam:
jika (benar) { biarkan frase = "Halo!"; peringatan(frasa); // Halo! } peringatan(frasa); // Kesalahan, tidak ada variabel seperti itu!
Di sini, setelah if
selesai, alert
di bawah tidak akan melihat phrase
, sehingga terjadi kesalahan.
Itu bagus, karena memungkinkan kita membuat variabel blok-lokal, khusus untuk cabang if
.
Hal serupa juga berlaku untuk perulangan for
dan while
:
untuk (misalkan i = 0; i < 3; i++) { // variabel i hanya terlihat di dalam for ini peringatan(i); // 0, lalu 1, lalu 2 } peringatan(i); // Kesalahan, tidak ada variabel seperti itu
Secara visual, let i
berada di luar {...}
. Tetapi konstruksi for
di sini istimewa: variabel yang dideklarasikan di dalamnya dianggap sebagai bagian dari blok.
Suatu fungsi disebut “bersarang” ketika dibuat di dalam fungsi lain.
Hal ini dapat dilakukan dengan mudah menggunakan JavaScript.
Kita dapat menggunakannya untuk mengatur kode kita, seperti ini:
fungsi sayHiBye(Nama Depan, Nama Belakang) { // fungsi bertingkat pembantu untuk digunakan di bawah fungsi getNama Lengkap() { kembalikan Nama Depan + " " + Nama Belakang; } alert("Halo," + getFullName() ); alert("Sampai jumpa," + getFullName() ); }
Di sini fungsi getFullName()
bersarang dibuat untuk kenyamanan. Itu dapat mengakses variabel luar sehingga dapat mengembalikan nama lengkap. Fungsi bersarang cukup umum di JavaScript.
Yang lebih menarik lagi, fungsi bersarang dapat dikembalikan: baik sebagai properti objek baru atau sebagai hasilnya sendiri. Itu kemudian dapat digunakan di tempat lain. Di mana pun, ia masih memiliki akses ke variabel luar yang sama.
Di bawah, makeCounter
membuat fungsi “counter” yang mengembalikan nomor berikutnya pada setiap pemanggilan:
fungsi makeCounter() { biarkan hitung = 0; fungsi kembali() { jumlah pengembalian++; }; } biarkan penghitung = makeCounter(); waspada( counter() ); // 0 waspada( counter() ); // 1 waspada( counter() ); // 2
Meskipun sederhana, varian kode tersebut yang sedikit dimodifikasi memiliki kegunaan praktis, misalnya, sebagai penghasil angka acak untuk menghasilkan nilai acak untuk pengujian otomatis.
Bagaimana cara kerjanya? Jika kita membuat beberapa penghitung, apakah penghitung tersebut akan independen? Apa yang terjadi dengan variabel di sini?
Memahami hal-hal seperti itu sangat bagus untuk pengetahuan JavaScript secara keseluruhan dan bermanfaat untuk skenario yang lebih kompleks. Jadi mari kita bahas lebih dalam.
Inilah naga!
Penjelasan teknis mendalam ada di depan.
Sejauh saya ingin menghindari detail bahasa tingkat rendah, pemahaman apa pun tanpa detail tersebut akan kurang dan tidak lengkap, jadi bersiaplah.
Untuk lebih jelasnya, penjelasannya dibagi menjadi beberapa langkah.
Dalam JavaScript, setiap fungsi yang berjalan, blok kode {...}
, dan skrip secara keseluruhan memiliki objek terkait internal (tersembunyi) yang dikenal sebagai Lingkungan Lexical .
Objek Lingkungan Leksikal terdiri dari dua bagian:
Catatan Lingkungan – objek yang menyimpan semua variabel lokal sebagai propertinya (dan beberapa informasi lain seperti nilai this
).
Referensi ke lingkungan leksikal luar , yang terkait dengan kode luar.
Sebuah "variabel" hanyalah properti dari objek internal khusus, Environment Record
. “Mendapatkan atau mengubah suatu variabel” berarti “mendapatkan atau mengubah properti dari objek tersebut”.
Dalam kode sederhana tanpa fungsi ini, hanya ada satu Lingkungan Leksikal:
Inilah yang disebut Lingkungan Leksikal global , yang terkait dengan keseluruhan skrip.
Pada gambar di atas, persegi panjang berarti Catatan Lingkungan (penyimpanan variabel) dan tanda panah berarti referensi luar. Lingkungan Leksikal global tidak memiliki referensi luar, itulah sebabnya panah menunjuk ke null
.
Saat kode mulai dijalankan dan dijalankan, Lingkungan Leksikal berubah.
Berikut kode yang sedikit lebih panjang:
Persegi panjang di sisi kanan menunjukkan bagaimana Lingkungan Leksikal global berubah selama eksekusi:
Saat skrip dimulai, Lingkungan Leksikal sudah diisi sebelumnya dengan semua variabel yang dideklarasikan.
Awalnya, mereka berada dalam kondisi “Tidak Diinisialisasi”. Itu keadaan internal khusus, artinya mesin mengetahui variabel tersebut, tetapi tidak dapat direferensikan sampai dideklarasikan dengan let
. Hampir sama seperti jika variabelnya tidak ada.
Kemudian let phrase
muncul. Belum ada penugasan, jadi nilainya undefined
. Kita dapat menggunakan variabel mulai saat ini dan seterusnya.
phrase
diberi nilai.
phrase
mengubah nilainya.
Semuanya terlihat sederhana untuk saat ini, bukan?
Variabel adalah properti objek internal khusus, yang terkait dengan blok/fungsi/skrip yang sedang dijalankan.
Bekerja dengan variabel sebenarnya bekerja dengan properti objek itu.
Lingkungan Leksikal adalah objek spesifikasi
“Lingkungan Leksikal” adalah objek spesifikasi: ia hanya ada “secara teoritis” dalam spesifikasi bahasa untuk mendeskripsikan cara kerja. Kita tidak bisa memasukkan objek ini ke dalam kode kita dan memanipulasinya secara langsung.
Mesin JavaScript juga dapat mengoptimalkannya, membuang variabel yang tidak digunakan untuk menghemat memori dan melakukan trik internal lainnya, selama perilaku yang terlihat tetap seperti yang dijelaskan.
Fungsi juga merupakan nilai, sama seperti variabel.
Perbedaannya adalah Deklarasi Fungsi langsung diinisialisasi sepenuhnya.
Ketika Lingkungan Leksikal dibuat, Deklarasi Fungsi segera menjadi fungsi yang siap digunakan (tidak seperti let
, yang tidak dapat digunakan hingga deklarasi).
Itu sebabnya kita bisa menggunakan suatu fungsi, yang dideklarasikan sebagai Deklarasi Fungsi, bahkan sebelum deklarasi itu sendiri.
Misalnya, inilah keadaan awal Lingkungan Leksikal global saat kita menambahkan fungsi:
Tentu saja, perilaku ini hanya berlaku untuk Deklarasi Fungsi, bukan Ekspresi Fungsi di mana kita menetapkan fungsi ke variabel, misalnya let say = function(name)...
.
Saat suatu fungsi berjalan, di awal panggilan, Lingkungan Leksikal baru dibuat secara otomatis untuk menyimpan variabel lokal dan parameter panggilan.
Misalnya, untuk say("John")
, tampilannya seperti ini (eksekusinya ada pada baris, diberi label dengan panah):
Selama pemanggilan fungsi, kita mempunyai dua Lingkungan Leksikal: lingkungan dalam (untuk pemanggilan fungsi) dan lingkungan luar (global):
Lingkungan Leksikal bagian dalam sesuai dengan eksekusi say
saat ini. Ia memiliki satu properti: name
, argumen fungsi. Kami memanggil say("John")
, jadi nilai name
adalah "John"
.
Lingkungan Leksikal bagian luar adalah Lingkungan Leksikal global. Ia memiliki variabel phrase
dan fungsi itu sendiri.
Lingkungan Leksikal bagian dalam mempunyai referensi ke lingkungan outer
.
Ketika kode ingin mengakses suatu variabel – Lingkungan Lexical bagian dalam dicari terlebih dahulu, lalu lingkungan leksikal bagian luar, lalu lingkungan leksikal bagian luar, dan seterusnya hingga lingkungan leksikal global.
Jika suatu variabel tidak ditemukan di mana pun, itu adalah kesalahan dalam mode ketat (tanpa use strict
, penetapan ke variabel yang tidak ada akan membuat variabel global baru, agar kompatibel dengan kode lama).
Dalam contoh ini pencarian berlangsung sebagai berikut:
Untuk variabel name
, alert
di dalam say
langsung menemukannya di Lingkungan Leksikal bagian dalam.
Ketika ingin mengakses phrase
, maka tidak ada phrase
secara lokal, sehingga mengikuti referensi ke Lingkungan Leksikal luar dan menemukannya di sana.
Mari kembali ke contoh makeCounter
.
fungsi makeCounter() { biarkan hitung = 0; fungsi kembali() { jumlah pengembalian++; }; } biarkan penghitung = makeCounter();
Di awal setiap panggilan makeCounter()
, objek Lingkungan Lexical baru dibuat, untuk menyimpan variabel untuk proses makeCounter
ini.
Jadi kita mempunyai dua Lingkungan Leksikal yang bertumpuk, seperti pada contoh di atas:
Yang berbeda adalah, selama eksekusi makeCounter()
, fungsi bersarang kecil dibuat hanya dari satu baris: return count++
. Kami belum menjalankannya, hanya membuat.
Semua fungsi mengingat Lingkungan Leksikal di mana fungsi tersebut dibuat. Secara teknis, tidak ada keajaiban di sini: semua fungsi memiliki properti tersembunyi bernama [[Environment]]
, yang menyimpan referensi ke Lingkungan Leksikal tempat fungsi tersebut dibuat:
Jadi, counter.[[Environment]]
memiliki referensi ke {count: 0}
Lingkungan Leksikal. Begitulah cara fungsi mengingat di mana ia dibuat, tidak peduli di mana pun namanya dipanggil. Referensi [[Environment]]
disetel sekali dan selamanya pada waktu pembuatan fungsi.
Kemudian, ketika counter()
dipanggil, Lingkungan Lexical baru dibuat untuk panggilan tersebut, dan referensi Lingkungan Lexical bagian luarnya diambil dari counter.[[Environment]]
:
Sekarang ketika kode di dalam counter()
mencari variabel count
, pertama-tama ia mencari Lingkungan Lexical-nya sendiri (kosong, karena tidak ada variabel lokal di sana), kemudian Lingkungan Lexical dari panggilan makeCounter()
bagian luar, di mana ia menemukan dan mengubahnya .
Sebuah variabel diperbarui di Lingkungan Leksikal tempatnya berada.
Berikut keadaan setelah eksekusi:
Jika kita memanggil counter()
beberapa kali, variabel count
akan bertambah menjadi 2
, 3
dan seterusnya, di tempat yang sama.
Penutup
Ada istilah pemrograman umum “penutupan”, yang umumnya harus diketahui oleh pengembang.
Penutupan adalah fungsi yang mengingat variabel luarnya dan dapat mengaksesnya. Dalam beberapa bahasa, hal ini tidak mungkin dilakukan, atau suatu fungsi harus ditulis dengan cara khusus untuk mewujudkannya. Namun seperti yang dijelaskan di atas, dalam JavaScript, semua fungsi secara alami merupakan penutupan (hanya ada satu pengecualian, yang akan dibahas dalam Sintaks "Fungsi baru").
Artinya: mereka secara otomatis mengingat di mana mereka dibuat menggunakan properti [[Environment]]
yang tersembunyi, dan kemudian kode mereka dapat mengakses variabel luar.
Saat wawancara, pengembang frontend mendapat pertanyaan tentang “apa itu penutupan?”, jawaban yang valid adalah definisi penutupan dan penjelasan bahwa semua fungsi di JavaScript adalah penutupan, dan mungkin beberapa kata lagi tentang detail teknis: properti [[Environment]]
dan cara kerja Lingkungan Leksikal.
Biasanya, Lingkungan Leksikal dihapus dari memori dengan semua variabelnya setelah pemanggilan fungsi selesai. Itu karena tidak ada referensi mengenai hal itu. Seperti halnya objek JavaScript lainnya, objek tersebut hanya disimpan di memori selama dapat dijangkau.
Namun, jika ada fungsi bertumpuk yang masih dapat dijangkau setelah fungsi berakhir, maka fungsi tersebut memiliki properti [[Environment]]
yang mereferensikan lingkungan leksikal.
Dalam hal ini, Lingkungan Leksikal masih dapat dijangkau bahkan setelah fungsinya selesai, sehingga tetap hidup.
Misalnya:
fungsi f() { misalkan nilai = 123; fungsi kembali() { peringatan(nilai); } } misalkan g = f(); // g.[[Lingkungan]] menyimpan referensi ke Lingkungan Leksikal // dari panggilan f() yang sesuai
Harap dicatat bahwa jika f()
dipanggil berkali-kali, dan fungsi yang dihasilkan disimpan, maka semua objek Lingkungan Lexical yang terkait juga akan disimpan dalam memori. Pada kode di bawah ini, ketiganya:
fungsi f() { biarkan nilai = Matematika.acak(); kembali fungsi() { peringatan(nilai); }; } // 3 fungsi dalam array, masing-masing terhubung ke Lingkungan Leksikal // dari proses f() yang sesuai biarkan arr = [f(), f(), f()];
Objek Lingkungan Leksikal mati ketika tidak dapat dijangkau (sama seperti objek lainnya). Dengan kata lain, ini hanya ada jika ada setidaknya satu fungsi bersarang yang mereferensikannya.
Pada kode di bawah ini, setelah fungsi bersarang dihapus, Lingkungan Lexical yang melingkupinya (dan karenanya value
) dibersihkan dari memori:
fungsi f() { misalkan nilai = 123; fungsi kembali() { peringatan(nilai); } } misalkan g = f(); // selama fungsi g ada, nilainya tetap berada di memori g = nol; // ...dan sekarang memorinya sudah dibersihkan
Seperti yang telah kita lihat, secara teori, ketika suatu fungsi masih aktif, semua variabel luar juga dipertahankan.
Namun dalam praktiknya, mesin JavaScript mencoba mengoptimalkannya. Mereka menganalisis penggunaan variabel dan jika jelas dari kode bahwa variabel luar tidak digunakan – maka variabel tersebut akan dihapus.
Efek samping penting di V8 (Chrome, Edge, Opera) adalah variabel tersebut tidak tersedia saat debugging.
Coba jalankan contoh di bawah ini di Chrome dengan Alat Pengembang terbuka.
Saat dijeda, di konsol ketik alert(value)
.
fungsi f() { biarkan nilai = Matematika.acak(); fungsi g() { debug; // di konsol: ketik alert(value); Tidak ada variabel seperti itu! } kembali g; } misalkan g = f(); G();
Seperti yang Anda lihat – tidak ada variabel seperti itu! Secara teori, ini seharusnya dapat diakses, tetapi mesin mengoptimalkannya.
Hal ini dapat menyebabkan masalah debugging yang lucu (jika tidak memakan waktu). Salah satunya – kita bisa melihat variabel luar dengan nama yang sama, bukan variabel yang diharapkan:
biarkan nilai = "Kejutan!"; fungsi f() { let value = "nilai terdekat"; fungsi g() { debug; // di konsol: ketik alert(value); Kejutan! } kembali g; } misalkan g = f(); G();
Fitur V8 ini perlu diketahui. Jika Anda melakukan debug dengan Chrome/Edge/Opera, cepat atau lambat Anda akan menemuinya.
Itu bukan bug di debugger, melainkan fitur khusus V8. Mungkin suatu saat akan diubah. Anda selalu dapat memeriksanya dengan menjalankan contoh di halaman ini.
pentingnya: 5
Fungsi sayHi menggunakan nama variabel eksternal. Saat fungsi dijalankan, nilai manakah yang akan digunakan?
biarkan nama = "John"; fungsi ucapkan Hai() { alert("Hai," + nama); } nama = "Pete"; sayHai(); // apa yang akan ditampilkan: "John" atau "Pete"?
Situasi seperti ini biasa terjadi pada pengembangan browser dan sisi server. Suatu fungsi mungkin dijadwalkan untuk dijalankan lebih lambat dari waktu pembuatannya, misalnya setelah tindakan pengguna atau permintaan jaringan.
Jadi, pertanyaannya adalah: apakah ia menerima perubahan terbaru?
Jawabannya adalah: Pete .
Suatu fungsi mendapatkan variabel luar seperti sekarang, ia menggunakan nilai terbaru.
Nilai variabel lama tidak disimpan di mana pun. Ketika suatu fungsi menginginkan sebuah variabel, ia mengambil nilai saat ini dari Lingkungan Leksikalnya sendiri atau dari lingkungan terluarnya.
pentingnya: 5
Fungsi makeWorker
di bawah membuat fungsi lain dan mengembalikannya. Fungsi baru itu bisa dipanggil dari tempat lain.
Apakah ia akan memiliki akses ke variabel luar dari tempat pembuatannya, atau tempat pemanggilan, atau keduanya?
fungsi makeWorker() { biarkan nama = "Pete"; fungsi kembali() { peringatan(nama); }; } biarkan nama = "John"; // membuat suatu fungsi biarkan bekerja = makeWorker(); // sebut saja bekerja(); // apa yang akan ditampilkannya?
Nilai manakah yang akan ditunjukkannya? “Pete” atau “John”?
Jawabannya adalah: Pete .
Fungsi work()
pada kode di bawah ini mendapat name
dari tempat asalnya melalui referensi lingkungan leksikal luar:
Jadi, hasilnya adalah "Pete"
di sini.
Tetapi jika tidak ada let name
di makeWorker()
, maka pencarian akan keluar dan mengambil variabel global seperti yang kita lihat dari rantai di atas. Dalam hal ini hasilnya adalah "John"
.
pentingnya: 5
Di sini kita membuat dua counter: counter
dan counter2
menggunakan fungsi makeCounter
yang sama.
Apakah mereka mandiri? Apa yang akan ditunjukkan oleh penghitung kedua? 0,1
atau 2,3
atau yang lainnya?
fungsi makeCounter() { biarkan hitung = 0; fungsi kembali() { jumlah pengembalian++; }; } biarkan penghitung = makeCounter(); biarkan counter2 = makeCounter(); waspada( counter() ); // 0 waspada( counter() ); // 1 peringatan( counter2() ); // ? peringatan( counter2() ); // ?
Jawabannya: 0,1.
Fungsi counter
dan counter2
dibuat oleh pemanggilan makeCounter
yang berbeda.
Jadi mereka memiliki Lingkungan Leksikal luar yang independen, masing-masing memiliki count
sendiri.
pentingnya: 5
Di sini objek counter dibuat dengan bantuan fungsi konstruktor.
Apakah ini akan berhasil? Apa yang akan ditampilkannya?
fungsi Penghitung() { biarkan hitung = 0; ini.up = fungsi() { kembalikan ++hitungan; }; ini.turun = fungsi() { kembali --menghitung; }; } biarkan penghitung = Penghitung baru(); waspada( counter.up() ); // ? waspada( counter.up() ); // ? waspada( counter.down() ); // ?
Tentunya itu akan berfungsi dengan baik.
Kedua fungsi bertingkat dibuat dalam Lingkungan Leksikal luar yang sama, sehingga keduanya berbagi akses ke variabel count
yang sama:
fungsi Penghitung() { biarkan hitung = 0; ini.up = fungsi() { kembalikan ++hitungan; }; ini.turun = fungsi() { kembali --menghitung; }; } biarkan penghitung = Penghitung baru(); waspada( counter.up() ); // 1 waspada( counter.up() ); // 2 waspada( counter.down() ); // 1
pentingnya: 5
Lihatlah kodenya. Apa hasil panggilan pada baris terakhir?
biarkan frase = "Halo"; jika (benar) { biarkan pengguna = "John"; fungsi ucapkan Hai() { peringatan(`${phrase}, ${pengguna}`); } } sayHai();
Hasilnya adalah kesalahan .
Fungsi sayHi
dideklarasikan di dalam if
, sehingga hanya ada di dalamnya. Tidak ada sayHi
di luar.
pentingnya: 4
Tulis fungsi sum
yang berfungsi seperti ini: sum(a)(b) = a+b
.
Ya persis seperti ini, menggunakan tanda kurung ganda (jangan salah ketik).
Misalnya:
jumlah(1)(2) = 3 jumlah(5)(-1) = 4
Agar tanda kurung kedua berfungsi, tanda kurung pertama harus mengembalikan suatu fungsi.
Seperti ini:
jumlah fungsi(a) { fungsi pengembalian(b) { kembalikan a+b; // mengambil "a" dari lingkungan leksikal luar }; } peringatan( jumlah(1)(2) ); // 3 peringatan( jumlah(5)(-1) ); // 4
pentingnya: 4
Apa hasil dari kode ini?
misalkan x = 1; fungsi fungsi() { konsol.log(x); // ? misalkan x = 2; } fungsi();
PS Ada jebakan dalam tugas ini. Solusinya tidak jelas.
Hasilnya adalah: kesalahan .
Coba jalankan:
misalkan x = 1; fungsi fungsi() { konsol.log(x); //ReferenceError: Tidak dapat mengakses 'x' sebelum inisialisasi misalkan x = 2; } fungsi();
Dalam contoh ini kita dapat mengamati perbedaan aneh antara variabel “tidak ada” dan “belum diinisialisasi”.
Seperti yang mungkin telah Anda baca di artikel Cakupan variabel, penutupan, variabel dimulai dalam keadaan "tidak diinisialisasi" sejak eksekusi memasuki blok kode (atau fungsi). Dan itu tetap tidak diinisialisasi hingga pernyataan let
yang sesuai.
Dengan kata lain, suatu variabel secara teknis ada, tetapi tidak dapat digunakan sebelum let
.
Kode di atas menunjukkannya.
fungsi fungsi() { // variabel lokal x diketahui mesin sejak awal fungsi, // tetapi "tidak diinisialisasi" (tidak dapat digunakan) hingga dibiarkan ("zona mati") // oleh karena itu kesalahannya konsol.log(x); //ReferenceError: Tidak dapat mengakses 'x' sebelum inisialisasi misalkan x = 2; }
Zona di mana suatu variabel tidak dapat digunakan untuk sementara (dari awal blok kode hingga let
) kadang-kadang disebut “zona mati”.
pentingnya: 5
Kami memiliki metode bawaan arr.filter(f)
untuk array. Ini menyaring semua elemen melalui fungsi f
. Jika mengembalikan true
, maka elemen tersebut dikembalikan dalam array yang dihasilkan.
Buat satu set filter “siap digunakan”:
inBetween(a, b)
– antara a
dan b
atau sama dengan keduanya (secara inklusif).
inArray([...])
– dalam array yang diberikan.
Penggunaannya harus seperti ini:
arr.filter(inBetween(3,6))
– hanya memilih nilai antara 3 dan 6.
arr.filter(inArray([1,2,3]))
– hanya memilih elemen yang cocok dengan salah satu anggota [1,2,3]
.
Misalnya:
/* .. kode Anda untuk inBetween dan inArray */ misalkan arr = [1, 2, 3, 4, 5, 6, 7]; waspada( arr.filter(diBetween(3, 6)) ); // 3,4,5,6 peringatan( arr.filter(inArray([1, 2, 10])) ); // 1,2
Buka kotak pasir dengan tes.
fungsi di Antara(a, b) { fungsi pengembalian(x) { kembalikan x >= a && x <= b; }; } misalkan arr = [1, 2, 3, 4, 5, 6, 7]; waspada( arr.filter(diBetween(3, 6)) ); // 3,4,5,6
fungsi dalamArray(arr) { fungsi pengembalian(x) { return arr.termasuk(x); }; } misalkan arr = [1, 2, 3, 4, 5, 6, 7]; peringatan( arr.filter(inArray([1, 2, 10])) ); // 1,2
Buka solusi dengan pengujian di kotak pasir.
pentingnya: 5
Kami memiliki serangkaian objek untuk diurutkan:
biarkan pengguna = [ { nama: "John", usia: 20, nama keluarga: "Johnson" }, { nama: "Pete", umur: 18, nama keluarga: "Peterson" }, { nama: "Ann", usia: 19, nama keluarga: "Hathaway" } ];
Cara yang biasa dilakukan adalah:
// berdasarkan nama (Ann, John, Pete) pengguna.sort((a, b) => a.nama > b.nama ? 1 : -1); // berdasarkan usia (Pete, Ann, John) pengguna.sort((a, b) => a.age > b.age ? 1 : -1);
Bisakah kita membuatnya tidak terlalu bertele-tele, seperti ini?
pengguna.sort(byField('nama')); pengguna.sort(byField('usia'));
Jadi, daripada menulis fungsi, masukkan saja byField(fieldName)
.
Tulis fungsi byField
yang dapat digunakan untuk itu.
Buka kotak pasir dengan tes.
fungsi berdasarkanField(Namabidang){ kembali (a, b) => a[namabidang] > b[namabidang] ? 1 : -1; }
Buka solusi dengan pengujian di kotak pasir.
pentingnya: 5
Kode berikut membuat array shooters
.
Setiap fungsi dimaksudkan untuk menampilkan nomornya. Tapi ada sesuatu yang salah…
fungsi makeArmy() { biarkan penembak = []; misalkan saya = 0; sementara (saya < 10) { biarkan shooter = function() { // membuat fungsi shooter, peringatan (saya); // itu seharusnya menunjukkan nomornya }; shooters.push(penembak); // dan menambahkannya ke array saya++; } // ...dan mengembalikan deretan penembak penembak kembali; } biarkan tentara = makeArmy(); // semua penembak menampilkan 10, bukannya angka 0, 1, 2, 3... tentara[0](); // 10 dari penembak nomor 0 tentara[1](); // 10 dari penembak nomor 1 tentara[2](); // 10 ...dan seterusnya.
Mengapa semua penembak menunjukkan nilai yang sama?
Perbaiki kode agar berfungsi sebagaimana mestinya.
Buka kotak pasir dengan tes.
Mari kita periksa apa yang sebenarnya terjadi di dalam makeArmy
, dan solusinya akan menjadi jelas.
Ini menciptakan array shooters
kosong :
biarkan penembak = [];
Isi dengan fungsi melalui shooters.push(function)
di loop.
Setiap elemen adalah sebuah fungsi, sehingga array yang dihasilkan terlihat seperti ini:
penembak = [ fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); }, fungsi() { peringatan(i); } ];
Array dikembalikan dari fungsi.
Kemudian, nanti, panggilan ke anggota mana pun, misalnya army[5]()
akan mendapatkan elemen army[5]
dari array (yang merupakan fungsi) dan memanggilnya.
Sekarang mengapa semua fungsi tersebut menunjukkan nilai yang sama, 10
?
Itu karena tidak ada variabel lokal i
di dalam fungsi shooter
. Ketika fungsi seperti itu dipanggil, ia mengambil i
dari lingkungan leksikal luarnya.
Lalu, berapakah nilai i
?
Jika kita melihat sumbernya:
fungsi makeArmy() { ... misalkan saya = 0; sementara (saya < 10) { biarkan penembak = fungsi() { // fungsi penembak peringatan (saya); // harus menunjukkan nomornya }; shooters.push(penembak); // menambahkan fungsi ke dalam array saya++; } ... }
Kita dapat melihat bahwa semua fungsi shooter
dibuat di lingkungan leksikal fungsi makeArmy()
. Namun ketika army[5]()
dipanggil, makeArmy
telah menyelesaikan tugasnya, dan nilai akhir i
adalah 10
( while
berhenti di i=10
).
Hasilnya, semua fungsi shooter
mendapatkan nilai yang sama dari lingkungan leksikal luar yaitu nilai terakhir, i=10
.
Seperti yang Anda lihat di atas, pada setiap iterasi blok while {...}
, lingkungan leksikal baru dibuat. Jadi, untuk memperbaikinya, kita dapat menyalin nilai i
ke dalam variabel di dalam blok while {...}
, seperti ini:
fungsi makeArmy() { biarkan penembak = []; misalkan saya = 0; sementara (saya < 10) { misalkan j = saya; biarkan penembak = fungsi() { // fungsi penembak peringatan( j ); // harus menunjukkan nomornya }; shooters.push(penembak); saya++; } penembak kembali; } biarkan tentara = makeArmy(); // Sekarang kodenya berfungsi dengan benar tentara[0](); // 0 tentara[5](); // 5
Di sini let j = i
mendeklarasikan variabel “iterasi-lokal” j
dan menyalin i
ke dalamnya. Primitif disalin "berdasarkan nilai", jadi kita sebenarnya mendapatkan salinan independen i
, yang termasuk dalam iterasi loop saat ini.
Penembaknya bekerja dengan benar, karena nilai i
sekarang menjadi sedikit lebih dekat. Bukan di Lingkungan Leksikal makeArmy()
, tetapi di Lingkungan Leksikal yang sesuai dengan iterasi loop saat ini:
Masalah seperti ini juga bisa dihindari jika kita for
di awal, seperti ini:
fungsi makeArmy() { biarkan penembak = []; untuk(misalkan i = 0; i < 10; i++) { biarkan penembak = fungsi() { // fungsi penembak peringatan (saya); // harus menunjukkan nomornya }; shooters.push(penembak); } penembak kembali; } biarkan tentara = makeArmy(); tentara[0](); // 0 tentara[5](); // 5
Pada dasarnya sama, karena for
pada setiap iterasi menghasilkan lingkungan leksikal baru, dengan variabelnya sendiri i
. Jadi shooter
yang dihasilkan di setiap iterasi mereferensikannya sendiri i
, dari iterasi itu sendiri.
Sekarang, setelah Anda berusaha keras membaca ini, dan resep akhirnya sangat sederhana – gunakan saja for
, Anda mungkin bertanya-tanya – apakah itu sepadan?
Nah, jika Anda bisa dengan mudah menjawab pertanyaan itu, Anda tidak akan membaca solusinya. Jadi, semoga tugas ini membantu Anda memahami segalanya dengan lebih baik.
Selain itu, memang ada kasus ketika seseorang lebih memilih while
daripada for
, dan skenario lain, di mana masalah tersebut nyata.
Buka solusi dengan pengujian di kotak pasir.