Pertanyaan Apakah penangan kejadian pada node DOM dihapus dengan node?


(Catatan: Saya menggunakan jQuery di bawah ini, tetapi pertanyaannya adalah benar-benar Javascript umum.)

Katakan saya punya div#formsection yang isinya berulang kali diperbarui menggunakan AJAX, seperti ini:

var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);

Setiap kali saya memperbarui div ini, saya memicu acara khusus, yang mengikat penangan acara ke beberapa elemen yang baru ditambahkan, seperti ini:

// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});

...

// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});

Jadi: Saya mengikat pengendali event ke beberapa node DOM, kemudian, menghapus node DOM dan memasukkan yang baru (html() melakukan itu) dan mengikat pengendali event ke node DOM baru.

Apakah penangan event saya dihapus bersama dengan node DOM yang mereka tuju? Dengan kata lain, saat saya memuat bagian baru, banyak sekali penangan kejadian yang tidak berguna yang menumpuk dalam memori browser, menunggu acara pada simpul DOM yang tidak ada lagi, atau apakah mereka dihapus ketika node DOM mereka dihapus?

Pertanyaan bonus: bagaimana bisa menguji ini sendiri?


32
2017-12-02 16:55


asal


Jawaban:


Fungsi pengendali acara tunduk pada Pengumpulan Sampah yang sama dengan variabel lain. Itu berarti mereka akan dihapus dari memori ketika interpreter menentukan bahwa tidak ada cara yang mungkin untuk mendapatkan referensi ke fungsi. Hanya menghapus node tetapi tidak menjamin pengumpulan sampah. Misalnya, ambil node ini dan pengendali event terkait

var node = document.getElementById('test');
node.onclick = function() { alert('hai') };

Sekarang mari kita hapus node dari DOM

node.parentNode.removeChild(node);

Begitu node tidak akan lagi terlihat di situs web Anda, tetapi jelas masih ada di memori, seperti halnya pengendali event

node.onclick(); //alerts hai

Asalkan referensi node masih bisa diakses entah bagaimana, itu sifat terkait (yang onclick adalah satu) akan tetap utuh.

Sekarang mari kita coba tanpa membuat variabel yang menggantung

document.getElementById('test').onclick = function() { alert('hai'); }

document.getElementById('test').parentNode.removeChild(document.getElementById('test'));

Dalam hal ini, tampaknya tidak ada cara lebih lanjut untuk mengakses node DOM #test, jadi ketika siklus pengumpulan sampah dijalankan, onclick handler harus dikeluarkan dari memori.

Tetapi ini adalah kasus yang sangat sederhana. Penggunaan javascript oleh Javascript dapat sangat menyulitkan penentuan kolektibilitas sampah. Mari mencoba mengikat fungsi handler event yang sedikit lebih kompleks onclick

document.getElementById('test').onclick = function() {
  var i = 0;
  setInterval(function() {
    console.log(i++);
  }, 1000);

  this.parentNode.removeChild(this);
};

Jadi ketika Anda mengklik pada #test, elemen akan langsung dihapus, namun satu detik kemudian, dan setiap detik setelahnya, Anda akan melihat nomor yang ditambahkan ke konsol Anda. Node dihapus, dan tidak ada referensi lebih lanjut untuk itu mungkin, namun tampaknya bagian-bagiannya tetap. Dalam hal ini fungsi handler event itu sendiri kemungkinan tidak disimpan dalam memori tetapi ruang lingkup yang dibuatnya.

Jadi jawaban yang saya kira adalah; tergantung. Jika ada rujukan, referensi yang dapat diakses ke node DOM yang dihapus, pengendali event terkait mereka akan tetap berada di memori, bersama dengan sisa propertinya. Bahkan jika ini tidak terjadi, ruang lingkup yang dibuat oleh fungsi event handler mungkin masih digunakan dan di memori.

Dalam sebagian besar kasus (dan dengan senang hati mengabaikan IE6) sebaiknya hanya mempercayai Pengumpul Sampah untuk melakukan tugasnya, Javascript bukan C setelah semua. Namun, dalam kasus-kasus seperti contoh terakhir, penting untuk menulis fungsi destruktor semacam fungsionalitas yang secara implisit mematikan.


