Artikel ini dimaksudkan untuk seseorang yang mengambil langkah-langkah pemalu di jalur sulit untuk mempelajari JavaScript. Terlepas dari kenyataan bahwa pada tahun 2018, saya menggunakan sintaks ES5 sehingga artikel tersebut dapat dipahami oleh Padawans muda yang mengambil kursus JavaScript Level 1 di HTML Academy.Salah satu fitur yang membedakan JS dari banyak bahasa pemrograman lain adalah bahwa dalam bahasa ini fungsi adalah "objek kelas satu". Atau, dalam bahasa Rusia, fungsi adalah sebuah makna. Sama seperti angka, string atau objek. Kita bisa menulis fungsi ke variabel, kita bisa meletakkannya di array atau di properti objek. Kami bahkan dapat menambahkan dua fungsi. Bahkan, tidak ada yang berarti akan datang dari ini, tetapi sebagai fakta - kita bisa!
function hello(){}; function world(){}; console.log(hello + world);
Yang paling menarik adalah kita dapat membuat fungsi yang beroperasi pada fungsi lain - menerimanya sebagai argumen atau mengembalikannya sebagai nilai. Fungsi semacam itu disebut
fungsi tingkat tinggi . Dan hari ini kita, anak perempuan dan laki-laki, akan berbicara tentang bagaimana menyesuaikan peluang ini dengan kebutuhan ekonomi nasional. Sepanjang jalan, Anda akan belajar lebih banyak tentang beberapa fitur fungsi berguna di JS.
Saluran pipa
Katakanlah kita memiliki sesuatu yang Anda butuhkan untuk membuat banyak karya. Katakanlah pengguna mengunggah file teks yang menyimpan data dalam format JSON, dan kami ingin memproses isinya. Pertama, kita perlu memangkas karakter spasi putih tambahan, yang bisa "tumbuh" di sekitar tepi sebagai akibat dari tindakan pengguna atau sistem operasi. Kemudian periksa bahwa tidak ada kode jahat dalam teks (siapa tahu, pengguna ini). Kemudian beralih dari teks ke objek menggunakan metode
JSON.parse
. Kemudian hapus data yang kita butuhkan dari objek ini. Dan pada akhirnya - kirim data ini ke server. Anda mendapatkan sesuatu seperti ini:
function trim(){}; function sanitize(){}; function parse(){}; function extractData(){}; function send(){}; var textFromFile = getTextFromFile(); send(extractData(parse(sanitize(trim(testFromFile))));
Terlihat sangat setuju. Selain itu, Anda mungkin tidak memperhatikan bahwa ada satu braket penutup yang hilang. Tentu saja, IDE akan memberi tahu Anda hal ini, tetapi masih ada masalah. Untuk mengatasinya,
operator baru |> baru-baru ini diusulkan. Sebenarnya, ini bukan hal baru, tetapi secara jujur โโmeminjam dari bahasa fungsional, tetapi ini bukan intinya. Dengan menggunakan operator ini, baris terakhir dapat ditulis ulang sebagai berikut:
textFromFile |> trim |> sanitize |> parse |> extractData |> send;
Operator |> mengambil operan kiri dan meneruskannya ke operan kanan sebagai argumen. Misalnya,
"Hello" |> console.log
setara dengan
console.log("Hello")
. Ini sangat tepat untuk kasus-kasus ketika beberapa fungsi dipanggil sepanjang rantai. Namun, sebelum pengenalan operator ini, banyak waktu akan berlalu (jika proposal ini diterima sama sekali), tetapi Anda harus hidup entah bagaimana sekarang. Oleh karena itu, kita dapat menulis fungsi
sepeda yang mensimulasikan perilaku ini:
function pipe(){ var args = Array.from(arguments); var result = args.shift(); while(args.length){ var f = args.shift(); result = f(result); } return result; } pipe( textFromFile, trim, sanitize, parse, extractData, send );
Jika Anda seorang pemula javascript (javascript? Javascript?), Baris pertama dari fungsi ini mungkin tampak tidak dapat dipahami oleh Anda. Sederhana: di dalam fungsi, kami menggunakan kata kunci
argumen untuk mengakses objek mirip array yang berisi semua argumen yang diteruskan ke fungsi. Ini sangat nyaman ketika kita tidak tahu sebelumnya berapa banyak argumen yang akan dia miliki. Objek besar seperti array, tetapi tidak cukup. Oleh karena itu, kami mengonversinya menjadi array normal menggunakan metode
Array.from
. Kode selanjutnya, saya harap, sudah cukup mudah dibaca: kita mulai dari kiri ke kanan untuk mengekstrak elemen dari array dan menerapkannya satu sama lain dengan cara yang sama seperti yang dilakukan oleh operator |>.
Penebangan
Berikut adalah contoh lain yang dekat dengan kehidupan nyata. Misalkan kita sudah memiliki fungsi
f
yang tidak ... sesuatu yang bermanfaat. Dan dalam proses pengujian kode kami, kami ingin tahu lebih banyak tentang bagaimana tepatnya melakukannya. Pada saat apa yang disebut, argumen apa yang diteruskan ke sana, nilai mana yang dikembalikan.
Tentu saja, dengan setiap pemanggilan fungsi, kita dapat menulis ini:
var result = f(a, b); console.log(" f " + a + " " + b + " " + result); console.log(" : " + Date.now());
Tapi, pertama, agak rumit. Dan kedua, sangat mudah untuk melupakannya. Suatu hari kita hanya akan menulis
f(a, b)
, dan sejak itu kegelapan ketidaktahuan akan menetap di pikiran kita. Ini akan berkembang dengan setiap tantangan baru, yang tidak kita ketahui.
Idealnya, saya ingin logging terjadi secara otomatis. Sehingga setiap kali Anda memanggil
f
, semua hal yang kita butuhkan ditulis ke konsol. Dan, untungnya, kita punya cara untuk melakukan ini. Memenuhi fitur tatanan baru yang lebih tinggi!
function addLogger(f){ return function(){ var args = Array.from(arguments); var result = f.apply(null, args); console.log(" " + f.name + " " + args.join() + " " + result + "\n" + " : " + Date.now()); return result; } } function sum(a, b){ return a + b; } var sumWithLogging = addLogger(sum); sum(1, 2);
Fungsi mengambil fungsi dan mengembalikan fungsi yang memanggil fungsi yang diteruskan ke fungsi saat fungsi tersebut dibuat. Maaf, saya tidak bisa berhenti menulis ini. Sekarang dalam bahasa Rusia: fungsi
addLogger
menciptakan
addLogger
sekitar fungsi yang diteruskan sebagai argumen. Pembungkus juga merupakan fungsi. Saat dipanggil, ia mengumpulkan array argumennya dengan cara yang sama seperti yang kami lakukan pada contoh sebelumnya. Kemudian, menggunakan metode
apply , ia memanggil fungsi yang dibungkus dengan argumen yang sama dan mengingat hasilnya. Setelah itu, pembungkus menulis semuanya ke konsol.
Di sini kita memiliki kasus serangan man-in-the-middle klasik. Jika Anda menggunakan pembungkus alih-alih
f
, maka dari sudut pandang kode yang menggunakannya, praktis tidak ada perbedaan. Kode dapat berasumsi bahwa ia berkomunikasi dengan
f
secara langsung. Sementara itu, pembungkus melaporkan semuanya kepada Kamerad Mayor.
Eins, zwei, drei, vier ...
Dan satu tugas lagi yang dekat dengan latihan. Misalkan kita perlu memberi nomor beberapa entitas. Setiap kali entitas baru muncul, kami mendapatkan nomor baru untuk itu, satu lebih dari yang sebelumnya. Untuk melakukan ini, kami memulai fungsi dari formulir berikut:
var lastNumber = 0; function getNewNumber(){ return lastNumber++; }
Dan kemudian kita memiliki entitas jenis baru. Katakanlah, sebelum itu, kami memberi nomor kelinci, dan sekarang ada juga kelinci. Jika Anda menggunakan satu fungsi untuk keduanya dan yang lainnya, maka setiap nomor yang dikeluarkan untuk kelinci akan membuat "lubang" dalam rangkaian angka yang dikeluarkan untuk kelinci. Jadi, kita membutuhkan fungsi kedua, dan dengan itu variabel kedua:
var lastHareNumber = 0; function getNewHareNumber(){ return lastHareNumber++; } var lastRabbitNumber = 0; function getNewRabbitNumber(){ return lastRabbitNumber++; }
Anda merasa kode ini baunya tidak enak? Saya ingin memiliki sesuatu yang lebih baik. Pertama, saya ingin dapat mendeklarasikan fungsi tersebut kurang verbose, tanpa menduplikasi kode. Dan kedua, saya ingin entah bagaimana "mengemas" variabel yang digunakan fungsi ke dalam fungsi itu sendiri agar tidak menyumbat namespace sekali lagi.
Dan kemudian seorang pria masuk, akrab dengan konsep OOP, dan berkata:"Dasar, Watson." Hal ini diperlukan untuk membuat generator nomor bukan objek, tetapi objek. Objek hanya dirancang untuk menyimpan fungsi yang bekerja dengan data, bersama dengan data ini. Lalu kita bisa menulis sesuatu seperti:
var numberGenerator = new NumberGenerator(); var n = numberGenerator.get();
Yang akan saya jawab:
- Sejujurnya, saya sepenuhnya setuju dengan Anda. Dan pada prinsipnya, ini adalah pendekatan yang lebih benar daripada apa yang akan saya tawarkan sekarang. Tetapi di sini kita memiliki artikel tentang fungsi, bukan tentang OOP. Jadi bisakah kamu diam sebentar dan biarkan aku menyelesaikannya?
Di sini (kejutan!) Fungsi tingkat tinggi akan membantu kita lagi.
function createNumberGenerator(){ var n = 0; return function(){ return n++; } } var getNewHareNumber = createNumberGenerator(); var getNewRabbitNumber = createNumberGenerator(); console.log( getNewHareNumber(), getNewHareNumber(), getNewHareNumber(), getNewRabbitNumber(), getNewRabbitNumber(), );
Dan di sini beberapa orang mungkin memiliki pertanyaan, mungkin bahkan dalam bentuk cabul: apa yang sedang terjadi? Mengapa kita membuat variabel yang tidak digunakan dalam fungsi itu sendiri? Bagaimana cara fungsi internal mengaksesnya jika fungsi eksternal telah menyelesaikan eksekusi sejak lama? Mengapa dua fungsi yang dibuat mengacu pada variabel yang sama mendapatkan hasil yang berbeda? Satu jawaban untuk semua pertanyaan ini adalah
penutupan .
Setiap kali fungsi
createNumberGenerator
, JS interpreter menciptakan hal ajaib yang disebut "konteks eksekusi". Secara kasar, ini adalah objek di mana semua variabel yang dideklarasikan dalam fungsi ini disimpan. Kami tidak dapat mengaksesnya sebagai objek javascript biasa, namun demikian.
Jika fungsi itu "sederhana" (katakanlah, menambahkan angka), maka setelah akhir kerjanya, konteks eksekusi ternyata tidak berguna. Apakah Anda tahu apa yang terjadi pada data yang tidak perlu di JS? Mereka ditelan oleh iblis tak pernah puas bernama Pengumpul Sampah. Namun, jika fungsi itu "rumit", mungkin saja seseorang masih membutuhkan konteksnya bahkan setelah fungsi ini dijalankan. Dalam hal ini, Pengumpul Sampah menyelamatkannya, dan dia tetap menggantung di suatu tempat dalam ingatannya, sehingga mereka yang membutuhkannya masih dapat memiliki akses kepadanya.
Dengan demikian, fungsi yang dikembalikan oleh
createNumberGenerator
akan selalu memiliki akses ke salinan variabelnya sendiri
n
. Anda dapat menganggapnya sebagai Bag of Holding dari D&D. Anda memasukkan tangan Anda ke dalam tas dan menemukan diri Anda dalam "saku" interdimensional pribadi di mana Anda dapat menyimpan semua yang Anda inginkan.
Debounce
Ada yang namanya "menghilangkan bouncing". Ini adalah saat kita tidak ingin beberapa fungsi dipanggil terlalu sering. Misalkan ada tombol tertentu, mengklik yang meluncurkan proses "mahal" (panjang, atau memakan banyak memori, atau Internet, atau mengorbankan perawan). Mungkin saja pengguna yang tidak sabar mulai mengklik tombol ini dengan frekuensi lebih dari sepuluh hertz. Selain itu, proses tersebut memiliki sifat yang tidak masuk akal untuk menjalankannya sepuluh kali berturut-turut, karena hasil akhirnya tidak akan berubah. Saat itulah kami menerapkan "penghapusan obrolan".
Esensinya sangat sederhana: kami melakukan fungsi tidak segera, tetapi setelah beberapa waktu. Jika sebelum waktu ini berlalu, fungsinya dipanggil lagi, kita โreset timerโ. Dengan demikian, pengguna dapat mengklik tombol setidaknya seribu kali - hanya satu yang diperlukan untuk pengorbanan. Namun, lebih sedikit kata, lebih banyak kode:
function debounce(f, delay){ var lastTimeout; return function(){ if(lastTimeout){ clearTimeout(lastTimeout); } var args = Array.from(arguments); lastTimeout = setTimeout(function(){ f.apply(null, args); }, delay); } } function sacrifice(name){ console.log(name + " * *"); } function sacrificeDebounced = debounce(sacrifice, 500); sacrificeDebounced(""); sacrificeDebounced(""); sacrificeDebounced("");
Dalam setengah detik Lena akan dikorbankan, dan Katya dan Sveta akan selamat berkat fungsi magis kami.
Jika Anda hati-hati membaca contoh sebelumnya, Anda harus memiliki pemahaman yang baik tentang bagaimana semuanya bekerja di sini. Fungsi wrapper yang dibuat oleh
debounce
memicu eksekusi yang tertunda dari fungsi asli menggunakan
setTimeout . Dalam hal ini, pengidentifikasi waktu habis disimpan dalam variabel lastTimeout, yang dapat diakses oleh pembungkus karena penutupan. Jika pengidentifikasi batas waktu sudah ada dalam variabel ini, pembungkus membatalkan batas waktu ini dengan
clearTimeout . Jika batas waktu sebelumnya telah selesai, maka tidak ada yang terjadi. Jika tidak, jauh lebih buruk baginya.
Tentang ini, mungkin, aku akan berakhir. Saya harap hari ini Anda belajar banyak hal baru, dan yang paling penting - Anda memahami semua yang Anda pelajari. Sampai jumpa lagi.