Topik temu duga representatif

Temu Duga Frontend: Bagaimana Anda Mendiagnosis dan Membaiki Kebocoran Memori JavaScript?

FrontendSukar
Pasukan Editorial Offer.ccDiterbitkan Dikemas kini

Soalan

Papan pemuka pesanan satu halaman React mencipta carta, melanggan mesej, dan memerhatikan perubahan tetingkap setiap kali laluan butirannya dibuka. Selepas 20 perjalanan pergi dan balik, halaman menjadi ketara perlahan; selepas pemungutan sampah (garbage collection) dipaksa, timbunan JS, nod DOM, dan bilangan pendengar masih meningkat setiap pusingan. Terangkan bagaimana anda membuktikan kebocoran memori, mencari laluan penahanannya dengan Chrome DevTools, membaiki kitaran hayat sumber, dan mereka bentuk ujian penerimaan yang boleh dihasilkan semula.

Gesaan dan Konteks Berkenaan

Papan pemuka pesanan satu halaman React mengandungi carta langsung, langganan peristiwa pesanan, dan tingkah laku reka letak responsif. Pengguna membuka laluan butiran pesanan, kembali ke senarai, dan mengulangi perjalanan tersebut sebanyak 20 kali. Halaman kemudiannya menjadi perlahan. Rakaman Chrome Performance menunjukkan bahawa selepas operasi yang sama dan peluang pemungutan sampah pada akhir setiap pusingan, paras rendah timbunan JS terus meningkat. Bilangan nod DOM dan pendengar juga gagal kembali ke julat asalnya.

Komponen yang berkaitan dipermudahkan di bawah:

tsx
useEffect(() => {
  const chart = createOrderChart(containerRef.current)

  window.addEventListener("resize", () => chart.resize())
  orderBus.on("order", (order) => chart.append(order))
  const timerId = window.setInterval(() => chart.refresh(), 5_000)

  return () => {
    window.clearInterval(timerId)
  }
}, [])

Temu duga ini meminta rantaian bukti yang boleh disemak. Calon mesti membezakan peruntukan biasa, kembung memori (memory bloat), pemungutan sampah yang kerap, dan kebocoran sebenar; mengenal pasti apa yang menahan objek yang sepatutnya dimusnahkan daripada punca GC; membaiki jangka hayat pendengar, langganan, pemasa, dan tika pihak ketiga; serta membuktikan pembetulan tersebut dengan gelung tindakan yang sama.

Bank soalan frontend 2025 awam secara eksplisit bertanya bagaimana untuk mengenal pasti dan membaiki kebocoran memori JavaScript. Panduan temu duga JavaScript 2025 yang lain menggunakan SPA yang menjadi perlahan selepas navigasi berulang dan bertanya bagaimana DevTools akan mencari kebocoran tersebut. Satu perkongsian temu duga awam baru-baru ini merekodkan soalan susulan tentang pemungutan sampah, penutupan (closures), pendengar, dan penyahlekapan (unmounting) komponen. Niat carian adalah konkrit: calon memerlukan aliran kerja diagnostik pelayar yang boleh dilaksanakan dan bukannya senarai yang sekadar berakhir dengan "kosongkan pemasa."

Perkara yang Dinilai oleh Penemu Duga

Kemahiran pertama ialah menetapkan peraturan keputusan yang sah. Peningkatan objek sementara, puncak yang lebih tinggi, atau angka memori proses yang meningkat tidak membuktikan kebocoran secara bersendirian. Jawapan yang kukuh menetapkan pelayar, binaan, data, dan jujukan tindakan yang tetap; memberikan pemungutan peluang yang setanding antara pusingan; dan membandingkan beberapa garis dasar langsung. Peruntukan biasa boleh membentuk gigi gergaji yang naik dan turun. Objek yang kekal wujud dan terkumpul selepas operasi yang sama mewajarkan penyiasatan terhadap laluan rujukannya.

