使用preventDefault()后onchange事件未触发及输入值重复问题咨询
关于Input框keydown事件中preventDefault与change事件的疑问
我给input元素绑定了keydown事件,在事件里手动修改输入框的值。这时候必须调用preventDefault(),不然按键输入的内容会在输入框里重复显示。我找到一个可行的解决方案(代码如下),但有两个疑问:
- 为什么用
preventDefault()会导致onchange事件不触发? - 为什么不用
preventDefault()时输入值会重复?
示例代码
HTML
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script> <input id="test" name="test" class="test">
JavaScript
$('.test').on('change', function(event) { console.log('## run change') }); const symbolDecimal = '.'; const ignorables = [8, 9, 37, 38, 39, 40, 46, 109, 189]; $(".test").on('keydown', function(event) { const oldValue = event.target.value; // 若为字母键且未按住Ctrl,忽略操作 const key = event.key; const $input = $(event.target); // 放行特殊快捷键操作 if (event.ctrlKey || (event.ctrlKey && (key.toLowerCase() === 'v' || key.toLowerCase() === 'a' )) ) { return; } // 输入精度设置 const precisao = 0; // 构建掩码正则 const mask = new RegExp("(\\-)?(\\d+)(\\d{"+precisao+"})$", 'g'); // 获取光标选中范围 const start = $input.prop('selectionStart'); const end = $input.prop('selectionEnd'); // 将输入值拆分为字符数组 let arr = $input.val().split(''); // 根据精度构建掩码替换规则 let masked = '$1$2'; if (precisao > 0) masked = '$1$2.$3'; let value = $input.val(); // 处理退格/删除键 if (event.keyCode === 8 || event.keyCode === 46) { // 退格键且光标在最左端时直接返回 if (event.keyCode === 8 && start === 0 && end === 0) return; // 计算需要删除的字符数量 let diff = start === end ? 1 : end - start; // 确定删除起始位置 let initStart = start === 0 ? 0 : start - 1; if (event.keyCode === 46) initStart = start; // 若要删除的是小数点,额外多删一位 if (arr[initStart] === symbolDecimal) { initStart -= 1; } // 执行删除操作 arr.splice(initStart, diff); value = arr.join(''); // 应用掩码 $input.val(value.replace(mask, masked)); // 重置光标位置 $input[0].setSelectionRange(initStart, initStart); } else if (!isNaN(parseInt(key))) { // 添加数字字符到数组 arr.push(key); value = arr.join(''); value = value.replace(mask, masked); // 应用掩码后更新输入值 $input.val(value); } // 阻止默认行为 event.preventDefault(); // 手动触发change事件的解决方案 if (event.target.valueOnFocus === undefined) { event.target.valueOnFocus = oldValue; $(event.target).on('focus', function(event) { event.target.valueOnFocus = event.target.value; }); $(event.target).on('blur', function(event) { if (event.target.value !== event.target.valueOnFocus) $(event.target).trigger('change'); }); } });
现象说明
- 注释掉标注为
THE SOLUTION的代码段,onchange事件会停止触发; - 注释掉
THE SOLUTION上方的preventDefault(),onchange事件可正常工作,但输入框会出现内容重复的问题; - 测试发现
blur事件可正常触发,但不符合业务需求。
问题解答
1. 为什么preventDefault()会导致onchange事件不触发?
浏览器触发change事件的逻辑是:当输入框的值因用户交互(比如按键、粘贴)发生改变,且输入框失去焦点时触发。但调用preventDefault()会阻止浏览器执行keydown事件的默认行为——也就是把按键对应的字符插入到输入框中。
虽然你手动修改了输入框的值,但浏览器内部会跟踪“用户是否通过默认行为修改了值”。当preventDefault()阻止了默认输入行为后,浏览器认为这次交互没有触发“值的变更操作”,因此不会标记输入框为“已修改”,后续失去焦点时也就不会触发change事件。
2. 为什么不用preventDefault()时输入值会重复?
因为你在keydown事件中手动把按键对应的字符添加到了输入框里,而浏览器的默认行为也会执行同样的操作:把按键字符插入到输入框。相当于同一个字符被添加了两次——一次是你的代码手动添加,一次是浏览器默认行为添加,所以会出现重复。
补充说明
现有解决方案的原理是:通过监听focus事件记录输入框的初始值,在blur时对比当前值和初始值,若有差异则手动触发change事件,以此弥补preventDefault()导致的change事件缺失问题。
内容的提问来源于stack exchange,提问作者Bruno Batista
相关产品推荐
相关产品推荐

