Catatan Penerjemah: Saya pertama kali saya menerjemahkan bahasa asing, dan kata -kata saya agak tidak jelas, tetapi saya mencoba yang terbaik untuk mengekspresikan niat asli penulis dan tidak memiliki banyak polesan. Kritik dan koreksi dipersilakan. Selain itu, artikel ini panjang dan memiliki sejumlah besar informasi, yang mungkin sulit dicerna. Silakan tinggalkan pesan untuk membahas detail. Artikel ini terutama berfokus pada optimalisasi kinerja V8, dan beberapa konten tidak berlaku untuk semua mesin JS. Akhirnya, tunjukkan sumbernya saat mencetak ulang :)
=========================================================================
Banyak mesin JavaScript, seperti mesin V8 Google (digunakan oleh Chrome dan Node), dirancang khusus untuk aplikasi JavaScript besar yang memerlukan eksekusi cepat. Jika Anda seorang pengembang dan khawatir tentang penggunaan memori dan kinerja halaman, Anda harus memahami bagaimana mesin JavaScript di browser Anda bekerja. Baik itu V8, Spidermonkey (Firefox) Carakan (Opera), Chakra (IE) atau mesin lain, melakukan hal itu dapat membantu Anda mengoptimalkan aplikasi Anda dengan lebih baik . Ini tidak berarti bahwa Anda harus mengoptimalkan secara khusus untuk browser atau mesin tertentu, dan tidak pernah melakukan ini.
Namun, Anda harus bertanya pada diri sendiri beberapa pertanyaan:
Situs web yang memuat cepat seperti mobil sport cepat yang membutuhkan suku cadang khusus. Sumber Gambar: Dhybridcars.
Ada beberapa jebakan umum saat menulis kode kinerja tinggi, dan dalam artikel ini kami akan menunjukkan beberapa cara yang terbukti dan lebih baik untuk menulis kode.
Jika Anda tidak memiliki pemahaman yang mendalam tentang mesin JS, tidak ada masalah mengembangkan aplikasi web yang besar, sama seperti orang yang dapat mengemudi hanya melihat kap mesin tetapi bukan mesin di dalam kap mesin. Mengingat bahwa Chrome adalah pilihan pertama browser saya, mari kita bicara tentang mesin JavaScript -nya. V8 terdiri dari bagian -bagian inti berikut:
Pengumpulan sampah adalah bentuk manajemen memori , yang sebenarnya merupakan konsep seorang kolektor, mencoba mendaur ulang memori yang ditempati oleh objek yang tidak lagi digunakan. Dalam bahasa pengumpulan sampah seperti JavaScript, objek yang masih dirujuk dalam aplikasi tidak akan dihapus.
Referensi objek secara manual tidak diperlukan dalam banyak kasus. Semuanya akan bekerja dengan baik hanya dengan meletakkan variabel di mana mereka dibutuhkan (idealnya, seperti mungkin secara lokal, yaitu, fungsi yang mereka gunakan sebagai ganti lapisan luar fungsi).
Pengumpul sampah mencoba mendaur ulang memori. Sumber Gambar: Valtteri Mäki.
Dalam Javascript, tidak mungkin untuk memaksa pengumpulan sampah. Anda seharusnya tidak melakukan ini karena proses pengumpulan sampah dikendalikan oleh runtime dan ia tahu apa waktu terbaik untuk membersihkan.
Ada banyak diskusi tentang daur ulang memori JavaScript di internet tentang Delete Kata Kunci. Meskipun dapat digunakan untuk menghapus atribut (kunci) di objek (MAP), beberapa pengembang percaya itu dapat digunakan untuk memaksa "dereferences". Disarankan untuk menghindari penggunaan hapus bila memungkinkan. Dalam contoh di bawah ini delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x: 1}; hapus sapi; // sapi sejati; // belum diartikanAnda akan dengan mudah menemukan penghapusan referensi di perpustakaan JS populer - ini secara linguistik bertujuan. Perlu dicatat di sini bahwa menghindari memodifikasi struktur objek "panas" saat runtime. Mesin JavaScript dapat mendeteksi objek "panas" seperti itu dan mencoba mengoptimalkannya. Jika struktur objek tidak berubah secara signifikan selama siklus hidup, mesin akan lebih mudah baginya untuk mengoptimalkan objek, dan operasi penghapusan sebenarnya akan memicu perubahan struktural yang lebih besar ini, yang tidak kondusif untuk optimasi mesin.
Ada juga kesalahpahaman tentang bagaimana NULL bekerja. Mengatur referensi objek ke null tidak membuat objek "kosong", itu hanya menetapkan referensi untuk kosong. Menggunakan OX = NULL lebih baik daripada menggunakan Hapus, tetapi mungkin tidak diperlukan.
var o = {x: 1}; o = null; o; // nullo.x // typeerrorJika referensi ini adalah referensi terakhir ke objek saat ini, objek akan dikumpulkan. Jika referensi ini bukan referensi terakhir ke objek saat ini, objek dapat diakses dan tidak akan dikumpulkan sampah.
Perlu juga dicatat bahwa variabel global tidak dibersihkan oleh pengumpul sampah selama siklus hidup halaman. Tidak peduli berapa lama halaman terbuka, variabel dalam ruang lingkup objek global akan selalu ada ketika JavaScript berjalan.
var myglobalnamespace = {};Objek global hanya akan dibersihkan saat menyegarkan halaman, menavigasi ke halaman lain, menutup tab, atau keluar dari browser. Variabel dalam lingkup fungsi akan dibersihkan ketika mereka berada di luar cakupan, yaitu, ketika fungsi keluar, tidak ada referensi, dan variabel tersebut akan dibersihkan.
Agar pengumpul sampah mengumpulkan sebanyak mungkin objek, jangan memegang benda yang tidak lagi digunakan . Berikut beberapa hal yang perlu diingat:
Selanjutnya, mari kita bicara tentang fungsi. Seperti yang telah kami katakan, pengumpulan sampah bekerja dengan mendaur ulang blok memori (objek) yang tidak lagi dapat diakses. Untuk mengilustrasikan ini lebih baik, berikut adalah beberapa contoh.
function foo () {var bar = new thargerObject (); bar.somecall ();}Ketika Foo kembali, objek yang ditunjuk oleh bar akan secara otomatis didaur ulang oleh pengumpul sampah karena tidak memiliki referensi yang ada.
Membandingkan:
function foo () {var bar = new thargerObject (); bar.somecall (); return bar;} // di suatu tempat elsevar b = foo ();Sekarang kami memiliki referensi yang menunjuk ke objek batang, sehingga siklus hidup objek bar berlanjut dari panggilan ke foo sampai penelepon menentukan variabel lain B (atau B berada di luar cakupan).
Saat Anda melihat suatu fungsi, kembalikan fungsi internal, yang akan keluar dari akses lingkup, bahkan setelah fungsi eksternal dieksekusi. Ini adalah penutupan dasar - ekspresi variabel yang dapat diatur dalam konteks tertentu. Misalnya:
function sum (x) {function sumit (y) {return x + y; }; return sumit;} // usagevar suma = sum (4); var sumb = suma (3); console.log (sumb); // pengembalian 7Objek fungsi (sumit) yang dihasilkan dalam konteks panggilan jumlah tidak dapat didaur ulang. Ini direferensikan oleh variabel global (SUMA) dan dapat dipanggil melalui suma (n).
Mari kita lihat contoh lain, di mana kita dapat mengakses variabel terbesar?
var a = function () {var terbesar = array baru (1000000) .join ('x'); return function () {return terbesar; };} ();Ya, kita dapat mengakses terbesar melalui (), jadi itu tidak didaur ulang. Bagaimana dengan yang berikut?
var a = function () {var smallstr = 'x'; var terbesar = array baru (1000000) .jamin ('x'); return function (n) {return smallstr; };} ();Kami tidak dapat lagi mengakses terbesar, itu sudah merupakan kandidat pengumpulan sampah. [Catatan Penerjemah: Karena terbesar tidak lagi memiliki referensi eksternal]
Salah satu tempat terburuk untuk bocor memori adalah di loop, atau di setTimeout ()/setInterval (), tetapi ini cukup umum. Pikirkan tentang contoh -contoh berikut:
var myobj = {callmemaybe: function () {var myref = this; var val = setTimeout (function () {console.log ('waktu habis!'); myref.callmemaybe ();}, 1000); }}; Jika kita menjalankan myobj.callmemaybe (); Untuk memulai timer, kita dapat melihat bahwa konsol mencetak "Waktu habis!" setiap detik. Jika myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
Perlu diingat juga bahwa referensi dalam panggilan setTimeout/setInterval (seperti fungsi) perlu dieksekusi dan diselesaikan sebelum dapat dikumpulkan sampah.
Jangan pernah mengoptimalkan kode sampai Anda benar -benar membutuhkannya. Sekarang Anda sering dapat melihat beberapa tolok ukur yang menunjukkan bahwa N lebih dioptimalkan dalam V8 daripada M, tetapi jika Anda mengujinya dalam kode atau aplikasi modul, Anda akan menemukan bahwa optimasi ini benar -benar jauh lebih kecil dari yang Anda harapkan.
Lebih baik tidak melakukan apa pun daripada melakukannya terlalu banyak. Sumber Gambar: Tim Sheerman-Chase.
Misalnya, kami ingin membuat modul seperti itu:
Ada beberapa faktor berbeda dalam masalah ini, meskipun juga mudah dipecahkan. Bagaimana cara menyimpan data, bagaimana kita secara efisien menggambar tabel dan menambahkannya ke DOM, dan bagaimana kita menangani acara tabel dengan lebih baik?
Pendekatan awal (naif) untuk menghadapi masalah ini adalah menggunakan objek untuk menyimpan data dan memasukkannya ke dalam array, menggunakan jQuery untuk melintasi data untuk menggambar tabel dan menambahkannya ke DOM, dan akhirnya menggunakan ikatan peristiwa dengan perilaku klik yang kami harapkan.
Catatan: Ini bukan apa yang harus Anda lakukan
var modulea = function () {return {data: dataArrayObject, init: function () {this.addtable (); this.addevents (); }, addtable: function () {for (var i = 0; i <rows; i ++) {$ tr = $ ('<tr> </tr>'); untuk (var j = 0; j <this.data.length; j ++) {$ tr.Append ('<td>' + this.data [j] ['id'] + '</td>'); } $ tr.Appendto ($ tBody); }}, addEvents: function () {$ ('TABLE TD'). ON ('klik', function () {$ (this) .toggleClass ('Active');}); }};} ();Kode ini secara sederhana dan efektif menyelesaikan tugas.
Tetapi dalam hal ini, data yang kami lintasi hanyalah ID properti numerik yang seharusnya disimpan dalam array. Menariknya, lebih baik menggunakan DocumentFragment dan metode DOM lokal secara langsung daripada menghasilkan tabel menggunakan jQuery (dengan cara ini), dan tentu saja, proxy acara memiliki kinerja yang lebih tinggi daripada mengikat masing -masing TD saja.
Perhatikan bahwa meskipun JQuery menggunakan DocumentFragment secara internal, dalam contoh kami, kode panggilan ditambahkan dalam satu loop dan panggilan ini melibatkan beberapa pengetahuan kecil lainnya, sehingga efek optimisasi di sini tidak terlalu baik. Semoga ini tidak akan menjadi titik rasa sakit, tetapi pastikan untuk melakukan tolok ukur untuk memastikan kode Anda baik -baik saja.
Sebagai contoh kami, praktik di atas membawa peningkatan kinerja (yang diinginkan). Proksi acara adalah peningkatan untuk mengikat sederhana, dan DocumentFragment opsional juga membantu.
var moduled = function () {return {data: dataArray, init: function () {this.addtable (); this.addevents (); }, addtable: function () {var td, tr; var fragment = document.createDocumentFragment (); var fragment2 = document.createDocumentFragment (); untuk (var i = 0; i <rows; i ++) {tr = document.createElement ('tr'); untuk (var j = 0; j <this.data.length; j ++) {td = document.createElement ('td'); td.AppendChild (Document.CreateTextNode (this.data [j])); Frag2.AppendChild (TD); } tr.AppendChild (frag2); Frag.AppendChild (TR); } tbody.AppendChild (frag); }, addEvents: function () {$ ('tabel'). on ('click', 'td', function () {$ (this) .toggleClass ('Active');}); }};} ();Mari kita lihat cara lain untuk meningkatkan kinerja. Anda mungkin telah membaca bahwa menggunakan mode prototipe lebih baik daripada mode modul, atau Anda telah mendengar bahwa menggunakan kerangka kerja template JS berkinerja lebih baik. Terkadang ini benar, tetapi mereka digunakan untuk membuat kode lebih mudah dibaca. Ngomong -ngomong, ada prekompilasi! Mari kita lihat bagaimana kinerjanya dalam praktik?
moduleg = function () {}; moduleg.prototype.data = dataArray; moduleg.prototype.init = function () {this.addtable (); this.addevents ();}; moduleg.prototype.addtable = function () {var template = _.template ($ ('#template'). text ()); var html = template ({'data': this.data}); $ tbody.append (html);}; moduleg.prototype.addevents = function () {$ ('tabel'). on ('klik', 'td', function () {$ (this) .toggleClass ('Active');});}; var modg = moduleg baru ();Ternyata peningkatan kinerja yang dibawa dalam situasi ini dapat diabaikan. Pilihan template dan prototipe tidak benar -benar menawarkan apa -apa lagi. Dengan kata lain, kinerja bukanlah alasan mengapa pengembang menggunakannya, dan keterbacaan, model warisan dan pemeliharaan yang dibawa ke kode adalah alasan sebenarnya.
Masalah yang lebih kompleks termasuk menggambar secara efisien pada kanvas dan memanipulasi data piksel dengan atau tanpa array jenis.
Sebelum menggunakan beberapa metode untuk aplikasi Anda sendiri, pastikan untuk mempelajari lebih lanjut tentang pembandingan solusi ini. Mungkin seseorang masih ingat tembak-menembak dan ekstensi selanjutnya dari template JS. Anda perlu mengetahui bahwa pembandingan tidak ada dalam aplikasi virtual yang tidak dapat Anda lihat, tetapi harus menguji optimisasi yang dibawa oleh kode Anda yang sebenarnya.
Titik optimasi setiap mesin V8 diperkenalkan secara rinci di luar ruang lingkup artikel ini. Tentu saja, ada banyak tip yang patut disebutkan di sini. Ingat tips ini dan Anda dapat mengurangi kode yang memiliki kinerja yang buruk.
fungsi add (x, y) {return x+y;} add (1, 2); tambahkan ('a', 'b'); tambahkan (my_custom_object, tidak terdefinisi);Untuk konten lebih lanjut, silakan lihat berbagi Daniel Clifford di Google I/O. Melanggar batas kecepatan JavaScript dengan V8. Mengoptimalkan untuk V8 - Seri juga sangat layak dibaca.
Hanya ada satu perbedaan utama antara objek dan array dalam JavaScript, yaitu, properti panjang ajaib array. Jika Anda mempertahankan properti ini sendiri, maka objek dan array di V8 secepat yang ada di array.
Kloning objek adalah masalah umum bagi pengembang aplikasi. Sementara berbagai tolok ukur dapat membuktikan bahwa V8 menangani masalah ini dengan baik, hati -hati. Menyalin hal -hal besar biasanya lebih lambat - jangan lakukan itu. For..di loop di JS sangat buruk karena memiliki spesifikasi iblis dan mungkin tidak akan pernah lebih cepat daripada objek apa pun di mesin apa pun.
Ketika Anda yakin untuk menyalin objek pada jalur kode kinerja kritis, gunakan array atau fungsi "copy constructor" khusus untuk secara eksplisit menyalin setiap properti. Ini mungkin cara tercepat:
function clone (asli) {this.foo = original.foo; this.bar = original.bar;} var copy = new clone (asli);Fungsi caching saat menggunakan mode modul dapat menyebabkan peningkatan kinerja. Lihat contoh di bawah ini, karena selalu membuat salinan baru fungsi anggota, perubahan yang Anda lihat mungkin lebih lambat.
Perhatikan juga bahwa menggunakan metode ini jelas lebih baik, tidak hanya mengandalkan mode prototipe (dikonfirmasi oleh tes JSPERF).
Peningkatan Kinerja Saat Menggunakan Mode Modul atau Mode Prototipe
Ini adalah uji perbandingan kinerja mode prototipe dan mode modul:
// pola prototipe klass1 = function () {} klass1.prototype.foo = function () {log ('foo'); } Klass1.prototype.bar = function () {log ('bar'); } // Modul pola klass2 = function () {var foo = function () {log ('foo'); }, bar = function () {log ('bar'); }; return {foo: foo, bar: bar}} // pola modul dengan fungsi cache var foofunction = function () {log ('foo'); }; var barfunction = function () {log ('bar'); }; Klass3 = function () {return {foo: foofunction, bar: barfunction}} // tes iterasi // prototipal var i = 1000, objs = []; while (i--) {var o = Klass1 baru () objs.push (klass1 baru ()); o.bar; o.foo; } // Modul Pola var i = 1000, objs = []; while (i--) {var o = Klass1 baru () objs.push (klass1 baru ()); o.bar; o.foo; } // Modul Pola var i = 1000, objs = []; while (i--) {var o = klass2 () objs.push (klass2 ()); o.bar; o.foo; } // pola modul dengan fungsi cacheed var i = 1000, objs = []; while (i--) {var o = klass3 () objs.push (klass3 ()); o.bar; o.foo; } // Lihat tes untuk detail lengkapSelanjutnya, mari kita bicara tentang teknik yang berkaitan dengan array. Secara umum, jangan menghapus elemen array , yang akan membuat transisi array ke representasi internal yang lebih lambat. Ketika indeks menjadi jarang, V8 akan mengubah elemen menjadi pola kamus yang lebih lambat.
Array literal sangat berguna, dan dapat mengisyaratkan ukuran dan jenis array VM. Biasanya digunakan dalam array dengan ukuran kecil.
// Di sini V8 dapat melihat bahwa Anda menginginkan array 4-elemen yang berisi angka: var a = [1, 2, 3, 4]; // Jangan lakukan ini: a = []; // di sini v8 tidak tahu apa -apa tentang arrayfor (var i = 1; i <= 4; i ++) {a.push (i);}Ini sama sekali bukan ide yang baik untuk menyimpan data jenis campuran (seperti angka, string, tidak terdefinisi, benar/salah) dalam array. Misalnya var arr = [1, "1", tidak terdefinisi, benar, "benar"]
Pengujian Kinerja Jenis Inferensi
Seperti yang telah kita lihat, array bilangan bulat adalah yang tercepat.
Saat Anda menggunakan array yang jarang, berhati -hatilah untuk mengakses elemen akan jauh lebih lambat daripada array penuh. Karena V8 tidak akan mengalokasikan seluruh ruang untuk array yang hanya menggunakan bagian dari ruang. Sebaliknya, itu dikelola dalam kamus, menyimpan kedua ruang tetapi meluangkan waktu untuk mengakses.
Pengujian array yang jarang dan array penuh
Jangan pra-alokasi array besar (seperti elemen yang lebih besar dari 64k), ukuran maksimumnya, tetapi harus dialokasikan secara dinamis. Sebelum menguji kinerja kami dalam artikel ini, ingatlah bahwa ini hanya berlaku untuk beberapa mesin JavaScript.
Literal kosong dan array yang dialokasikan sebelumnya diuji di browser yang berbeda
Nitro (Safari) lebih bermanfaat untuk array yang dialokasikan sebelumnya. Pada mesin lain (V8, spidermonkey), pra-alokasi tidak efisien.
Pengujian array yang telah ditentukan sebelumnya
// ARRAYVAR ARR Kosong = []; for (var i = 0; i <1000000; i ++) {arr [i] = i;} // arrayvar arrayvar pra-allocated = array baru (1000000); untuk (var i = 0; i <1000000; i ++) {arr [i] = i;}Di dunia aplikasi web, kecepatan adalah segalanya. Tidak ada pengguna yang ingin menggunakan aplikasi tabel yang membutuhkan waktu beberapa detik untuk menghitung jumlah total kolom atau untuk meringkas informasi. Ini adalah alasan penting mengapa Anda ingin memeras setiap sedikit kinerja dalam kode Anda.
Sumber Gambar: Per olof Forsberg.
Memahami dan meningkatkan kinerja aplikasi Anda sangat berguna, tetapi juga sulit. Kami merekomendasikan langkah -langkah berikut untuk menyelesaikan poin nyeri kinerja:
Beberapa alat dan teknik yang direkomendasikan di bawah ini dapat membantu Anda.
Ada banyak cara untuk menjalankan tolok ukur untuk cuplikan kode JavaScript untuk menguji kinerjanya - asumsi umum adalah bahwa tolok ukur hanya membandingkan dua cap waktu. Pola ini ditunjukkan oleh tim JSPERF dan digunakan dalam rangkaian patokan Sunspider dan Kraken:
var TotalTime, start = Tanggal baru, iterasi = 1000; while (iterasi--) {// Kode cuplikan pergi ke sini} // TotalTime → Jumlah jutaan orang yang diambil // untuk menjalankan cuplikan kode 1000 timestotalTime = Tanggal baru-mulai;Di sini, kode yang akan diuji ditempatkan dalam satu loop dan menjalankan beberapa kali (misalnya, 6 kali). Setelah ini, tanggal mulai dikurangi dari tanggal akhir, dan waktu yang dibutuhkan untuk melakukan operasi di loop diturunkan.
Namun, benchmarking ini melakukan hal -hal yang terlalu sederhana, terutama jika Anda ingin menjalankan tolok ukur di beberapa browser dan lingkungan. Kolektor sampah itu sendiri memiliki dampak tertentu pada hasil. Bahkan jika Anda menggunakan solusi seperti window.performance, kekurangan ini harus diperhitungkan.
Terlepas dari apakah Anda hanya menjalankan bagian benchmark dari kode, tulis test suite atau kode perpustakaan patokan, tolok ukur JavaScript sebenarnya lebih dari yang Anda pikirkan. Untuk tolok ukur panduan yang lebih rinci, saya sangat menyarankan Anda membaca tolok ukur JavaScript yang disediakan oleh Mathias Bynens dan John-David Dalton.
Alat pengembang Chrome memiliki dukungan yang baik untuk analitik JavaScript. Anda dapat menggunakan fitur ini untuk mendeteksi fungsi mana yang memakan waktu sebagian besar waktu sehingga Anda dapat mengoptimalkannya. Ini penting, bahkan perubahan kecil dalam kode dapat memiliki dampak yang signifikan pada kinerja keseluruhan.
Panel Analisis Alat Pengembang Chrome
Proses analisis mulai mendapatkan garis dasar kinerja kode dan kemudian memanifestasikannya dalam bentuk garis waktu. Ini akan memberi tahu kami berapa lama kode yang dibutuhkan untuk dijalankan. Tab Profil memberi kita perspektif yang lebih baik tentang apa yang terjadi dalam aplikasi. File analisis CPU JavaScript menunjukkan berapa banyak waktu CPU yang digunakan dalam kode kami, file analisis pemilih CSS menunjukkan berapa banyak waktu yang dihabiskan untuk pemrosesan pemroses, dan heap snapshot menunjukkan berapa banyak memori yang digunakan dalam objek kami.
Dengan alat -alat ini, kami dapat memisahkan, menyesuaikan, dan menganalisis ulang untuk mengukur apakah optimasi kinerja fungsional atau operasional kami sebenarnya efektif.
Tab Profil menampilkan informasi kinerja kode.
Pengantar analisis yang baik, baca profil JavaScript Zack Grossbart dengan alat pengembang Chrome.
Kiat: Idealnya, jika Anda ingin memastikan analisis Anda tidak terpengaruh oleh aplikasi atau ekstensi yang diinstal, Anda dapat menggunakan bendera --user-data-dir <empty_directory> untuk memulai chrome. Dalam kebanyakan kasus, tes optimasi metode ini harus cukup, tetapi juga membutuhkan lebih banyak waktu untuk Anda. Inilah yang dapat membantu logo V8.
Di dalam Google, alat pengembang Chrome banyak digunakan oleh tim seperti Gmail untuk membantu mendeteksi dan memecahkan masalah memori.
Statistik memori dalam alat pengembang chrome
Memori menghitung penggunaan memori pribadi, ukuran tumpukan JavaScript, jumlah node DOM, pembersihan penyimpanan, penghitung mendengarkan acara dan pengumpul sampah yang menjadi perhatian tim kami. Teknologi "3 Snapshot" yang Direkomendasikan Membaca Loreena Lee. Titik kunci dari teknik ini adalah untuk mencatat beberapa perilaku dalam aplikasi Anda, memaksa pengumpulan sampah, periksa apakah jumlah node DOM telah dikembalikan ke baseline yang diharapkan, dan kemudian menganalisis snapshot dari tiga tumpukan untuk menentukan apakah ada kebocoran memori.
Manajemen memori aplikasi satu halaman (seperti AngularJS, Backbone, Ember) sangat penting, mereka hampir tidak pernah menyegarkan halaman. Ini berarti bahwa kebocoran memori mungkin cukup jelas. Aplikasi satu halaman pada terminal seluler penuh dengan jebakan karena perangkat memiliki memori terbatas dan menjalankan aplikasi seperti klien email atau jejaring sosial untuk waktu yang lama. Semakin besar kemampuannya, semakin berat tanggung jawabnya.
Ada banyak cara untuk menyelesaikan masalah ini. Di tulang punggung, pastikan untuk menggunakan Dispose () untuk menangani tampilan dan referensi lama (saat ini tersedia di tulang punggung (EDGE). Fungsi ini baru -baru ini ditambahkan, menghapus pawang yang ditambahkan ke objek "acara" tampilan, dan pendengar acara melalui model yang diserahkan. Pendengar untuk menghindari kebocoran memori ketika mereka mendeteksi elemen itu dihapus.
Beberapa nasihat bijak dari Derick Bailey:
Alih -alih memahami bagaimana acara dan referensi bekerja, ikuti aturan standar untuk mengelola memori dalam JavaScript. Jika Anda ingin memuat data ke dalam koleksi tulang punggung yang penuh dengan objek pengguna, Anda ingin menghapus koleksi sehingga tidak lagi mengambil memori, maka semua referensi ke koleksi dan referensi ke objek dalam koleksi diperlukan. Setelah referensi yang digunakan jelas, sumber daya didaur ulang. Ini adalah aturan pengumpulan sampah JavaScript standar.
Dalam artikel tersebut, Derick mencakup banyak kelemahan memori umum saat menggunakan backbone.js dan cara menyelesaikan masalah ini.
Tutorial tentang debugging memori kebocoran di Node oleh Felix Geisendörfer juga layak dibaca, terutama ketika membentuk bagian dari tumpukan spa yang lebih luas.
Ketika browser menata ulang elemen dalam dokumen, mereka perlu dihitung ulang dan posisi serta geometri mereka, yang kami sebut reflow. Reflow memblokir operasi pengguna di browser, sehingga sangat membantu untuk memahami bahwa meningkatkan waktu reflow ditingkatkan.
Bagan Waktu Reflow
Anda harus memicu reflow atau menggambar ulang dalam batch, tetapi menggunakan metode ini secukupnya. Penting juga untuk mencoba untuk tidak berurusan dengan DOM. Anda dapat menggunakan DocumentFragment, objek dokumen ringan. Anda dapat menggunakannya sebagai cara untuk mengekstraksi bagian dari pohon dokumen, atau membuat dokumen baru "fragmen". Alih -alih terus menambahkan node DOM, lebih baik melakukan operasi penyisipan DOM hanya sekali setelah menggunakan fragmen dokumen untuk menghindari reflow yang berlebihan.
Misalnya, kami menulis fungsi untuk menambahkan 20 div ke elemen. Jika Anda cukup menambahkan div ke elemen setiap kali, ini akan memicu 20 reflows.
fungsi addDivs (elemen) {var div; untuk (var i = 0; i <20; i ++) {div = document.createelement ('div'); div.innerhtml = 'heya!'; Element.AppendChild (Div); }}Untuk mengatasi masalah ini, kita dapat menggunakan DocumentFragment sebagai gantinya, kita dapat menambahkan div baru sekaligus. Menambahkan DocumentFragment ke DOM setelah selesai hanya akan memicu reflow sekali.
fungsi addDivs (elemen) {var div; // Membuat DocumentFragment baru yang kosong. var fragment = document.createDocumentFragment (); untuk (var i = 0; i <20; i ++) {div = document.createElement ('a'); div.innerhtml = 'heya!'; Fragment.AppendChild (Div); } element.appendChild (fragment);}Lihat Buat web lebih cepat, optimasi memori JavaScript, dan menemukan kebocoran memori.
Untuk membantu menemukan kebocoran memori JavaScript, pengembang Google (Marja Hölttä dan Jochen Eisinger) mengembangkan alat yang bekerja bersama dengan alat pengembang chrome untuk mengambil snapshot dari tumpukan dan mendeteksi objek apa yang menyebabkan kebocoran memori.
Alat Deteksi Kebocoran Memori JavaScript
Ada artikel lengkap tentang cara menggunakan alat ini. Dianjurkan agar Anda pergi ke halaman proyek Detektor Kebocoran Memori untuk diri Anda sendiri.
Jika Anda ingin tahu mengapa alat seperti itu belum diintegrasikan ke dalam alat pengembangan kami, ada dua alasan. Awalnya dirancang untuk membantu kami menangkap beberapa skenario memori spesifik di perpustakaan penutupan, yang lebih cocok sebagai alat eksternal.
Chrome mendukung beberapa bendera langsung ke V8 untuk hasil output optimisasi mesin yang lebih rinci. Misalnya, ini dapat melacak optimalisasi V8:
"/Aplikasi/Google Chrome/Google Chrome" --js-flags = "-trace-opt --trace-deopt"
Pengguna Windows dapat menjalankan chrome.exe js-flags = "trace-opt trace-deopt"
Saat mengembangkan aplikasi, logo V8 di bawah ini dapat digunakan.
Skrip pemrosesan V8 menggunakan * (tanda bintang) untuk mengidentifikasi fungsi yang dioptimalkan dan menggunakan ~ (bergelombang) untuk mewakili fungsi yang tidak dioptimalkan.
Jika Anda tertarik untuk mempelajari lebih lanjut tentang logo V8 dan bagaimana interior V8 bekerja, sangat disarankan untuk membaca posting V8 V8 V8 yang sangat baik untuk membaca vyacheslav Egorov.
High Precision Time (HRT) adalah antarmuka waktu presisi tinggi tingkat submillisecond yang tidak memberikan dampak pada waktu sistem dan penyesuaian pengguna. Ini dapat dianggap sebagai metode pengukuran yang lebih akurat daripada tanggal dan tanggal baru. Sekarang (). Ini banyak membantu kita dalam menulis tolok ukur.
High-Precision Time (HRT) memberikan akurasi waktu sub-milidetik saat ini
Saat ini HRT digunakan dalam chrome (versi stabil) di window.performance.webkitnow (), tetapi awalan dibuang di Chrome Canary, yang memungkinkan untuk memanggil melalui window.performance.now (). Paul Irish memposting lebih banyak tentang HRT di html5rocks.
Sekarang kita tahu waktu yang tepat saat ini, apakah ada API yang dapat secara akurat mengukur kinerja halaman? Well, now there is a Navigation Timing API that provides an easy way to get accurate and detailed time measurement records when web pages are loaded and presented to users. You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
记住这几点可以保证你获取了本文的大部分信息,希望对你有所帮助!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani