JavaScript事件死锁求助:绑定同一点击事件致浏览器崩溃
问题分析与解决办法
哥们,你这个问题我之前帮人排查过,核心就是JavaScript单线程事件循环的阻塞搞的鬼!
为啥浏览器会崩溃?
当你点击按钮后,两个点击事件的处理函数会排队执行:先跑uploadFile,里面的$("#menu2").show()虽然触发了DOM更新,但浏览器的渲染任务是要等当前主线程的代码跑完才会执行的。紧接着执行showProgress里的while(!$("#menu2").is(":visible")) {}——这个死循环会死死占住主线程,完全不给浏览器任何机会去完成menu2的渲染,也没法处理其他任何任务(比如用户点击、页面刷新),时间一长浏览器就直接卡死崩溃了。
给你几个靠谱的解决方案:
方案1:把逻辑串起来(最推荐,代码最简洁)
既然showProgress本来就要等uploadFile的服务器响应完成、menu2显示后再执行,那根本不需要绑定两个独立的点击事件,直接在uploadFile的回调里调用showProgress就行:
button.addEventListener('click', function() { // 点击后先执行uploadFile,完成后再触发showProgress uploadFile(function() { showProgress(); }); }); // 给uploadFile加个回调参数,用来通知后续逻辑 function uploadFile(callback) { // 这里是你的服务器请求逻辑,比如AJAX/fetch fetch('/your-upload-url') .then(res => res.json()) .then(data => { $("#menu2").show(); // 服务器响应完成,触发回调 if (callback) callback(); }); } // 删掉原来的死循环,直接写进度逻辑 function showProgress() { // 这里放你原来循环里的代码,比如更新进度条之类的 console.log('开始显示上传进度啦'); }
方案2:用MutationObserver监听DOM变化(适合必须保留两个事件的场景)
如果因为某些限制必须保留两个独立的点击事件,那可以用浏览器提供的MutationObserver来监听menu2的可见性变化,代替死循环:
button.addEventListener('click', uploadFile); button.addEventListener('click', function() { const menuElement = $("#menu2")[0]; // 创建一个观察者,监听元素的样式变化 const observer = new MutationObserver((mutations) => { if ($("#menu2").is(":visible")) { observer.disconnect(); // 监听完成,停止观察 showProgress(); // 执行进度逻辑 } }); // 监听元素的style属性变化(show()会修改display,触发这个监听) observer.observe(menuElement, { attributes: true, attributeFilter: ['style'] }); }); function uploadFile() { fetch('/your-upload-url') .then(res => res.json()) .then(data => { $("#menu2").show(); }); } function showProgress() { // 你的进度处理代码 console.log('进度开始更新'); }
方案3:用Promise+async/await(现代JS写法,可读性更高)
如果把uploadFile改造成返回Promise,用async/await写起来会更清爽:
button.addEventListener('click', async function() { await uploadFile(); // 等待上传完成、menu2显示 showProgress(); // 之后执行进度逻辑 }); // 把uploadFile改成返回Promise的函数 function uploadFile() { return new Promise((resolve) => { fetch('/your-upload-url') .then(res => res.json()) .then(data => { $("#menu2").show(); resolve(); // 标记Promise完成 }); }); } function showProgress() { // 进度处理代码 console.log('显示上传进度'); }
最后提个醒:
永远不要用同步死循环去等异步操作或者DOM更新!JavaScript是单线程的,主线程被阻塞的话,浏览器啥都干不了,只能崩溃。所有需要等待的逻辑,都得用回调、Promise、观察者这类异步机制来实现,给事件循环留活路~
内容的提问来源于stack exchange,提问作者JinLing
相关产品推荐
相关产品推荐

