Dibandingkan dengan C/C ++, pemrosesan JavaScript dalam memori dalam JavaScript yang kami gunakan telah membuat kami lebih memperhatikan penulisan logika bisnis dalam pengembangan. Namun, dengan kompleksitas bisnis yang berkelanjutan, pengembangan aplikasi satu halaman, aplikasi html5 seluler, program Node.js, dll., Lag dan overflow memori yang disebabkan oleh masalah memori dalam javascript tidak lagi tidak dikenal.
Artikel ini akan membahas penggunaan memori dan optimalisasi dari tingkat bahasa JavaScript. Dari aspek -aspek yang semua orang kenal atau sedikit terdengar, hingga hal -hal yang setiap orang tidak akan melihat sebagian besar waktu, kami akan menganalisisnya satu per satu.
1. Manajemen memori tingkat bahasa
1.1 Lingkup
Lingkup adalah mekanisme operasi yang sangat penting dalam pemrograman JavaScript. Itu tidak menarik perhatian pemula dalam pemrograman JavaScript sinkron, tetapi dalam pemrograman asinkron, keterampilan kontrol ruang lingkup yang baik telah menjadi keterampilan yang diperlukan bagi pengembang JavaScript. Selain itu, ruang lingkup memainkan peran penting dalam manajemen memori JavaScript.
Dalam JavaScript, fungsi dapat dipanggil, dengan pernyataan dan lingkup global yang dapat dilingkupi.
Seperti yang ditunjukkan dalam kode berikut sebagai contoh:
Salinan kode adalah sebagai berikut:
var foo = function () {
var local = {};
};
foo ();
Console.log (lokal); // => tidak terdefinisi
var bar = function () {
local = {};
};
batang();
Console.log (lokal); // => {}
Di sini kita mendefinisikan fungsi foo () dan fungsi bar (). Niat mereka adalah untuk mendefinisikan variabel bernama lokal. Tetapi hasil akhirnya sangat berbeda.
Dalam fungsi FOO (), kami menggunakan pernyataan VAR untuk menyatakan bahwa variabel lokal didefinisikan. Karena ruang lingkup terbentuk di dalam tubuh fungsi, variabel ini didefinisikan dalam ruang lingkup. Selain itu, tidak ada pemrosesan ekstensi lingkup dalam fungsi foo (), jadi setelah fungsi dieksekusi, variabel lokal juga dihancurkan. Namun, variabel ini tidak dapat diakses di ruang lingkup luar.
Dalam fungsi bar (), variabel lokal tidak dinyatakan menggunakan pernyataan VAR, tetapi sebaliknya secara langsung mendefinisikan lokal sebagai variabel global. Oleh karena itu, variabel ini dapat diakses oleh ruang lingkup luar.
Salinan kode adalah sebagai berikut:
local = {};
// definisi di sini setara dengan
global.local = {};
1.2 Rantai Lingkup
Dalam pemrograman JavaScript, Anda pasti akan menemukan skenario sarang fungsi multi-lapisan, yang merupakan representasi khas rantai lingkup.
Seperti yang ditunjukkan dalam kode berikut:
Salinan kode adalah sebagai berikut:
fungsi foo () {
var val = 'halo';
function bar () {
fungsi baz () {
Global.val = 'World;'
}
baz ();
console.log (val); // => halo
}
batang();
}
foo ();
Berdasarkan penjelasan sebelumnya tentang ruang lingkup, Anda mungkin berpikir bahwa hasil yang ditunjukkan dalam kode di sini adalah dunia, tetapi hasil sebenarnya adalah halo. Banyak pemula akan mulai merasa bingung di sini, jadi mari kita lihat bagaimana kode ini bekerja.
Sejak JavaScript, pencarian untuk pengidentifikasi variabel dimulai dari ruang lingkup saat ini dan melihat ke luar hingga lingkup global. Oleh karena itu, akses ke variabel dalam kode JavaScript hanya dapat dilakukan di luar, tetapi tidak secara terbalik.
Eksekusi fungsi BAZ () mendefinisikan variabel variabel global dalam ruang lingkup global. Dalam fungsi bar (), ketika mengakses Val Identifier, prinsip pencarian dari dalam ke luar adalah: jika tidak ditemukan dalam ruang lingkup fungsi batang, ia menuju ke lapisan sebelumnya, yaitu, ruang lingkup fungsi foo ().
Namun, kunci untuk membingungkan semua orang adalah: akses pengidentifikasi ini menemukan variabel yang cocok dalam ruang lingkup fungsi foo (), dan tidak akan terus melihat ke luar, sehingga variabel variabel global yang ditentukan dalam fungsi BAZ () tidak memiliki efek dalam akses variabel ini.
1.3 Penutupan
Kita tahu bahwa pencarian pengidentifikasi di JavaScript mengikuti prinsip luar-dalam. Namun, dengan kompleksitas logika bisnis, pesanan pengiriman tunggal jauh dari memenuhi meningkatnya kebutuhan baru.
Mari kita lihat kode berikut:
Salinan kode adalah sebagai berikut:
fungsi foo () {
var lokal = 'halo';
return function () {
kembali lokal;
};
}
var bar = foo ();
console.log (bar ()); // => halo
Teknologi yang memungkinkan ruang lingkup luar untuk mengakses ruang lingkup dalam seperti yang ditunjukkan di sini adalah penutupan. Berkat penerapan fungsi tingkat tinggi, ruang lingkup fungsi foo () "diperluas".
Fungsi FOO () mengembalikan fungsi anonim, yang ada dalam lingkup fungsi foo (), sehingga Anda dapat mengakses variabel lokal dalam ruang lingkup fungsi foo () dan menyimpan referensi. Karena fungsi ini secara langsung mengembalikan variabel lokal, fungsi bar () dapat secara langsung dieksekusi dalam ruang lingkup luar untuk mendapatkan variabel lokal.
Penutupan adalah fitur JavaScript tingkat tinggi, dan kami dapat menggunakannya untuk mencapai efek yang lebih dan lebih kompleks untuk memenuhi kebutuhan yang berbeda. Namun, perlu dicatat bahwa karena fungsi dengan referensi variabel internal diajukan dari fungsi, variabel dalam ruang lingkup ini tidak akan dihancurkan setelah fungsi dieksekusi sampai semua referensi ke variabel internal dibatalkan. Oleh karena itu, penerapan penutupan dapat dengan mudah menyebabkan memori menjadi tidak bebas.
2. Mekanisme Daur Ulang Memori JavaScript
Di sini saya akan mengambil mesin V8 yang digunakan oleh Chrome dan Node.js dan diluncurkan oleh Google sebagai contoh untuk secara singkat memperkenalkan mekanisme daur ulang memori JavaScript. Untuk konten yang lebih rinci, Anda dapat membeli buku teman baik saya "mendalam dan mudah dimengerti Node.js" untuk belajar, yang merupakan pengantar yang sangat rinci dalam bab "Control Memory".
Di V8, semua objek JavaScript dialokasikan melalui "tumpukan".
Ketika kami mendeklarasikan dan menetapkan nilai dalam kode, V8 akan mengalokasikan bagian dari variabel ini dalam memori heap. Jika memori yang diminta tidak cukup untuk menyimpan variabel ini, V8 akan terus berlaku untuk memori sampai ukuran tumpukan mencapai batas memori V8. Secara default, batas atas memori heap V8 adalah 1464MB pada sistem 64-bit dan 732MB pada sistem 32-bit, yaitu sekitar 1,4GB dan 0,7GB.
Selain itu, V8 mengelola objek JavaScript dalam banyak memori dalam generasi: generasi baru dan generasi lama. Generasi baru adalah objek JavaScript dengan siklus bertahan hidup pendek, seperti variabel sementara, string, dll.; Sementara generasi lama adalah objek dengan siklus bertahan hidup yang panjang setelah beberapa koleksi sampah, seperti pengontrol utama, objek server, dll.
Algoritma daur ulang sampah selalu menjadi bagian penting dari pengembangan bahasa pemrograman, dan algoritma daur ulang sampah yang digunakan dalam V8 terutama sebagai berikut:
1. Algoritma SCAVENGE: Manajemen ruang memori dilakukan melalui penyalinan, terutama digunakan dalam ruang memori generasi baru;
2.mark-sweep algoritma dan algoritma mark-compact: Heap memori diurutkan dan didaur ulang melalui tanda, terutama digunakan untuk inspeksi dan daur ulang benda generasi lama.
PS: Implementasi pengumpulan sampah V8 yang lebih rinci dapat dipelajari dengan membaca buku, dokumen, dan kode sumber terkait.
Mari kita lihat keadaan apa yang akan mendaur ulang mesin JavaScript.
2.1 Lingkup dan Referensi
Pemula sering secara keliru percaya bahwa ketika fungsi dieksekusi, objek yang dinyatakan di dalam fungsi akan dihancurkan. Namun pada kenyataannya, pemahaman ini tidak ketat dan komprehensif, dan mudah untuk bingung olehnya.
Referensi adalah mekanisme yang sangat penting dalam pemrograman JavaScript, tetapi anehnya, sebagian besar pengembang tidak akan memperhatikannya atau bahkan memahaminya. Referensi mengacu pada hubungan abstrak "akses kode ke objek". Ini agak mirip dengan pointer C/C ++, tetapi bukan hal yang sama. Referensi juga merupakan mekanisme paling penting dari mesin JavaScript dalam pengumpulan sampah.
Kode berikut adalah contoh:
Salinan kode adalah sebagai berikut:
// ......
var val = 'halo dunia';
fungsi foo () {
return function () {
return val;
};
}
global.bar = foo ();
// ......
Setelah membaca kode ini, dapatkah Anda memberi tahu objek mana yang masih bertahan setelah bagian kode ini dieksekusi?
Menurut prinsip yang relevan, objek yang tidak didaur ulang dan dirilis dalam kode ini termasuk VAL dan BAR (). Apa sebenarnya yang membuat mereka tidak dapat didaur ulang?
Bagaimana cara mesin JavaScript melakukan koleksi sampah? Algoritma pengumpulan sampah yang disebutkan di atas hanya digunakan selama daur ulang. Jadi bagaimana ia tahu objek mana yang dapat didaur ulang dan objek mana yang perlu terus bertahan? Jawabannya adalah referensi ke objek JavaScript.
Dalam kode JavaScript, bahkan jika Anda cukup menuliskan nama variabel sebagai satu baris tanpa melakukan apa pun, mesin JavaScript akan berpikir bahwa ini adalah perilaku akses ke objek dan ada referensi ke objek. Untuk memastikan bahwa perilaku pengumpulan sampah tidak mempengaruhi pengoperasian logika program, mesin JavaScript tidak boleh mendaur ulang objek yang digunakan, jika tidak itu akan berantakan. Oleh karena itu, standar untuk menilai apakah suatu objek sedang digunakan adalah apakah masih ada referensi ke objek. Tetapi pada kenyataannya, ini adalah kompromi, karena referensi JavaScript dapat ditransfer, sehingga mungkin ada beberapa referensi yang dibawa ke ruang lingkup global, tetapi pada kenyataannya, tidak perlu lagi mengaksesnya dalam logika bisnis dan harus didaur ulang, tetapi mesin JavaScript masih akan dengan tegas percaya bahwa program tersebut masih membutuhkannya.
Cara menggunakan variabel dan referensi dalam postur yang benar adalah kunci untuk mengoptimalkan JavaScript dari tingkat bahasa.
3. Optimalkan JavaScript Anda
Akhirnya sampai intinya. Terima kasih banyak telah melihat ini dengan sabar. Setelah begitu banyak perkenalan di atas, saya yakin Anda memiliki pemahaman yang baik tentang mekanisme manajemen memori JavaScript. Maka keterampilan berikut akan membuat Anda merasa lebih baik.
3.1 Manfaatkan fungsi yang baik
Jika Anda memiliki kebiasaan membaca proyek JavaScript yang sangat baik, Anda akan menemukan bahwa ketika mengembangkan kode javascript front-end, banyak orang besar sering menggunakan fungsi anonim untuk membungkusnya pada lapisan luar kode.
Salinan kode adalah sebagai berikut:
(fungsi() {
// Kode Bisnis Utama
}) ();
Beberapa bahkan lebih maju:
Salinan kode adalah sebagai berikut:
; (function (win, doc, $, undefined) {
// Kode Bisnis Utama
}) (jendela, dokumen, jQuery);
Bahkan solusi pemuatan modular front-end seperti persyaratan, seajs, ozjs, dll. Semua mengadopsi bentuk yang sama:
Salinan kode adalah sebagai berikut:
// persyaratan
define (['jQuery'], function ($) {
// Kode Bisnis Utama
});
// seajs
define ('module', ['dep', 'underscore'], function ($, _) {
// Kode Bisnis Utama
});
Jika Anda mengatakan bahwa banyak kode proyek open source node.js tidak diproses dengan cara ini, maka Anda salah. Sebelum benar -benar menjalankan kode, Node.js akan membungkus setiap file .js ke dalam formulir berikut:
Salinan kode adalah sebagai berikut:
(fungsi (ekspor, membutuhkan, modul, __dirname, __filename) {
// Kode Bisnis Utama
});
Apa manfaat dari melakukan ini? Kita semua tahu bahwa pada awal artikel, kami mengatakan bahwa JavaScript dapat memiliki fungsi -fungsi pelingkupan, dengan pernyataan dan ruang lingkup global. Kita juga tahu bahwa objek yang didefinisikan dalam ruang lingkup global dapat bertahan sampai proses keluar. Jika itu adalah objek besar, itu akan merepotkan. Misalnya, beberapa orang suka membuat templat di JavaScript:
Salinan kode adalah sebagai berikut:
<? php
$ db = mysqli_connect (server, pengguna, kata sandi, 'myapp');
$ topik = mysqli_query ($ db, "pilih * dari topik;");
?>
<! Doctype html>
<html lang = "en">
<head>
<meta charset = "UTF-8">
<title> Apakah Anda seorang pria lucu yang diundang oleh monyet? </title>
</head>
<body>
<ul id = "topik"> </ul>
<type script = "text/tmpl" id = "topic-tmpl">
<li>
<h1> <%= judul%> </h1>
<p> <%= konten%> </p>
</li>
</script>
<type skrip = "Teks/JavaScript">
var data = <? php echo json_encode ($ topik); ?>;
var topictmpl = document.queryselector ('#topic-tmpl'). innerHtml;
var render = function (tmlp, view) {
var dikompilasi = tmlp
.replace (// n/g, '// n')
.replace (/<%= ([/s/s]+?)%>/g, fungsi (kecocokan, kode) {
return '" + Escape (' + code + ') +"';
});
disusun = [
'var res = "";',
'with (view || {}) {',
'res = "' + dikompilasi + '";',
'}',
'return res;'
] .join ('/n');
var fn = fungsi baru ('view', disusun);
return fn (view);
};
var topics = document.queryselector ('#topik');
fungsi init ()
data.foreach (function (topic) {
topik.innerHtml += render (topictmpl, topik);
});
}
init ();
</script>
</body>
</html>
Kode semacam ini sering dapat dilihat dalam karya -karya pemula. Apa masalahnya di sini? Jika jumlah data yang diperoleh dari database sangat besar, variabel data akan menganggur setelah front-end menyelesaikan rendering template. Namun, karena variabel ini didefinisikan dalam ruang lingkup global, mesin JavaScript tidak akan mendaur ulang dan menghancurkannya. Ini akan terus ada dalam memori tumpukan generasi lama sampai halaman ditutup.
Tetapi jika kita membuat beberapa modifikasi yang sangat sederhana dan membungkus lapisan fungsi di luar kode logis, efeknya akan sangat berbeda. Setelah rendering UI selesai, referensi kode untuk data juga dibatalkan. Ketika fungsi terluar dieksekusi, mesin JavaScript mulai memeriksa objek di dalamnya, dan data dapat didaur ulang.
3.2 Jangan pernah mendefinisikan variabel global
Kami hanya membicarakan hal itu ketika suatu variabel didefinisikan dalam ruang lingkup global, mesin JavaScript tidak akan mendaur ulang dan menghancurkannya secara default. Ini akan terus ada dalam memori tumpukan generasi lama sampai halaman ditutup.
Kemudian kami selalu mengikuti prinsip: Jangan pernah menggunakan variabel global. Meskipun variabel global memang sangat mudah dikembangkan, masalah yang disebabkan oleh variabel global jauh lebih serius daripada kenyamanan yang dibawanya.
Membuat variabel lebih kecil kemungkinannya untuk didaur ulang;
1. Kebingungan mudah disebabkan ketika banyak orang berkolaborasi;
2. Sangat mudah untuk diganggu dalam rantai lingkup.
3. Sehubungan dengan fungsi pembungkus di atas, kami juga dapat menangani "variabel global" melalui fungsi pembungkus.
3.3 Variabel Unreferensi Secara Manual
Jika suatu variabel tidak diperlukan dalam kode bisnis, maka variabel dapat secara manual diereferping untuk membuatnya didaur ulang.
Salinan kode adalah sebagai berikut:
var data = { / * Beberapa data besar * /};
// bla bla bla
data = null;
3.4 Manfaatkan panggilan balik
Selain menggunakan penutupan untuk akses variabel internal, kami juga dapat menggunakan fungsi callback yang sekarang sangat populer untuk pemrosesan bisnis.
Salinan kode adalah sebagai berikut:
fungsi getData (callback) {
var data = 'beberapa data besar';
Callback (null, data);
}
getData (function (err, data) {
console.log (data);
Fungsi Callback adalah teknologi gaya kelulusan kelanjutan (CPS). Gaya pemrograman ini mentransfer fokus bisnis fungsi dari nilai pengembalian ke fungsi callback. Dan memiliki banyak manfaat dibandingkan penutupan:
1. Jika parameter yang dilewatkan adalah tipe dasar (seperti string, nilai numerik), parameter formal yang diteruskan dalam fungsi callback akan disalin nilai, dan akan lebih mudah untuk didaur ulang setelah kode bisnis digunakan;
2. Melalui callback, selain menyelesaikan permintaan sinkron, kami juga dapat menggunakannya dalam pemrograman asinkron, yang merupakan gaya penulisan yang sangat populer sekarang;
3. Fungsi panggilan balik itu sendiri biasanya merupakan fungsi anonim sementara. Setelah fungsi permintaan dijalankan, referensi ke fungsi callback itu sendiri akan dibatalkan dan akan didaur ulang.
3.5 Manajemen Penutupan yang Baik
Ketika kebutuhan bisnis kami (seperti ikatan acara melingkar, atribut pribadi, panggilan balik dengan argumen, dll.) Harus menggunakan penutupan, harap berhati -hati tentang detailnya.
Acara pengikatan loop dapat dikatakan sebagai kursus wajib untuk memulai dengan penutupan javascript. Mari kita asumsikan skenario: ada enam tombol, sesuai dengan enam acara. Ketika pengguna mengklik tombol, acara yang sesuai adalah output di tempat yang ditentukan.
Salinan kode adalah sebagai berikut:
var btns = document.queryselectorall ('. btn'); // 6 elemen
var output = document.queryselector ('#output');
var peristiwa = [1, 2, 3, 4, 5, 6];
// Kasus 1
untuk (var i = 0; i <btns.length; i ++) {
btns [i] .onClick = function (evt) {
output.innerText + = 'diklik' + peristiwa [i];
};
}
// Kasus 2
untuk (var i = 0; i <btns.length; i ++) {
btns [i] .onClick = (function (index) {
return function (evt) {
output.innerText + = 'diklik' + peristiwa [indeks];
};
})(Saya);
}
// Kasus 3
untuk (var i = 0; i <btns.length; i ++) {
btns [i] .onClick = (function (event) {
return function (evt) {
output.innerText + = 'diklik' + acara;
};
}) (Acara [i]);
}
Solusi pertama di sini jelas merupakan kesalahan peristiwa pengikat loop yang khas. Saya tidak akan menjelaskannya secara detail di sini. Anda dapat merujuk pada jawaban saya ke netizen secara rinci; Perbedaan antara solusi kedua dan ketiga terletak pada parameter yang dilewati dalam penutupan.
Parameter yang dilewatkan dalam skema kedua adalah loop subscript saat ini, sedangkan yang terakhir langsung diteruskan ke objek peristiwa yang sesuai. Faktanya, yang terakhir lebih cocok untuk sejumlah besar aplikasi data, karena dalam pemrograman fungsional JavaScript, parameter yang dilewati dalam panggilan fungsi adalah objek tipe dasar, sehingga parameter formal yang diperoleh dalam badan fungsi akan menjadi nilai salinan, sehingga nilai ini didefinisikan sebagai variabel lokal dalam ruang lingkup tubuh fungsi. Setelah pengikatan acara selesai, variabel acara dapat secara manual dierference untuk mengurangi penggunaan memori dalam lingkup luar. Selain itu, ketika suatu elemen dihapus, fungsi mendengarkan acara yang sesuai, objek peristiwa, dan fungsi penutupan juga dihancurkan dan didaur ulang.
3.6 Memori bukanlah cache
Peran caching dalam pengembangan bisnis memainkan peran penting dan dapat mengurangi beban sumber daya ruang-waktu. Tetapi perlu dicatat bahwa Anda tidak boleh menggunakan memori sebagai cache dengan mudah. Memori adalah sesuatu dari setiap inci tanah untuk pengembangan program apa pun. Jika itu bukan sumber yang sangat penting, jangan letakkan langsung di memori, atau merumuskan mekanisme kedaluwarsa untuk secara otomatis menghancurkan cache kedaluwarsa.
4. Periksa penggunaan memori JavaScript
Dalam pengembangan harian, kita juga dapat menggunakan beberapa alat untuk menganalisis dan memecahkan masalah penggunaan memori dalam JavaScript.
4.1 Browser Blink/Webkit
Di browser Blink/Webkit (Chrome, Safari, Opera dll.), Kami dapat menggunakan alat profil alat pengembang untuk melakukan pemeriksaan memori pada program kami.
4.2 Pemeriksaan Memori di Node.js
Di Node.js, kita dapat menggunakan modul Node-HeapDump dan Node-memwatch untuk pemeriksaan memori.
Salinan kode adalah sebagai berikut:
var heapdump = membutuhkan ('heapdump');
var fs = membutuhkan ('fs');
var path = membutuhkan ('path');
fs.writeFileSync (path.join (__ dirname, 'app.pid'), process.pid);
// ...
The code copy is as follows:<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 14px; line-height: 1.5em;">After introducing node-heapdump into the business code, we need to send a SIGUSR2 signal to the Node.js process at a certain run time, and let node-heapdump take a snapshot of memori tumpukan. </span>
Salin kode sebagai berikut: $ kill -usr2 (Cat app.pid)
Dengan cara ini, akan ada file snapshot yang dinamai dalam format heapdump- <sec>. <suec> .heapsnapshot di direktori file. Kami dapat membukanya menggunakan alat profil di alat pengembang browser dan memeriksanya.
5. Ringkasan
Artikel ini akan segera hadir. Berbagi ini terutama menunjukkan kepada Anda konten berikut:
1. JavaScript terkait erat dengan penggunaan memori di tingkat bahasa;
2. Manajemen memori dan mekanisme daur ulang di JavaScript;
3. Cara menggunakan memori secara lebih efisien sehingga javascript yang diproduksi dapat lebih diperluas dan energik;
4. Cara melakukan pemeriksaan memori saat menghadapi masalah memori.
Saya berharap bahwa dengan mempelajari artikel ini, Anda dapat menghasilkan kode JavaScript yang lebih baik untuk membuat ibu Anda merasa nyaman dan bos Anda merasa nyaman.