要了解正在发生的情况,有必要明确 attribute(内容属性)和 property(IDL 属性)之间的区别。我不会对此进行扩展,因为在 SO 中已经有很好的答案涵盖了该主题:
当您通过输入或通过 JS 更改
input
元素的内容时:
targetNode.value="foo";
浏览器更新
value
属性,但不更新 value
属性(而是反映 defaultValue
属性)。
然后,如果我们查看 MutationObserver 的 spec,我们会发现 attributes 是可以使用的对象成员之一。因此,如果您显式设置
value
属性:
targetNode.setAttribute("value", "foo");
MutationObserver 将通知属性修改。但是规范列表中没有类似 properties 的内容:无法观察到
value
属性。
如果您想检测用户何时更改输入元素的内容,事件是最直接的方法。如果您需要捕获 JS 修改,请使用
setInterval
并将新值与旧值进行比较。
检查这个SO问题以了解不同的替代方案及其局限性。
Shawn的方法并想分享它。不敢相信实际上有解决方案。
在输入框中键入内容以查看默认行为。现在,打开 DevTools 并选择输入元素,然后更改其值,例如$0.value = "hello"
。检查 UI 与 API 的差异。看来 UI 交互不会直接修改
value
属性。如果是的话,它也会记录
"...changed via API..."
。
let inputBox = document.querySelector("#inputBox");
inputBox.addEventListener("input", function () {
console.log("Input value changed via UI. New value: '%s'", this.value);
});
observeElement(inputBox, "value", function (oldValue, newValue) {
console.log("Input value changed via API. Value changed from '%s' to '%s'", oldValue, newValue);
});
function observeElement(element, property, callback, delay = 0) {
let elementPrototype = Object.getPrototypeOf(element);
if (elementPrototype.hasOwnProperty(property)) {
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
Object.defineProperty(element, property, {
get: function() {
return descriptor.get.apply(this, arguments);
},
set: function () {
let oldValue = this[property];
descriptor.set.apply(this, arguments);
let newValue = this[property];
if (typeof callback == "function") {
setTimeout(callback.bind(this, oldValue, newValue), delay);
}
return newValue;
}
});
}
}
<input type="text" id="inputBox" placeholder="Enter something" />
价值属性可以观察到,不要浪费时间。
function changeValue (event, target) {
document.querySelector("#" + target).value = new Date().getTime();
}
function changeContentValue () {
document.querySelector("#content").value = new Date().getTime();
}
Object.defineProperty(document.querySelector("#content"), "value", {
set: function (t) {
alert('#changed content value');
var caller = arguments.callee
? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
: ''
console.log('this =>', this);
console.log('event => ', event || window.event);
console.log('caller => ', caller);
return this.textContent = t;
}
});
<form id="form" name="form" action="test.php" method="post">
<input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
<textarea id="content" name="content" placeholder="content" ></textarea> <br />
<button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>
var registered = [];
var setDetectChangeHandler = function(field) {
if (!registered.includes(field)) {
var superProps = Object.getPrototypeOf(field);
var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
var newProps = {
get: function() {
return superGet.apply(this, arguments);
},
set: function (t) {
var _this = this;
setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
return superSet.apply(this, arguments);
}
};
Object.defineProperty(field, "value", newProps);
registered.push(field);
}
}
var observerList = [];
var observePropertyChangeStarted = false;
function observePropertyChange(input, propertyName, callback) {
observerList.push({
input: input,
propertyName: propertyName,
state: input[propertyName],
callback: callback
});
if (!observePropertyChangeStarted) {
observePropertyChangeStarted = true;
setInterval(_ => {
for (const obj of observerList) {
const state = obj.input[obj.propertyName];
if (state !== obj.state) {
obj.state = state;
obj.callback(obj.input);
}
}
}, 100);
}
}
然后只需监听任何输入属性:
const checkBoxInput = document.querySelector('input[type="checkbox"]');
observePropertyChange(checkBoxInput, 'checked', (target) => {
// myActions();
});
checkBoxInput.checked = false;
const rangeInput = document.querySelector('input[type="range"]');
observePropertyChange(rangeInput , 'value', (target) => {
// myActions();
});
rangeInput.value = 10;
该函数仅调用一次“setInterval”来限制资源的使用。