You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用preventDefault()后onchange事件未触发及输入值重复问题咨询

关于Input框keydown事件中preventDefault与change事件的疑问

我给input元素绑定了keydown事件,在事件里手动修改输入框的值。这时候必须调用preventDefault(),不然按键输入的内容会在输入框里重复显示。我找到一个可行的解决方案(代码如下),但有两个疑问:

  1. 为什么用preventDefault()会导致onchange事件不触发?
  2. 为什么不用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:31:06