21
2017-12-02 17:45



jQuery berusaha keras untuk menghindari kebocoran memori saat menghapus elemen dari DOM. Selama Anda menggunakan jQuery untuk menghapus node DOM, penghapusan event handler dan data tambahan harus ditangani oleh jQuery. Saya sangat merekomendasikan membaca John Resig's Rahasia Ninja JavaScript saat ia masuk ke detail besar tentang potensi kebocoran di berbagai peramban dan bagaimana pustaka JavaScript seperti jQuery mengatasi masalah ini. Jika Anda tidak menggunakan jQuery, Anda pasti harus khawatir tentang kebocoran memori melalui penangan acara yatim saat menghapus node DOM.


6
2017-12-02 17:56



Anda mungkin perlu menghapus penangan kejadian tersebut.

Memori Javascript bocor setelah membongkar halaman web

Dalam kode kami, yang tidak didasarkan pada jQuery, tetapi beberapa prototipe menyimpang, kami memiliki inisialisasi dan destruktor di kelas kami. Kami menemukan bahwa sangat penting untuk menghapus penangan kejadian dari objek DOM saat kami tidak hanya merusak aplikasi kami, tetapi juga widget individual selama waktu proses.

Kalau tidak, kita berakhir dengan kebocoran memori di IE.

Ini mengejutkan mudah untuk mendapatkan kebocoran memori di IE - bahkan ketika kita membongkar halaman, kita harus memastikan aplikasi "mati" secara bersih, merapikan segalanya - atau proses IE akan berkembang seiring waktu.

Edit: Untuk melakukan ini dengan benar, kami memiliki pengamat kejadian window Untuk unload peristiwa. Ketika peristiwa itu datang, rantai penghancur kami dipanggil untuk membersihkan semua objek dengan benar.

Dan beberapa kode contoh:

/**
 * @constructs
 */
initialize: function () {
    // call superclass
    MyCompany.Control.prototype.initialize.apply(this, arguments);

    this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},

destroy: function () {

    if (this.overMap) {
        this.overMap.destroy();
    }

    this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);

    // call superclass
    MyCompany.Control.prototype.destroy.apply(this, arguments);
},

2
2017-12-02 17:14



Belum tentu

Itu dokumentasi di jQuery's empty() metode keduanya menjawab pertanyaan saya dan memberi saya solusi untuk masalah saya. Ia mengatakan:

Untuk menghindari kebocoran memori, jQuery menghapus   konstruksi lain seperti data dan   penangan acara dari elemen anak   sebelum menghapus elemen   diri.

Jadi: 1) jika kita tidak melakukan ini secara eksplisit, kita akan mendapatkan kebocoran memori, dan 2) dengan menggunakan empty(), Saya bisa menghindari ini.

Oleh karena itu, saya harus melakukan ini:

formSection.empty();
formSection.html(newContents);

Masih belum jelas bagi saya .html() akan mengurus ini sendiri, tetapi satu baris tambahan untuk memastikan tidak mengganggu saya.


2
2017-12-02 17:49



Saya ingin tahu sendiri jadi setelah tes kecil, saya pikir jawabannya adalah ya.

removeEvent dipanggil ketika Anda. hapus () sesuatu dari DOM.

Jika Anda ingin melihatnya sendiri, Anda dapat mencoba ini dan ikuti kode dengan mengatur breakpoint. (Saya menggunakan jquery 1.8.1)

Tambahkan div baru terlebih dahulu:
$('body').append('<div id="test"></div>') 

  Memeriksa $.cache untuk memastikan tidak ada acara yang melekat padanya. (Itu harus menjadi objek terakhir)

  Lampirkan peristiwa klik untuk itu:
$('#test').on('click',function(e) {console.log("clicked")});

  Uji dan lihat objek baru di $.cache:
$('#test').click()

  Hapus dan Anda bisa melihat objeknya $.cache juga hilang:
$('#test').remove()


1
2017-10-20 04:45