[Alamat artikel ini] http://cuimingda.com/2009/02/speed-up-your-javascript-part-3.html
Rekursi adalah salah satu musuh yang memperlambat kecepatan berjalan skrip. Terlalu banyak rekursi akan membuat browser semakin lambat hingga mati atau keluar secara otomatis secara tiba-tiba dan tidak dapat dijelaskan, jadi kita harus menyelesaikan rangkaian masalah kinerja di JavaScript ini. Di artikel kedua seri ini, saya memperkenalkan secara singkat cara menggunakan teknologi memoisasi untuk menggantikan terlalu banyak panggilan rekursif dalam fungsi. Memoisasi adalah teknik menyimpan hasil perhitungan sebelumnya sehingga kita tidak perlu menghitung ulang hasil yang sudah dihitung. Untuk fungsi yang melakukan penghitungan melalui rekursi, memoisasi terlalu berguna. Memoizer yang saya gunakan saat ini ditulis oleh Crockford dan terutama digunakan dalam operasi rekursif yang mengembalikan bilangan bulat. Tentu saja, tidak semua fungsi rekursif mengembalikan bilangan bulat, jadi kita memerlukan fungsi memoizer() yang lebih umum untuk menangani lebih banyak jenis fungsi rekursif.
fungsi memoizer(dasar, cache){ cache = cache ||. {} var shell = function(arg){ if (!(arg dalam cache)){ cache[arg] = fundamental(shell, arg) } mengembalikan cache[arg] ; }; return shell;} Versi fungsi ini sedikit berbeda dari yang ditulis oleh Crockford. Pertama, urutan parameter dibalik, fungsi asli ditetapkan sebagai parameter pertama, dan parameter kedua adalah objek cache, yang bersifat opsional karena tidak semua fungsi rekursif berisi informasi awal. Di dalam fungsinya, saya melemparkan tipe objek cache dari array ke objek sehingga versi ini dapat mengakomodasi fungsi rekursif yang tidak mengembalikan bilangan bulat. Dalam fungsi shell, saya menggunakan operator in untuk menentukan apakah parameter sudah termasuk dalam cache. Cara penulisan seperti ini lebih aman daripada menguji apakah tipenya tidak terdefinisi, karena tidak terdefinisi adalah nilai kembalian yang valid. Kita masih menggunakan deret Fibonacci yang disebutkan sebelumnya untuk menggambarkan:
var fibonacci = memoizer(function (recur, n) { return recur(n - 1) + recur(n - 2); }, {"0": 0, "1" :1}); Demikian pula, mengeksekusi fungsi fibonacci(40) hanya akan memanggil fungsi asli sebanyak 40 kali, bukan 331.160.280 kali yang dilebih-lebihkan. Memoisasi sangat bagus untuk algoritma rekursif dengan kumpulan hasil yang ditentukan secara ketat. Namun memang banyak algoritma rekursif yang tidak cocok untuk optimasi menggunakan metode memoisasi.
Salah satu profesor saya di sekolah selalu bersikeras bahwa situasi apa pun di mana rekursi digunakan dapat diganti dengan iterasi jika diperlukan. Faktanya, rekursi dan iterasi sering kali digunakan sebagai metode untuk saling mengimbangi, terutama ketika salah satu metode mengalami kesalahan. Teknologi untuk mengubah algoritma rekursif menjadi algoritma berulang juga tidak bergantung pada bahasa pengembangan. Namun, pentingnya JavaScript lebih besar karena sumber daya lingkungan eksekusi sangat terbatas. Mari kita tinjau algoritma rekursif yang umum, seperti pengurutan gabungan. Mengimplementasikan algoritma ini dalam JavaScript memerlukan kode berikut:
function merge(left, right){ var result = []; { if (kiri[0] < kanan[0]){ hasil.push(kiri.shift() } else { hasil.push(kanan.shift()); } mengembalikan hasil.concat(kiri ).concat (kanan);}//Gabungkan algoritma pengurutan fungsi mergeSort(items){ if (items.length == 1) { return items; } var middle = Math.floor(items.length / 2) menggunakan rekursi , kiri = item. irisan(0, tengah), kanan = item.slice(tengah); return merge(mergeSort(kiri), mergeSort(kanan));} Panggil fungsi mergeSort() untuk memproses array, dan Anda dapat mengembalikan array yang diurutkan. Perhatikan bahwa setiap kali fungsi mergeSort() dipanggil, akan ada dua panggilan rekursif. Algoritme ini tidak dapat dioptimalkan menggunakan memoisasi, karena setiap hasil dihitung dan digunakan hanya sekali, dan bahkan buffering terhadap hasilnya tidak ada gunanya. Jika Anda menggunakan fungsi mergeSort() pada array yang terdiri dari 100 elemen, akan ada total 199 panggilan. Array yang terdiri dari 1000 elemen akan melakukan panggilan 1999. Dalam hal ini, solusi kami adalah mengubah algoritma rekursif menjadi algoritma iteratif, yang berarti memperkenalkan beberapa loop (untuk algoritma, Anda dapat merujuk ke artikel ini "Pemrosesan Daftar: Sortir Lagi, Secara Alami"):
// Gunakan implementasi berulang algoritma pengurutan gabungan fungsi mergeSort(item){ if (items.length == 1) { mengembalikan item; } var work = []; for (var i=0, len=items.length; i < len; i++){ berfungsi .push([items[i]]); } work.push([]); //jika jumlah item ganjil untuk (var lim=len; lim > 1; lim = (lim+1)/2 ) { untuk (var j=0,k=0; k < lim; j++, k+=2){ kerja[j] = penggabungan(kerja[k], kerja[k+1]); ]; //jika jumlah item ganjil } return work[0];}Implementasi algoritme pengurutan gabungan ini menggunakan serangkaian loop alih-alih rekursi untuk pengurutan. Karena pengurutan gabungan pertama-tama membagi array menjadi beberapa array dengan hanya satu elemen, metode ini melakukan operasi ini secara lebih eksplisit daripada secara implisit menggunakan fungsi rekursif. Array kerja diinisialisasi untuk memuat array array satu elemen. Setiap kali dalam loop, kedua array digabungkan dan hasil penggabungan dimasukkan kembali ke dalam array kerja. Ketika fungsi dijalankan, hasil yang diurutkan akan dikembalikan melalui elemen pertama dalam array kerja. Dalam implementasi merge sort ini, algoritma juga diimplementasikan tanpa menggunakan rekursi apapun. Namun, hal ini menyebabkan sejumlah besar perulangan, dan jumlah perulangan didasarkan pada jumlah elemen dalam array yang akan diurutkan, jadi kita mungkin perlu memodifikasinya menggunakan teknik yang dibahas di artikel sebelumnya untuk menangani overhead tambahan ini. .
Untuk meringkas prinsip dasarnya, Anda harus berhati-hati setiap kali menggunakan rekursi. Memoisasi dan iterasi adalah dua solusi untuk menggantikan rekursi. Hasil yang paling langsung tentu saja menghindari kotak dialog yang meminta skrip menjadi tidak terkendali.