我有一个本地测试环境,我想在其中临时覆盖querySelector。我知道猴子补丁是不好的做法,但在这种情况下,此代码将仅在开发人员本地使用。我写了这个片段(覆盖 querySelector 以使用选择器中名为 addonID 的另一个子字符串来获取所有选择器):
function maybeOverrideForTestStart(partialIDOfWidget, fullIDOfWidget) {
if(!isLocal) return;
const addonID = fullIDOfWidget.replace(partialIDOfWidget, "");
Element.prototype.querySelectorTemp = Element.prototype.querySelector.bind(Element);
Element.prototype.querySelector = function(selector) {
const element = this.querySelectorTemp(selector);
if (element) return element;
if (addonID) {
return this.querySelectorTemp(selector + addonID) || null;
}
};
}
function maybeOverrideForTestEnd() {
if(!isLocal) return;
Element.prototype.querySelector = Element.querySelectorTemp;
}
我在测试开始时调用
maybeOverrideForTestStart
,在测试结束时调用 maybeOverrideForTestEnd
。但这不起作用,而且我不确定我错过了什么。我得到的是 someElement.querySelector is not a function
或 "Uncaught TypeError: Illegal invocation"
。
注意 - 我也无法理解这是否也覆盖了 document.querySelector 和 document.body.querySelector 或者只是 someElement.querySelector。
感谢帮助,谢谢。
我会将
maybeOverrideForTestStart
的命名更改为 patchQuerySelectors
,因为它的实现也发生了变化。
为了正确地重新定义/修补修改后的
querySelector
实现(需要为 Document.prototype
和 Element.prototype
执行此操作)并准确恢复每个默认状态,应该选择一种利用每个原型的方法querySelector
的 属性描述符。该函数确实修补每个修改版本,但也返回一个恢复每个原始设置的函数,一切都通过Object.defineProperty
。
// apply the monkey patch.
const restoreDefaults = patchQuerySelectors(true, 'bar_123', 'foo');
console.log(
'after patching ... ', {
restoreDefaults,
elementQuery: Element.prototype.querySelector,
documentQuery: Document.prototype.querySelector,
});
// use the patched/modified versions of `querySelector`
console.log(
"document.querySelector('body') ...",
document.querySelector('body'),
);
console.log(
"document.body.querySelector('script') ...",
document.body.querySelector('script'),
);
// restore each specific `querySelector` to its correct default.
restoreDefaults();
console.log(
'after restoring ... ', {
elementQuery: Element.prototype.querySelector,
documentQuery: Document.prototype.querySelector,
});
// use the restored versions of `querySelector`
console.log(
"document.querySelector('body') ...",
document.querySelector('body'),
);
console.log(
"document.body.querySelector('script') ...",
document.body.querySelector('script'),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// get the monkey patching right.
function patchQuerySelectors(isLocal, partialIDOfWidget, fullIDOfWidget) {
if (!isLocal) return;
const addonID = fullIDOfWidget.replace(partialIDOfWidget, "");
const {
// the original/native implementation.
value: elementQuery,
// the original descriptor of the native implementation.
...elementConfig
} = Object.getOwnPropertyDescriptor(Element.prototype, 'querySelector');
const {
value: documentQuery,
...documentConfig
} = Object.getOwnPropertyDescriptor(Document.prototype, 'querySelector');
// the modified element specific `querySelector` implementation.
function modifiedElementQuery(selector) {
// apply the correct context to the original version.
const element = elementQuery.call(this, selector);
if (element) {
return element;
}
if (addonID) {
// apply the correct context to the original version.
return elementQuery.call(this, selector + addonID);
}
};
// the modified document specific `querySelector` implementation.
function modifiedDocumentQuery(selector) {
const element = documentQuery.call(this, selector);
if (element) {
return element;
}
if (addonID) {
return documentQuery.call(this, selector + addonID);
}
};
// - redefine the properties via the default descriptors
// and the newly assigned modified functions.
Object
.defineProperty(Element.prototype, 'querySelector', {
value: modifiedElementQuery,
...elementConfig
});
Object
.defineProperty(Document.prototype, 'querySelector', {
value: modifiedDocumentQuery,
...documentConfig
});
function restoreDefaults() {
// - redefine/restore the properties via the default descriptors
// and the locally stored original `querySelector` implementations.
Object
.defineProperty(Element.prototype, 'querySelector', {
value: elementQuery,
...elementConfig,
});
Object
.defineProperty(Document.prototype, 'querySelector', {
value: documentQuery,
...documentConfig,
});
}
return restoreDefaults;
}
</script>
我不确定您是否需要
.bind()
该函数,但您可以在常量中保留对原始函数的引用,例如
const querySelector = Element.prototype.querySelector;
function maybeOverrideForTestStart(partialIDOfWidget, fullIDOfWidget) {
// ....
Element.prototype.querySelector = function (selector) {
const element = querySelector(selector);
if (element) return element;
if (addonID) {
return querySelectorTemp(selector + addonID) || null;
}
};
}
function maybeOverrideForTestEnd() {
// ...
Element.prototype.querySelector = querySelector;
}