Chrome/Firefox中while+startsWith触发调用栈溢出问题
嘿,这个问题我太熟了!咱们来一步步拆解并解决它:
问题根源分析
你遇到的「栈溢出」错误主要来自两个关键问题,Chrome和Firefox的调用栈限制更严格,所以最先暴露出来,Safari只是暂时没触发而已:
- 重复绑定事件处理器:每次
custom_event_one、custom_event_two或者页面ready时,你都会调用select_parent_instead_of_current(),而这个函数每次都会给select元素新增一个change事件处理器——事件绑定是会叠加的!次数多了,触发一次change就会执行N遍逻辑,直接把调用栈撑爆。 - 递归触发change事件:当你选中父选项时,手动调用了
.change(),这会再次触发change事件处理器,形成无限递归循环,很快就会达到浏览器的调用栈上限。
修复后的代码方案
我给你重构了代码,从根源解决这两个问题:
<select name="my-select" class="my-class"> <option value="0">All</option> <option value="value-1">Value 1</option> <option value="value-1-child">- Value 1 child</option> <option value="value-2">Value 2</option> <option value="value-2-child-1">- Value 2 child 1</option> <option value="value-2-child-2">- Value 2 child 2</option> <option value="value-3">Value 3</option> </select>
/** * Function to find previous option in select that does NOT have a "-" prefix. * If current option HAS such prefix, it scans previous options until one WITHOUT it is found (the parent) and selects that one. * If current option HAS NO such prefix, it can skip. */ function select_parent_instead_of_current() { // 用命名空间事件,移除旧处理器再绑定新的,避免重复叠加 jQuery('select[name="my-select"]').off('change.parentSelect').on('change.parentSelect', function(){ const $select = jQuery(this); const $selectedOption = $select.find('option:selected'); const selectedText = jQuery.trim($selectedOption.text()); if (selectedText.startsWith('-')) { let $prevOption = $selectedOption.prev(); // 遍历前序选项,同时加长度判断防止越界 while ($prevOption.length > 0 && jQuery.trim($prevOption.text()).startsWith('-')) { $prevOption = $prevOption.prev(); } // 确保找到有效选项,且不是当前已选中的(避免无意义操作) if ($prevOption.length > 0 && !$prevOption.is(':selected')) { // 直接通过value设置选中状态,不触发change事件,杜绝递归 $select.val($prevOption.val()); // 如果你需要触发其他依赖change事件的逻辑,可以用下面的方式(带标记跳过当前逻辑) // $select.trigger('change', {skipParentCheck: true}); } } }); } /** * Fire the function on ready, and other events I require */ jQuery( document ).on( 'custom_event_one custom_event_two ready', function( event, data ) { select_parent_instead_of_current(); });
关键修改点说明
- 避免重复绑定:用
off('change.parentSelect') + on('change.parentSelect')的命名空间事件写法,每次绑定前先移除旧的处理器,不会出现多处理器叠加的问题。 - 杜绝递归触发:改用
$select.val($prevOption.val())设置选中状态,这个方法不会触发change事件,从根源切断了递归循环。 - 优化DOM查询:缓存
$select、$selectedOption等jQuery对象,避免重复查询DOM,提升性能。 - 增加边界判断:遍历前序选项时加
$prevOption.length > 0,防止遍历到最顶部无选项时出现报错。 - 精准选中选项:原代码用
option:contains()存在匹配歧义(比如"Value 2"和"Value 22"会同时匹配),改用value值设置选中,精准度更高。
如果你的业务逻辑确实需要在选中父选项后触发其他依赖change的操作,可以给trigger('change')传递一个标记参数,在事件处理器开头判断并跳过当前的父选项选择逻辑,比如:
jQuery('select[name="my-select"]').off('change.parentSelect').on('change.parentSelect', function(event, params){ // 如果是手动触发的带标记事件,跳过当前逻辑 if (params?.skipParentCheck) return; // 剩下的原有逻辑... });
内容的提问来源于stack exchange,提问作者user3934058
相关产品推荐
相关产品推荐

