Prompt dan Konteks yang Berlaku
Sebuah dasbor me-render 300 kartu. Selama pengguliran dan pengubahan ukuran, sebuah handler membaca bounding box setiap kartu, mengubah lebar dan posisinya, lalu membaca tingginya. Antarmuka menjadi tersendat-sendat. Rekaman Chrome Performance menunjukkan event Layout berwarna ungu yang berulang dan peringatan forced-reflow di dalam handler tersebut.
Jelaskan pipeline peramban JavaScript → style → layout → paint → composite, buktikan apakah kode ini menyebabkan layout thrashing, refaktor kode tersebut tanpa menggunakan geometri yang usang (stale), dan tentukan rencana verifikasinya. Jumlah kartu dan gejala pada trace adalah asumsi wawancara, bukan observasi dari produk nyata.
Ini adalah pertanyaan diagnosis performa frontend. Keterampilan intinya adalah menghubungkan invalidasi dan pembacaan geometri sinkron dengan bukti trace serta perubahan kode yang aman. Ruang lingkupnya lebih sempit daripada investigasi Core Web Vitals, yang dimulai dari metrik lapangan, dan berbeda dari penelusuran navigasi URL baru, yang mencakup tahap jaringan dan rendering awal.
Yang Dievaluasi oleh Pewawancara
Pertama, dapatkah kandidat membedakan invalidasi dari eksekusi? Penulisan DOM atau gaya dapat menandai gaya atau layout sebagai kotor (dirty) tanpa langsung menghitung ulang geometri. API selanjutnya yang harus mengembalikan geometri saat ini dapat memaksa peramban untuk menerapkan (flush) gaya dan layout yang tertunda secara sinkron.
Kedua, dapatkah kandidat menjelaskan thrashing sebagai pola dependensi dan bukan sekadar daftar “properti yang lambat”? Satu penulisan yang diikuti oleh satu pembacaan yang diperlukan bisa jadi sah. Pola yang merusak adalah penulisan → pembacaan yang bergantung pada layout → penulisan yang berulang di banyak elemen atau frame, sehingga mencegah peramban menggabungkan (coalescing) pekerjaan tersebut.
Ketiga, dapatkah kandidat mendiagnosis sebelum mengoptimalkan? Jawaban yang kuat merekam interaksi yang sebenarnya, memeriksa inisiator dan call stack untuk event Layout, membandingkan waktu yang dihabiskan dalam scripting, style, layout, dan paint, serta mengonfirmasi bahwa handler yang dicurigai berada pada jalur kausal. Batang ungu saja tidak membuktikan bahwa setiap layout dapat dihindari.
Keempat, dapatkah kandidat menjaga kebenaran visual dan fungsional? Memindahkan semua pembacaan ke awal hanya berfungsi jika pengukuran tersebut menggambarkan status yang dibutuhkan oleh algoritme. Jika setiap penulisan sengaja mengubah pengukuran berikutnya, algoritme atau model data harus diubah; sekadar men-cache geometri secara membabi buta akan menghasilkan layout yang cepat tetapi salah.
Terakhir, dapatkah kandidat memilih antara batching, requestAnimationFrame, observer, layout CSS, containment, dan properti yang ramah kompositor berdasarkan dependensi nyata? requestAnimationFrame mengubah waktu eksekusi tetapi tidak membuat pekerjaan berat menjadi gratis, dan will-change bukanlah perbaikan umum untuk masalah layout.
Pertanyaan untuk Diklarifikasi Sebelum Menjawab
- Interaksi mana yang lambat? Scroll, resize, render awal, drag, dan ekspansi satu kali memiliki anggaran dan pilihan penjadwalan yang berbeda. Tolok ukur standarnya adalah scroll dan resize berkelanjutan.
- Operasi baca dan tulis apa saja yang terjadi? Pembacaan geometri mencakup
getBoundingClientRect(),offsetWidth, danoffsetHeight; penulisan dapat mengubah kelas, gaya inline, konten, atau struktur DOM. Urutan yang tepat lebih penting daripada sekadar nama-nama API tersebut. - Apakah ukuran baru satu kartu menentukan pengukuran kartu lainnya? Jika tidak, semua pengukuran biasanya dapat dibaca dari satu status yang stabil. Jika ya, algoritme memiliki dependensi berurutan yang tidak dapat dipertahankan hanya dengan batching sederhana.
- Bisakah CSS menangani layout sepenuhnya? Grid, flexbox, container queries, dan intrinsic sizing dapat menghilangkan pengukuran JavaScript sepenuhnya. Hal tersebut sering kali lebih baik daripada mengoptimalkan loop pengukuran.
- Apakah input dimutasi di tempat lain? Commit framework, gambar, font, widget pihak ketiga, atau observer dapat menginvalidasi layout di antara fase-fase eksekusi. Perbaikannya memerlukan satu pemilik atau protokol penjadwalan yang jelas.
- Apa yang harus tetap identik secara visual? Tentukan posisi kartu, ukuran, perilaku fokus, scroll anchoring, dan respons resize sebelum mengubah implementasi.
- Apa lingkungan targetnya? Reproduksi pada viewport dan kelas perangkat yang terpengaruh. Laptop pengembangan yang cepat dapat menyembunyikan layout berulang yang gagal pada CPU dengan sumber daya terbatas.
Kerangka Jawaban 30 Detik
“Pertama-tama saya akan merekam interaksi scroll atau resize yang sebenarnya dan memilih event Layout yang berulang untuk menemukan inisiator dan call stack-nya. Penulisan gaya menginvalidasi geometri; panggilan getBoundingClientRect() atau offsetHeight berikutnya harus mengembalikan nilai saat ini, sehingga peramban dapat mem-flush gaya dan layout secara sinkron. Mengulangi urutan tersebut untuk 300 kartu adalah layout thrashing. Jika setiap kartu dapat menggunakan status pra-pembaruan yang sama, saya akan membaca semua geometri yang diperlukan terlebih dahulu, menghitungnya di memori, lalu melakukan penulisan bersama-sama pada pembaruan visual berikutnya. Saya lebih memilih layout CSS atau observer jika polling JavaScript tidak diperlukan. Kemudian saya akan memutar ulang interaksi deterministik yang sama dan membandingkan jumlah layout, durasi layout, celah frame, dan kebenaran visual. Saya tidak akan mengklaim bahwa requestAnimationFrame saja dapat memperbaiki pekerjaan yang berulang tersebut.”
Pembahasan Mendalam Langkah demi Langkah
Langkah 1: Bangun model kausal
Sebuah frame dapat mencakup JavaScript, kalkulasi gaya, layout, paint, dan compositing. Layout menghitung geometri kotak. Penulisan yang mengubah geometri menandai sebagian dari informasi tersebut sebagai usang. Peramban sering kali menunda penghitungan ulang sehingga beberapa mutasi dapat ditangani bersamaan.
Pembacaan geometri sinkron mengubah jadwal tersebut. Untuk mengembalikan nilai saat ini yang benar, peramban mungkin perlu menerapkan perubahan gaya yang tertunda dan segera menjalankan layout. Setelah penulisan lainnya, pembacaan berikutnya dapat memaksa layout lain terjadi. Dengan 300 kartu, loop yang berselang-seling dapat membuat banyak penghitungan ulang parsial atau mencakup seluruh dokumen di dalam satu handler.
Aturan praktis yang berguna adalah: baca dari satu status visual yang diketahui, hitung tanpa menyentuh DOM, lalu tulis status berikutnya bersama-sama. Ini adalah aturan dependensi, bukan jaminan bahwa setiap pembacaan atau penulisan selalu berat.
Langkah 2: Buktikan bahwa handler tersebut adalah penyebabnya
Rekam trace Performa pada aksi yang deterministik: data kartu, viewport, jarak scroll atau urutan resize, dan kondisi CPU yang sama. Periksa track Frames dan Main. Pilih event Layout yang berdurasi panjang dan ikuti “initiated by” atau stack-nya hingga ke kode aplikasi. Periksa berapa banyak event layout yang terjadi di dalam satu handler dan berapa banyak waktu yang dihabiskannya.
Tambahkan performance mark sementara di sekitar handler jika trace terlalu padat. Gunakan paint flashing dan batas layer hanya sebagai bukti pendukung: keduanya menunjukkan area yang di-paint ulang dan layer-layer yang ada, sedangkan trace Performa menghubungkan forced layout langsung ke kode. Rekam juga waktu scripting dan paint; menghilangkan layout tidak akan memperbaiki handler yang didominasi oleh komputasi yang tidak terkait atau repaint berskala masif.
Buat pembanding (control). Nonaktifkan hanya loop baca-tulis yang dicurigai sementara data dan interaksi tetap berjalan. Jika event Layout berulang dan frame drops mereda, bukti kausalitasnya semakin kuat. Jika tetap terjadi, periksa commit framework, ukuran gambar, font, dan kode pihak ketiga alih-alih memaksakan diagnosis awal Anda.
Langkah 3: Refaktor pengukuran independen menjadi beberapa fase
Misalkan handler awal menyelingi pembacaan dan penulisan:
function positionCards(container, cards, columns) {
let top = 0;
for (const card of cards) {
const width = container.getBoundingClientRect().width / columns;
card.style.width = `${Math.floor(width)}px`;
const height = card.offsetHeight;
card.style.transform = `translateY(${top}px)`;
top += height;
}
}Di sini, setiap tinggi kartu memang bergantung pada lebar barunya, sehingga men-cache setiap tinggi lama adalah tindakan yang salah. Bagi kode menjadi dua status visual yang stabil alih-alih 300 status yang berselang-seling:
function positionCards(container, cards, columns) {
const width = Math.floor(container.getBoundingClientRect().width / columns);
for (const card of cards) {
card.style.width = `${width}px`;
}
requestAnimationFrame(() => {
const heights = cards.map((card) => card.offsetHeight);
let top = 0;
cards.forEach((card, index) => {
card.style.transform = `translateY(${top}px)`;
top += heights[index];
});
});
}Pembacaan pertama mengamati kontainer sebelum penyesuaian lebar. Semua penulisan lebar dikelompokkan. Pembacaan tinggi pertama mungkin memerlukan satu layout yang diperlukan untuk lebar baru tersebut, sementara pembacaan tinggi sisanya menggunakan kembali geometri pasca-penyesuaian lebar yang stabil; transform kemudian ditulis tanpa pembacaan geometri lain. Callback animation-frame mengoordinasikan fase kedua, tetapi peningkatannya berasal dari pengurangan ratusan dependensi yang berselang-seling menjadi dua status eksplisit, bukan dari nama callback itu sendiri.
Gabungkan (coalesce) notifikasi scroll dan resize sehingga maksimal hanya ada satu pembaruan tertunda per frame. Jika setiap event mengantrekan callback lain, aplikasi hanya memindahkan tumpukan antrean (backlog). Simpan input terbaru, jadwalkan sekali, dan hapus flag pending saat callback berjalan.
Langkah 4: Tangani dependensi berurutan yang sesungguhnya
Batching tidak valid jika penulisan pada kartu A sengaja mengubah geometri yang harus dibaca untuk kartu B. Nyatakan batasan tersebut alih-alih berpura-pura bahwa semua pengukuran bersifat independen. Solusi yang memungkinkan mencakup penurunan setiap posisi dari model kumulatif di memori, membiarkan CSS Grid atau flexbox menangani flow layout, mengukur satu kontainer alih-alih setiap elemen anak, atau mendesain ulang efek sehingga hanya membutuhkan frame terakhir yang sudah di-commit.
Saat ukuran konten berubah secara asinkron, ResizeObserver dapat melaporkan perubahan ukuran tanpa perlu mem-polling setiap event scroll. Callback-nya tetap harus menghindari pembuatan loop feedback resize: lakukan perhitungan dari observasi yang diterima, kelompokkan penulisan, dan jangan mengubah ukuran kotak yang sama yang sedang diamati secara berulang tanpa aturan konvergensi.
CSS containment dapat mengurangi seberapa jauh invalidasi layout atau paint menyebar jika batas komponen benar-benar independen. Ini juga dapat mengubah intrinsic sizing, overflow, dan perilaku containing-block, jadi verifikasi tampilan visual dan aksesibilitasnya. Cakupan invalidasi yang lebih kecil mengurangi beban komputasi; ini tidak membenarkan terjadinya forced layout yang berulang.
Langkah 5: Pilih perubahan visual yang lebih murah hanya jika semantik mengizinkannya
Mengubah properti geometris seperti width atau top biasanya membutuhkan layout, paint, dan composite. Mengubah transform atau opacity terkadang dapat menghindari layout dan paint langsung ke proses compositing. Gunakan jalur tersebut untuk pergerakan visual ketika alur dokumen (document flow) tidak memerlukan geometri baru.
Jangan mengganti perubahan lebar sebenarnya dengan scale transform jika elemen saudara, area klik (hit areas), pemotongan teks (text wrapping), atau geometri aksesibilitas harus mencerminkan ukuran baru tersebut. Demikian pula, promosi layer yang berlebihan menghabiskan memori. Konfirmasikan semantik layout yang diinginkan terlebih dahulu, lalu pilih jalur pipeline paling murah yang tetap benar.
Langkah 6: Verifikasi performa dan kebenaran fungsi secara bersamaan
Putar ulang input yang sama sebelum dan sesudah perubahan. Bandingkan jumlah dan total durasi event Layout per interaksi, peringatan forced-reflow yang terikat pada handler, long frames, waktu scripting, area paint, dan dropped frames. Laporkan konfigurasi trace sehingga pengembang lain dapat mereproduksinya.
Validasi batas kartu, wrapping, urutan fokus, target pointer, posisi scroll, zoom, font dan gambar dinamis, serta beberapa ukuran viewport. Uji lonjakan event yang cepat dan perubahan konten setelah render awal. Trace dengan layout yang lebih sedikit bukanlah keberhasilan jika kartu saling tumpang tindih atau fokus keyboard melompat.
Hindari janji angka yang mutlak. Refresh rate perangkat dan beban kerja berbeda-beda, dan satu angka frame-rate lokal bukanlah jaminan produksi. Kriteria rilisnya adalah pengurangan material pada forced layout berulang untuk beban kerja yang sama, tidak adanya bottleneck dominan baru, dan perilaku visual pengguna yang tetap terjaga pada perangkat representatif.
Contoh Jawaban Berkualitas Tinggi
“Saya akan mereproduksi satu urutan resize tetap dengan 300 kartu yang sama dan merekamnya di panel Performance. Saya akan memilih event Layout yang berulang, mengikuti inisiatornya, dan mengonfirmasi bahwa handler menulis geometri lalu memanggil getBoundingClientRect() atau offsetHeight sebelum peramban dapat menggabungkan perubahan tersebut. Itulah pola kausal yang saya sebut layout thrashing; kategori ungu saja tidak cukup.
Saya kemudian akan memeriksa apakah setiap kartu dapat dihitung dari layout pra-pembaruan. Jika ya, saya akan membaca semua kotak terlebih dahulu, menghitung gaya berikutnya dalam bentuk data biasa, dan melakukan penulisan bersama-sama. Saya akan menggabungkan notifikasi resize dan scroll menjadi satu pembaruan visual yang tertunda. requestAnimationFrame membantu menempatkan pembaruan tersebut, tetapi tidak serta-merta memperbaiki pembacaan dan penulisan yang berselang-seling dengan sendirinya. Jika pengukuran hanya mengompensasi flow normal, saya lebih memilih CSS Grid. Jika ukuran berubah secara independen setelah render, saya akan mempertimbangkan ResizeObserver dan menjaga dari loop feedback.
Jika kartu B benar-benar bergantung pada ukuran kartu A pasca-penulisan, saya tidak akan men-cache nilai lama dan menganggapnya selesai. Saya akan menurunkan posisi dari model kumulatif atau membiarkan layout engine yang menangani dependensi tersebut. Saya akan menggunakan transform hanya untuk pergerakan yang tidak perlu memengaruhi flow dokumen.
Terakhir, saya akan mengulangi rekaman yang sama dan membandingkan jumlah dan durasi layout, stack forced-reflow, celah frame, scripting, dan paint. Saya juga akan memverifikasi batas elemen, wrapping, fokus, scroll anchoring, zoom, dan konten yang dimuat belakangan. Keberhasilan tercapai ketika forced layout berulang hilang atau berkurang drastis tanpa pengukuran yang usang atau timbulnya bottleneck paint baru.”
Kesalahan Umum
- Menganggap setiap event layout sebagai thrashing → layout memang diperlukan setiap kali geometri berubah → tunjukkan flush sinkron berulang yang disebabkan oleh dependensi yang berselang-seling.
- Menambahkan
requestAnimationFramedi sekitar loop awal → pembacaan dan penulisan yang sama masih berselang-seling di dalam satu callback → pisahkan fase baca, hitung, dan tulis terlebih dahulu. - Men-cache setiap pengukuran selamanya → font, konten, zoom, dan perubahan viewport membuat geometri usang → tentukan input invalidasi atau gunakan observer yang sesuai.
- Menggunakan
will-changesebagai perbaikan → layer hint tidak menghilangkan dependensi geometri dan justru menghabiskan sumber daya → perbaiki aliran data, lalu promosikan hanya efek visual yang memang beralasan. - Mengganti width dengan scale transform secara membabi buta → document flow, teks, hit testing, atau kualitas visual bisa menjadi salah → gunakan perubahan yang ramah kompositor hanya jika semantiknya mengizinkan.
- Mengoptimalkan tanpa trace → pekerjaan yang berat mungkin berupa scripting atau paint di bagian lain → rekam interaksi dan telusuri inisiator event ke kode.
- Hanya memeriksa rata-rata FPS → nilai rata-rata menyembunyikan frame yang panjang dan tidak mengidentifikasi penyebabnya → bandingkan jumlah layout, durasi, stack, dan frame drop untuk beban kerja yang sama.
- Mengabaikan kebenaran visual → geometri yang usang dapat membuat trace tampak lebih cepat → uji layout, fokus, scrolling, zoom, dan konten asinkron setelah pembaruan kode.
Pertanyaan Lanjutan dan Jawabannya
Pertanyaan Lanjutan 1: Apakah requestAnimationFrame mencegah forced synchronous layout?
Tidak. API ini menjadwalkan callback sebelum proses paint berikutnya. Jika callback tersebut menyelingi penulisan geometri dan pembacaan yang bergantung pada layout, ia masih dapat memaksakan layout sinkron berulang dan menunda frame. Gunakan untuk mengoordinasikan penulisan yang telah di-batch atau tahapan animasi setelah memperbaiki urutan dependensinya.
Pertanyaan Lanjutan 2: Kapan satu forced layout dapat diterima?
Ketika geometri saat ini benar-benar dibutuhkan dan pekerjaannya terbatas—misalnya, mengukur popover yang baru dibuka satu kali sebelum memposisikannya. Baca nilai-nilai yang diperlukan bersama-sama, hindari mengulangi proses flush di dalam loop, dan verifikasi biayanya pada perangkat representatif. Kata “forced” menggambarkan penjadwalan, bukan otomatis merupakan bug.
Pertanyaan Lanjutan 3: Apakah ResizeObserver akan menghilangkan beban kerja layout?
Tidak. Peramban tetap menjalankan layout untuk mengetahui bahwa ukuran telah berubah. Observer menghilangkan polling manual dan memberikan observasi pada tahapan yang ditentukan, yang dapat meningkatkan aliran data aplikasi. Callback-nya tetap dapat membuat loop feedback jika ia berulang kali menulis ukuran yang memicu observasi baru.
Pertanyaan Lanjutan 4: Bagaimana jika jumlah layout berkurang tetapi animasi tetap lambat?
Bandingkan dengan trace yang baru. Waktu scripting mungkin mendominasi, area paint mungkin besar, decoding gambar mungkin terjadi selama interaksi, atau terlalu banyak composited layer yang menghabiskan memori. Optimalkan bottleneck baru yang terukur tersebut; jangan terus mengurangi layout setelah metrik tersebut tidak lagi menjadi penyebab celah frame.
Pertanyaan Lanjutan 5: Bagaimana cara Anda menguji hal ini di dalam framework komponen?
Tandai commit framework dan measurement effect di dalam trace, lalu tentukan apakah kode aplikasi melakukan pembacaan setelah penulisan DOM oleh framework. Pertahankan pengukuran pada fase lifecycle yang diperlukan untuk menjaga kebenaran data, tetapi buat pengukuran tersebut terbatas dan terpisah per fase. Uji proses mount berulang, pembaruan state, konten yang terlambat, dan artefak mode pengembangan sebelum mengatribusikan penurunan performa produksi ke framework itu sendiri.