Kemahiran kedua ialah kebolehcapaian (reachability). Pemungut sampah JavaScript moden menandakan objek yang boleh dicapai daripada punca (roots) seperti objek global. Aplikasi yang tidak lagi menginginkan sesuatu objek tidak menjadikan fakta tersebut kelihatan kepada pemungut. Jika pendengar window, bas peristiwa, pemasa, cache, atau pustaka pihak ketiga masih memegang rujukan kuat, objek tersebut kekal boleh dicapai. Kitaran rujukan sahaja tidak semestinya bocor: seluruh kumpulan kitaran boleh dipungut sebaik sahaja tiada laluan daripada punca mencapainya.

Kemahiran ketiga ialah membaca bukti dan bukannya sekadar membuka panel Memory:

BuktiSoalan yang dijawabnyaPerkara yang tidak dapat dibuktikannya secara bersendirian
Pengurus Tugas / Keluk memori PerformanceAdakah kerja yang berulang terus meningkatkan memori?Objek mana yang menahannya
Perbandingan Petikan Heboh (Heap Snapshot Comparison)Jenis langsung dan kiraan rujukan mana yang meningkat?Sama ada pertumbuhan memenuhi kontrak cache
Penahan / laluan penahanan (Retainers / retaining path)Rantaian rujukan mana yang menyambungkan objek ke punca?Masa kod pemilik sepatutnya melepaskannya
Instrumentasi peruntukan pada garis masa (Allocation instrumentation on timeline)Tindakan mana yang memperuntukkan objek yang kemudiannya kekal wujud?Bahawa setiap peruntukan adalah kebocoran
Bilangan nod DOM, dokumen, dan pendengarAdakah pertumbuhan berorientasikan DOM atau pendengar?Memori proses lengkap di luar timbunan JS

Kemahiran terakhir ialah pemilikan sumber. Sempadan kitaran hayat yang mencipta sumber harus melepaskannya. AbortController boleh mengalih keluar pendengar DOM yang didaftarkan dengan signal miliknya, tetapi ia tidak memutuskan sambungan ResizeObserver, menutup WebSocket, menyahlanggan bas peristiwa, atau memusnahkan carta pihak ketiga. Sumber-sumber tersebut masih memerlukan operasi disconnect, close, unsubscribe, atau destroy mereka sendiri.

Soalan untuk Dijelaskan Sebelum Menjawab

  • Apakah sebenarnya yang menghasilkan semula masalah tersebut? Tetapkan titik masuk, tindakan kembali, syarat

kesediaan setiap pusingan, volum data, dan bilangan pengulangan. Pesanan yang berbeza dalam setiap pusingan mencemarkan perbezaan petikan dengan perbezaan data perniagaan.

  • Ukuran manakah yang berkembang? Jejak memori OS, timbunan JS, nod DOM, dokumen, dan pendengar meliputi

rantau yang berbeza. Kecilkan skop masalah sebelum memilih analisis petikan atau peruntukan.

  • Adakah produk sengaja mencache apa-apa? Cache laluan terhad atau sejarah carta mungkin kekal

hidup mengikut reka bentuk. Kapasiti, pengusiran (eviction), dan jangkaan keadaan mantap membezakan penggunaan terkawal daripada kebocoran.

  • Adakah komponen tersebut benar-benar dinyahlekapkan? Penghalaan mungkin menyembunyikan, mengekalkan, atau menggunakannya semula. Sahkan kitaran

hayat sebenar dengan log pelekapan (mount) dan pembersihan sebelum membaiki penyahlekapan yang tidak pernah berlaku.

  • Objek manakah yang datang daripada pustaka pihak ketiga? Carta, editor, dan peta sering memiliki DOM, pekerja (workers),

pemerhati (observers), dan pendengar dalaman. Mengosongkan HTML bekas tidak memusnahkan tika tersebut.

  • Bolehkah simptom pengeluaran dihasilkan semula dalam persekitaran ujian? Petikan heboh mungkin mengandungi data

pengguna. Utamakan penghasilan semula terkawal dengan data yang dibersihkan serta gunakan kawalan privasi dan akses.

  • Apakah takrifan kejayaan? Tiada had MB sejagat yang bebas daripada peranti dan beban kerja. Capai persetujuan mengenai

kecerunan garis dasar pasca operasi, bilangan tika langsung, bilangan nod/pendengar, dan kelambatan (stalls) yang kelihatan kepada pengguna.

Rangka Kerja Jawapan 30 Saat

