【问题标题】:Can we overwrite Javascript DOM object prototype properties?我们可以覆盖 Javascript DOM 对象原型属性吗?
【发布时间】:2016-03-26 09:24:02
【问题描述】:

我一直在寻找一种方法来永久更改 HTMLFormElement Javascript 对象的“提交”行为。假设我的一个 Javascript 将包含以下代码:

HTMLFormElement.prototype.onsubmit = function () {
    this.submit();
    // Make sure that you can only submit once.
    this.onsubmit = function () {
        return false;
    };
    return false;
};

我们的想法是简单地防止在所有形式的文档上重复提交。由于上面的示例不起作用,我无法弄清楚如何执行此操作。

有没有办法在加载文档后不循环遍历所有现有的 HTMLElement 来实现这一点?如果您使用其他脚本创建新的表单元素,这种方式将与某些较旧的浏览器兼容并且还保持该行为。 (请仅使用香草 JS)

【问题讨论】:

  • 只编写不提交表单两次而不是更改原生原型的代码不是更容易吗?
  • 我试过了,甚至多个.submit()或多次点击提交按钮只提交了一次表单。
  • document.addEventListener("submit", function(e){ var el=e.target; if(el.spent) return e.preventDefault(); el.spent=true; })
  • @dandavis 不要在事件监听器中使用return false,这仅适用于事件处理程序。使用e.preventDefault()
  • “与某些旧浏览器兼容”有多旧?如果您想与旧版浏览器兼容,为什么要使用 [ES6] 标签?您是试图阻止用户完成表单提交(通过提交按钮或隐式提交),还是由您完成(通过.submit())?您在表单中使用target 吗?

标签: javascript html dom ecmascript-6 ecmascript-5


【解决方案1】:

我们可以覆盖 Javascript DOM 对象原型属性吗?

尽管 javascript 规范在违反正常的 javascript 行为方面大幅削减了主机对象 Object.defineProperty(HTMLFormElement.prototype, "onsubmit", {get: () => {return false;}}) 确实有效。 “工作”就像在原型上设置一个吸气剂。

但这实际上并不是你想要的。这只是替换了 getter/setter 功能。不能保证浏览器会通过 javascript 继承链来检索当前表单的处理程序。

要么是因为它通过直接查看一些底层原生属性来绕过 javascript,要么是因为它根本不需要查看原型并查看表单本身的实例属性。

如果您不知道目标类实际使用各个属性的方式和时间,那么弄乱原型并不是一个好主意。

另一件事:getter/setter 实际上是在HTMLElement.prototype,而不是HTMLFormElement.prototype。至少在 Firefox 中是这样。在尝试弄乱它之前,您至少应该检查继承链;)

我们的想法是简单地防止在所有形式的文档上重复提交。

您可以简单地在文档上安装一个捕获事件侦听器,而不是弄乱原型。

捕获是因为它发生得更早,并且在不取消default action的情况下不太可能被stopPropagation拦截,这将是这种情况下的表单提交。

document.addEventListener("submit", (e) => {e.preventDefault();}, true);

【讨论】:

  • 提交事件实际上传播到文档?我认为 cmets 中有人声称没有,否则这将是理想的解决方案。
  • 顺便说一句,我在 Chrome 和 Firefox 上都尝试了 defineProperty 技巧,但它的表现似乎不如预期。我不确定是否真的有一种方法可以在不经过所有元素的情况下达到我希望的效果。此外,事件侦听器技巧不适用于所有浏览器。
  • 我说原型的东西不是你想要的,我只是先回答了那部分问题。
【解决方案2】:

这个怎么样?

[].forEach.call(document.getElementsByTagName('form'), function(form) {
  form.onsubmit = function () {
    // Make sure that you can only submit once.
    this.onsubmit = function () {
      return false;
    };
    return true;
  };
});

【讨论】:

  • 其实我正在寻找一种方法来避免这样做 :) 有没有一种方法可以在所有表​​单上预定义“onsubmit”操作而无需逐一进行?
  • 提交事件不会冒泡。 jQuery 必须破解该功能。
  • IE9 是(我认为),IE8,我认为需要 attachEvent。我不知道,我已经很久没有关心了......
  • @adeneo:表格可以是targeted
  • @dandavis - 我想我从来没有在表单中添加过目标属性,就像从来没有?
【解决方案3】:

tl;dr: 不,按照@Louy 说的做,这是正确的。 DOM 被设计为循环遍历,不用担心。


是的,您可以覆盖HTMLElement 属性,HTMLElement 后代也是如此。但特别是对于某些属性,例如事件回调,您不能这样做,因为它们不是普通属性,而是getterssetters。您的代码出现以下错误,这可能是您所说的“不起作用”:

即使您做到了这一点,我也不确定由此产生的行为是否由标准定义。我认为这绝对不会导致浏览器之间的任何兼容。

我的经验还表明,因为特定功能需要它而全局更改某些 API 并不是一个好主意 - 很快您的请求可能会更改,并且您将更改整个 API。

【讨论】:

  • 再次担心的是,如果您创建一个新表单(例如:var newForm= document.createElement('form');),如果您想要这种行为,您将不得不重新应用相同的逻辑成为您网站的标准。这种行为也非常不具侵入性,这就是为什么我在寻找简单的东西。但是感谢您提供的详细信息,我至少明白为什么我不能覆盖这些属性!
  • @NicolasBouvrette 相信我,在发布无法满足您要求的答案之前,我深思熟虑。我喜欢说,在编程中,没有什么是不可能的。不幸的是,这不是 100% 正确的。
  • @NicolasBouvrette,您可以轻松地创建一个工厂函数来创建 form 元素并附加监听器,现在您只需调用该函数而不是 document.createElement('form')
  • @MinusFour 是的,我已经有了类似的想法 - 感谢您的建议!
  • @MinusFour 我也想过提议.createElement ovrriding。但是还有其他创建元素的方法,比如解析 HTML (x.innerHTML = "<form ...";)。所以我得出的结论是,很难做出一个通用的可靠解决方案。
【解决方案4】:

是的,您可以覆盖 JavaScript DOM 对象原型属性。但是,您永远不应该……永远不要这样做!也就是说,除非您正在编写一个应该添加标准行为的polyfill,否则浏览器不支持它。除非您处于非常特殊且相当罕见的情况,否则you're not supposed to ever modify objects you don’t own

对于您的特定用例,您可以通过在除第一次点击之外的每次点击提交按钮上使用event.preventDefault() 轻松实现所需的效果:

var alreadySubmitted = false;

document.getElementById("submitbutton").addEventListener("click", function(event){
    if(!alreadySubmitted) {
        // The first time the button is clicked
        alreadySubmitted = true;
    } else {
        // All subsequent times the button is clicked
        event.preventDefault();
    }
}, false);

【讨论】: