如何为单个Input元素追加Getter/Setter逻辑且不覆盖原有修改?
为单个HTMLInputElement的value存取器追加逻辑而非覆盖
问题背景
这是针对Stack Overflow问题《Setter for HTMLInputElement.value》的跟进提问:已修改单个input元素(非全局所有input)的getter和setter,后续希望(如第三方JS模块)为该元素的getter/setter添加新逻辑,而非覆盖原有修改,该如何实现?具体而言,修改getter和setter时,后续代码应如何操作才能保留原有逻辑并添加新逻辑?
编辑1:现有代码基础上追加第二个逻辑函数
在以下代码执行后,若需(或第三方模块需)向id为anInputElement的同一input的setter(或getter)中注入第二个doSomeLogicWithInput2函数,该如何处理?
function doSomeLogicWithInput(input) { if (input.value.toLowerCase()==='one') {input.style.color='black'; } else { input.style.color='red';} } var inputElement = document.getElementById('anInputElement'); Object.defineProperty(inputElement, 'value', { set: function(newValue) { var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); var valuePropResult = valueProp.set.call(inputElement, newValue); doSomeLogicWithInput(inputElement); return valuePropResult; }, get : function() { var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); return valueProp.get.call(inputElement); } }); // Catch end-user changes inputElement.addEventListener('input', function(event) { doSomeLogicWithInput(event.target); }, true);
编辑2:尝试方案的问题反馈
以下是尝试使用Bergi提供的函数的示例,但无法正常运行——logic函数中el.value返回undefined。
<html><head> <script> function getAccessorDescriptor(obj, prop) { if (obj == null) { // only necessary if you may overwrite missing properties return { get(){}, set(){}, enumerable: false, configurable: true }; } const desc = Object.getOwnPropertyDescriptor(obj, prop); if (!desc) { return getAccessorDescriptor(Object.getPrototypeOf(obj), prop); } if ("writable" in desc) { // only necessary if you might overwrite data properties return { get() { return desc.value; }, set(v) { if (desc.writable) desc.value = v; else throw new TypeError() }, enumerable: desc.enumerable, configurable: true, } } return desc; } function addSetLogic(obj, prop, logic) { const oldDescriptor = getAccessorDescriptor(); //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Object.defineProperty(obj, prop, { ...oldDescriptor, set(value) { oldDescriptor.set.call(this, value); // ^^^^^^^^^^^^^^^^^ logic(this, value); } }); } var elem; function runtest() { elem = document.getElementById('mycolor'); addSetLogic(elem, 'value', logic); elem.value='bar'; } function logic(el,newValue) { console.log(el,el.value); // <- el.value is undefined if (el.value==='standard') { el.style.color='black;'} else { el.style.color='red'; } } </script> </head> <body onload="runtest();"> <input id="mycolor" type="text" value="foo"> </body> </html>
解决方案
核心思路
要实现追加逻辑而非覆盖,关键是先获取当前元素上已有的value属性描述符(包括之前自定义的存取器),再基于这个描述符包装新的逻辑,而非直接去原型上获取原始描述符。
修复编辑2中的问题
编辑2的代码存在两个明显错误:
addSetLogic调用getAccessorDescriptor()时未传入obj和prop参数,导致无法获取正确的属性描述符- 执行原有setter后直接读取
el.value,可能因自定义存取器的上下文问题返回undefined,建议直接使用setter传入的newValue参数
修复后的代码如下:
function getAccessorDescriptor(obj, prop) { if (obj == null) { return { get(){}, set(){}, enumerable: false, configurable: true }; } const desc = Object.getOwnPropertyDescriptor(obj, prop); if (!desc) { return getAccessorDescriptor(Object.getPrototypeOf(obj), prop); } if ("writable" in desc) { return { get() { return desc.value; }, set(v) { if (desc.writable) desc.value = v; else throw new TypeError() }, enumerable: desc.enumerable, configurable: true, } } return desc; } function addSetLogic(obj, prop, logic) { // 修复:传入obj和prop参数 const oldDescriptor = getAccessorDescriptor(obj, prop); Object.defineProperty(obj, prop, { ...oldDescriptor, set(value) { // 先执行原有setter逻辑 oldDescriptor.set.call(this, value); // 执行新逻辑,优先使用传入的value避免读取异常 logic(this, value); } }); } var elem; function runtest() { elem = document.getElementById('mycolor'); addSetLogic(elem, 'value', logic); elem.value='bar'; } function logic(el, newValue) { console.log(el, newValue); // 直接使用传入的newValue if (newValue==='standard') { el.style.color='black'; } else { el.style.color='red'; } }
针对编辑1的场景追加逻辑
如果要在编辑1的代码基础上追加doSomeLogicWithInput2,直接使用修复后的addSetLogic函数即可:
function doSomeLogicWithInput2(input) { // 示例:根据value长度设置背景色 input.style.backgroundColor = input.value.length > 3 ? '#f0f0f0' : '#fff'; } // 调用函数追加新逻辑 addSetLogic(inputElement, 'value', doSomeLogicWithInput2);
关键点说明
- 每次追加逻辑时,必须通过
getAccessorDescriptor获取当前元素最新的value属性描述符,确保保留之前的自定义逻辑 - 新逻辑优先使用setter传入的
value参数,避免因自定义存取器的逻辑导致读取el.value异常 - 确保属性描述符的
configurable为true(HTMLInputElement的value属性默认满足此条件),才能重新定义存取器
内容的提问来源于stack exchange,提问作者Bill2022
相关产品推荐
相关产品推荐