"Saya akan menetapkan tindakan senarai-butiran-senarai, melakukan pemanasan (warm-up), mengulanginya, dan membandingkan garis dasar langsung pada titik pemeriksaan GC yang serupa. Jika paras rendah timbunan, nod DOM, atau pendengar terkumpul, saya membandingkan petikan, mencari jenis yang berkembang, dan menjejaki Retainers ke window, bas peristiwa, atau pemasa. Saya menambah Garis masa peruntukan jika punca tidak jelas. Pencipta kemudiannya melepaskan pendengar, langganan, pemasa, pemerhati, dan carta. Saya menjalankan semula gelung yang sama dan memerlukan bilangan tika menurun, garis dasar stabil, dan tingkah laku kekal betul."

Penyelaman Mendalam Langkah demi Langkah

Langkah 1: Buktikan kebocoran dengan eksperimen berulang

Mulakan dengan garis dasar yang setanding. Gunakan versi Chrome yang sama dengan sambungan yang tidak berkaitan diminimumkan, dan tetapkan tetingkap, akaun ujian, data pesanan, dan laluan. Selepas muat semula, lengkapkan satu pemanasan supaya modul malas (lazy modules), fon, kolam sambungan, dan cache sekali guna dimulakan. Dalam panel Performance, dayakan Memory dan jalankan:

  1. Cetuskan pemungutan sampah sekali dan rekod titik permulaan.
  2. Lengkapkan lima perjalanan senarai-butiran-senarai, menunggu syarat "carta direncanakan" yang sama setiap kali.
  3. Cetuskan pemungutan sampah sekali lagi dan rekod paras rendah timbunan JS, nod DOM, dokumen, dan pendengar.
  4. Ulangi tiga kumpulan dan bandingkan paras rendah kumpulan berbanding puncak arbitrari.

Masa pemungutan adalah milik masa jalanan (runtime). Eksperimen pendek juga dipengaruhi oleh kerja JIT, penyahkodan imej, respons rangkaian, dan DevTools itu sendiri, jadi delta satu pusingan hanya mencipta hipotesis. Kumpulan pertama yang lebih tinggi yang kemudiannya stabil boleh menjadi pemanasan. Jika setiap kumpulan mengekalkan satu OrderChart baharu, satu set nod terpisah (detached nodes), dan dua pendengar mengikut perkadaran dengan bilangan tindakan, buktinya adalah lebih kukuh.

Pastikan tiga fenomena ini diasingkan:

  • Kebocoran: garis dasar langsung pasca-GC terus meningkat selepas operasi yang sama.
  • Kembung memori (Memory bloat): penggunaan keadaan mantap adalah berlebihan tetapi tidak lagi berkembang tanpa batasan dengan bilangan operasi.
  • Pergolakan peruntukan (Allocation churn): timbunan naik dan turun dengan jeda GC yang kerap, dan paras rendah pulih semula; terlalu

banyak peruntukan sementara adalah isunya.

Jejak Memory Pengurus Tugas merangkumi penggunaan proses seperti storan DOM. Nilai langsung dalam JavaScript Memory adalah lebih dekat dengan timbunan JS yang boleh dicapai. Pengukuran ini mengkategorikan penyiasatan, tetapi ia tidak menggantikan bukti rujukan daripada petikan heboh. GC yang dipaksa ialah kawalan diagnostik, bukan pembetulan produk, dan kod pengeluaran tidak boleh menjanjikan pemungutan pada saat tertentu.

Langkah 2: Cari pemilik yang bertanggungjawab dengan petikan dan laluan penahanan

Selepas mengesahkan pertumbuhan, ambil Snapshot A dalam panel Memory. Lakukan gelung navigasi yang ditetapkan, kembali ke senarai, tunggu pembersihan tak segerak (asynchronous cleanup), dan ambil Snapshot B. Pengambilan petikan bermula dengan pemungutan sampah, jadi Perbandingan (Comparison) berguna untuk objek yang kekal boleh dicapai. Isih mengikut delta tika dan saiz tertahan (retained size), mencari pembina perniagaan, penutupan, tatasusunan, dan pepohon DOM terpisah yang berkembang dengan bilangan gelung.

shallow size menerangkan objek itu sendiri. retained size menganggarkan memori yang boleh dilepaskan jika objek itu menjadi tidak boleh dicapai. Panggilan balik pendengar yang kecil mungkin menahan carta, tatasusunan data, dan keseluruhan subpepohon DOM melalui penutupannya, jadi saiz tertahan ialah isyarat pengutamaan yang lebih baik daripada saiz panggilan balik. Ia adalah anggaran penyiasatan, bukan tuntutan langsung memori fizikal eksklusif.

Pilih satu OrderChart atau nod terpisah yang sepatutnya dimusnahkan dan periksa Retainers. Laluan mungkin kelihatan seperti:

text
Window
└─ resize event listener
   └─ callback closure
      └─ chart
         └─ container
            └─ detached HTMLDivElement

Laluan ini menerangkan sebab GC tidak dapat memungut graf tersebut: window global masih memiliki panggilan balik saiz semula tanpa nama, yang penutupannya memiliki carta tersebut. Mengalih keluar DOM daripada dokumen mengubah hubungannya dengan dokumen; ia tidak memecahkan laluan JavaScript daripada punca. Pembetulan terletak pada pemilikan pendengar dan carta, bukan pada penetapan null secara spekulatif pada nod terpisah.

Jika petikan hanya menunjukkan entri generic Object dan Array, gunakan Garis masa peruntukan (Allocation instrumentation on timeline). Mulakan rakaman, lakukan tepat satu tindakan yang bocor, hentikan, dan tumpukan pada peruntukan yang kekal hidup merentasi pemungutan. Jejaki pembina dan timbunan peruntukannya kembali ke kod. Persampelan peruntukan (allocation sampling) mempunyai overhed yang lebih rendah dan berguna untuk mencari fungsi hangat peruntukan, tetapi hasil persampelannya tidak menggantikan petikan apabila tika individu dan penahan menjadi penting.

Sumber penahanan biasa termasuk:

  • objek global atau tatasusunan modul yang menambah tika halaman secara berterusan;
  • pendengar DOM/EventTarget yang tidak pernah dialih keluar, atau dialih keluar dengan identiti fungsi

atau pilihan penangkapan (capture option) yang berbeza;

  • panggilan balik setInterval dan tamat masa rekursif yang menahan keadaan komponen;
  • bas peristiwa, stor, WebSocket, atau observables tanpa penyahlangganan;
  • ResizeObserver, IntersectionObserver, pekerja, dan tika pihak ketiga tanpa pemusnahan;
  • Map, koleksi sejarah, atau cache permintaan tanpa had kapasiti;
  • janji (promises) yang tidak pernah selesai atau kerja beratur yang menahan penutupan untuk masa yang tidak terhad.

Langkah 3: Baiki pemilikan sumber dan jalankan semula ujian penerimaan yang sama

Effect yang dibaiki mengekalkan pemegang pelepasan bagi setiap sumber. AbortController boleh memiliki pendengar DOM, manakala sumber yang selebihnya dilepaskan secara eksplisit:

tsx
useEffect(() => {
  const container = containerRef.current
  if (!container) return

  const controller = new AbortController()
  const chart = createOrderChart(container)
  const observer = new ResizeObserver(() => chart.resize())
  const unsubscribe = orderBus.on("order", (order) => chart.append(order))
  const timerId = window.setInterval(() => chart.refresh(), 5_000)

  observer.observe(container)
  window.addEventListener("visibilitychange", () => chart.syncVisibility(), {
    signal: controller.signal,
  })

  return () => {
    controller.abort()
    observer.disconnect()
    unsubscribe()
    window.clearInterval(timerId)
    chart.destroy()
  }
}, [])

Sahkan kontrak API sebenar. Sesetengah kaedah on mengembalikan fungsi nyahlanggan; yang lain memerlukan pengendali yang sama dibekalkan kepada off. Jika kebergantungan yang berubah boleh mencipta semula sumber, pembersihan mesti melepaskan hanya tika yang dicipta oleh larian Effect tersebut dan tidak boleh menutup penggantinya. Permintaan tak segerak juga memerlukan pembatalan atau semakan generasi. Menghalang penulisan keadaan selepas penyahlekapan menangani satu kesan; kebocoran kekal jika langganan luaran masih memiliki penutupan tersebut.

WeakMap sesuai untuk metadata berkunci objek, tetapi ia tidak menggantikan pembersihan kitaran hayat yang eksplisit. WeakRef sengaja memberikan sedikit jaminan tentang masa pemungutan dan tingkah laku yang boleh diperhatikan, dan ia biasanya merupakan pembaikan yang lemah untuk pendengar, sambungan, atau tika carta. Pembaikan langsung adalah untuk memecahkan rujukan kuat yang tidak diperlukan dan memberikan cache yang disengajakan kapasiti, TTL, atau syarat pengusiran.

Penerimaan menjalankan semula eksperimen pra-pembetulan yang tepat: binaan, data, bilangan navigasi, titik kesediaan, dan kawalan GC yang sama. Syarat lulus termasuk:

  • komponen butiran langsung, tika carta, dan subpepohon terpisah kembali kepada bilangan yang dipersetujui selepas keluar;
  • merentasi beberapa kumpulan, garis dasar pasca-GC turun naik dalam julat yang stabil dan bukannya menjejaki bilangan

navigasi secara anggaran linear;

  • bilangan pendengar, dokumen, dan nod DOM kembali ke julat yang dijangkakan;
  • setiap pelekapan memiliki satu langganan mesej dan satu tika carta, dan setiap penyahlekapan melepaskan setiap satunya sekali;
  • carta masih dikemas kini, saiz semula bekas dan tingkah laku keterlihatan berfungsi, dan kunjungan semula mencipta semula sumber;
  • kelambatan jangka masa panjang, jeda GC, dan isyarat ranap memenuhi belanjawan produk.

Kekalkan petikan sebelum dan selepas, langkah penghasilan semula, identiti binaan, dan laluan penahanan utama untuk semakan. Petikan boleh mengandungi rentetan dan objek perniagaan, jadi simpan dan kongsikannya sebagai bahan penyahpepijatan yang sensitif.

Contoh Jawapan Berkualiti Tinggi

"Keluk tersebut merupakan bukti yang kukuh, tetapi satu kenaikan masih belum mencukupi untuk kesimpulan saya. Saya akan menetapkan data pesanan, mentakrifkan senarai-butiran-senarai sebagai satu operasi, memanaskan sistem, kemudian menjalankan tiga kumpulan sebanyak lima pusingan. Pada keadaan halaman yang serupa sebelum dan selepas setiap kumpulan, saya akan meminta GC dan merekodkan paras rendah timbunan, nod DOM, dokumen, dan pendengar. Jika hanya kumpulan pertama yang meningkat dan kumpulan kemudian stabil, saya memeriksa pemulaan atau cache yang terhad. Jika setiap kumpulan menambah bilangan carta dan pendengar yang sama, saya meneruskan ke petikan heboh."

"Saya mengambil Snapshot A selepas pemanasan, menjalankan gelung, kembali ke senarai, menunggu pembersihan, dan mengambil Snapshot B. Dalam Comparison saya bermula dengan delta tika dan saiz tertahan. Katakan saya menemui 15 tika OrderChart yang sepatutnya dimusnahkan, dengan Window → resize listener → closure → chart → detached container dalam Retainers. Laluan tersebut menerangkan kegagalan: window masih memiliki pendengar tanpa nama, yang penutupannya menahan carta dan DOM. Jika nama pembina adalah umum, saya merakam satu Garis masa peruntukan dan menggunakan timbunan peruntukannya untuk memetakan objek yang kekal wujud daripada navigasi tersebut kembali ke kod."

"Untuk pembaikan, Effect mengekalkan setiap pemegang pelepasan: pendengar DOM menggunakan AbortController, bas peristiwa dinyahlanggan, pemasa dikosongkan, pemerhati diputuskan sambungan, dan carta dimusnahkan. Jika sesebuah pustaka memerlukan off(handler), saya mengekalkan identiti pengendali yang sama. Cache diberikan kapasiti atau pengusiran. Saya menjalankan semula eksperimen yang sama dan lulus hanya apabila carta dan subpepohon terpisah kembali kepada bilangan yang dipersetujui, garis dasar pasca-GC berhenti menjejaki bilangan navigasi, dan kunjungan semula masih melanggan dan merencanakan."

