为什么「确定取消」值得单独说
几乎每个删除、提交、支付动作前,都要弹一次“确定要执行吗?”按钮就是确定和取消。逻辑看似简单,但真写起来,返回值类型、异步顺序、取消后的状态回滚,处处是坑。把 js 确定取消这件事理清楚,能省掉大量线上问题。
原生 confirm 的返回值陷阱
最直接的写法是 window.confirm():
if (confirm(‘确定删除该条记录吗?’)) {
deleteItem();
}
它返回布尔值,点确定是 true,点取消是 false。坑在于三点:
- 它是同步阻塞的,会卡住主线程,弹窗期间页面动画和定时器都会暂停;
- 样式完全由浏览器决定,无法定制文案按钮;
- 部分内嵌 WebView 或某些浏览器环境下会被拦截或直接返回
false。
所以在需要统一视觉或多端一致的项目里,原生 confirm 通常只作为兜底。
自定义弹窗:把确定取消做成 Promise
主流做法是用组件库或自写弹窗,把用户选择包装成 Promise,让调用方用 await 处理,代码线性可读:
function confirmDialog(message) {
return new Promise((resolve) => {
showModal({
message,
onOk: () => { closeModal(); resolve(true); },
onCancel: () => { closeModal(); resolve(false); }
});
});
}
async function handleDelete(id) {
const ok = await confirmDialog(‘确定删除吗?’);
if (!ok) return; // 取消:直接退出,不做任何副作用
await deleteItem(id);
}
关键点是取消分支必须提前返回,不要让删除请求、表单提交等副作用继续执行。很多人写成 if (ok) { ... } 而漏掉 else,后续代码照跑,取消就形同虚设。
几个容易忽略的细节
取消后要回滚 UI 状态
如果确定取消绑定在开关、复选框上,用户点取消时控件已经变了。需要在取消分支里把状态改回去,否则界面和真实数据不一致。
防止重复点击
确定按钮连点会触发两次请求。可以在点击后立即禁用按钮,或加一个 loading 标志位,Promise resolve 前忽略后续点击。
关闭弹窗的多种途径
点遮罩、按 Esc、点右上角叉号,本质上都等于“取消”。这些入口都要走同一个 resolve(false),不要只处理按钮,否则 Promise 永远不落地,造成内存泄漏或后续逻辑挂起。
移动端与可访问性
移动端要考虑键盘弹起遮挡按钮;按钮文案用“确定/取消”而非仅图标;给弹窗加 role="dialog" 和焦点管理,让键盘用户能操作。
小结
js 确定取消的核心不是弹窗长什么样,而是把用户意图准确转成布尔结果,并保证取消路径不产生副作用。原生 confirm 够用但不可控,Promise 封装的自定义弹窗更灵活。写的时候盯住三点:取消提前返回、状态能回滚、重复点击有防护,这块逻辑基本就不会出问题。