Kesilapan Biasa

  • Mengisytiharkan kebocoran daripada satu puncak → Peruntukan biasa, kerja JIT, dan cache juga menaikkan puncak → **Bandingkan

beberapa garis dasar pasca-GC dan hubung kaitkan bilangan tika dengan bilangan operasi.**

  • Mengosongkan bekas selepas melihat DOM terpisah → Pendengar global atau penutupan mungkin masih menahannya →

Jejaki Retainers ke punca dan lepaskan pemilik sebenar.

  • Menggantikan setiap struktur dengan WeakMap atau WeakRef → Rujukan kuat yang lain masih menjadikan objek boleh dicapai

Alih keluar pendengar, langganan, pemasa, dan rujukan cache yang tidak diingini terlebih dahulu.

  • Hanya menghalang setState selepas penyahlekapan → Sumber luaran mungkin masih menahan panggilan balik dan graf objek

Batalkan kerja dan nyahdaftarkannya.

  • Hanya mengesahkan bahawa halaman kekal boleh diklik → Ketepatan fungsi tidak membuktikan pelepasan → **Ulangi

eksperimen petikan yang sama dan bandingkan tika langsung, garis dasar, dan laluan penahanan.**

Soalan Susulan dan Respons

Susulan 1: Mengapakah rujukan bulat tidak semestinya bocor?

Mark-and-sweep mempersoalkan sama ada punca boleh mencapai objek tersebut. Dua objek boleh merujuk antara satu sama lain dan masih boleh dipungut apabila tiada laluan luaran yang boleh dicapai menunjuk kepada kumpulan tersebut. Jika pendengar window, cache modul, atau pemasa aktif mencapai salah satu daripadanya, seluruh kumpulan kekal boleh dicapai.

Susulan 2: Mengapakah nod DOM yang dialih keluar boleh kekal dalam petikan?

Pengalihan keluar memisahkan nod daripada pepohon dokumen. Pemboleh ubah JavaScript, panggilan balik pendengar, komponen pihak ketiga, atau pemerhati masih boleh merujuknya. Periksa Retainers bagi nod yang terpisah, cari pemilik langsung di sepanjang laluan, dan panggil operasi pelepasan pemilik tersebut.

Susulan 3: Bilakah anda memilih Petikan Heboh, Garis masa peruntukan, atau persampelan?

Petikan membandingkan objek langsung pada titik stabil dan mendedahkan laluan penahanan bagi setiap tika. Garis masa peruntukan menghubungkan tindakan pengguna dengan peruntukan yang kekal hidup selepas itu. Persampelan mencari fungsi yang bertanggungjawab untuk peruntukan berat dengan overhed yang lebih rendah. Jujukan praktikal membuktikan keluk terlebih dahulu, menggunakan petikan untuk objek dan rujukan, kemudian menambah rakaman peruntukan apabila punca masih tidak jelas.

Susulan 4: Bolehkah "memori mesti berkembang kurang daripada 10 MB" menjadi get automatik?

Nilai mutlak yang tetap adalah sensitif kepada peranti, pelayar, binaan, data, dan masa GC. Get yang lebih kukuh menetapkan persekitaran dan tindakan, memanaskan sistem, mengulangi kumpulan, dan menyemak kecerunan garis dasar pasca-GC, bilangan langsung pembina terkawal, dan bilangan DOM/pendengar. Tetapkan ambang daripada taburan sejarah halaman tersebut dan belanjawan produk, dengan toleransi hingar yang jelas.

Susulan 5: Bagaimana jika halaman disembunyikan tetapi tidak pernah dinyahlekapkan?

Jelaskan kontrak produk. Untuk tingkah laku kekal hidup (keep-alive) yang disengajakan, jedakan pemasa, kurangkan persampelan, atau hentikan langganan yang tidak kelihatan dan sambung semula kemudian; masih hadkan cache dan bilangan tika. "Sifar selepas kembali ke senarai" tidak lagi terpakai. Penerimaan harus memerlukan penggunaan yang stabil, terhad dan pelepasan penuh apabila komponen tersebut benar-benar dimusnahkan.

Sumber awam

Soalan berkaitan