即时更改 innerHTML 集

Change innerHTML set on the fly

我需要使用 innerHTML 即时更改每个节点上设置的值。

我找到的最接近的解决方案是:

...   
Object.defineProperty(Element.prototype, 'innerHTML', {
    set: function () {
        // get value (ok)
        var value = arguments[0];
        // change it (ok)
        var new_value = my_function(value);
        // set it (problem)
        this.innerHTML = new_value;              // LOOP
    }
}
...

但显然这是一个无限循环。 有没有办法调用原始的 innerHTML 集?

我也试过 Proxy way 但我无法让它工作。

更多详情:

我正在进行一个实验项目,该项目使用反向代理生成 CSP 策略并将其添加到网站,因此:

没有直接的方法可以用任意 HTML 字符串做到这一点,不。

问题是您使用的是任意 HTML 字符串。当前在元素上设置任意 HTML 的唯一方法是使用 innerHTML。您必须找到一种不同的方法来在元素上设置任意 HTML,例如将 HTML 附加到临时节点并获取其内容:

    // Attempt: build a temporary element, append the HTML to it,
    // then grab the contents
    var div = document.createElement( 'div' );
    div.innerHTML = new_value;
    var elements = div.childNodes;

    for( var i = 0; i < elements.length; i++ ) {
        this.appendChild( elements[ i ] );
    }

然而,这会遇到同样的问题,div.innerHTML = new_value; 将永远递归,因为您正在将唯一的入口点修改为任意 HTML 设置。

我能想到的唯一解决方案是实现一个真正的、完整的 HTML 解析器,它可以接受任意 HTML 字符串并将其转换为 DOM 节点,例如 document.createElement('p') 等,然后您可以使用 appendChild 将其附加到当前元素。然而,这将是一个糟糕的、过度设计的解决方案。

除此之外,您不应该这样做。此代码会毁了某人的一天。它违反了我们在前端开发中认识到的几个原则:

  • 不要修改默认的对象原型。任何碰巧 运行 这段代码,甚至 运行 同一页面上的代码的人(比如第三方跟踪库)将从它们下面拉出地毯。追踪问题所在几乎是不可能的 - 没有人会想到寻找 innerHTML 劫持。
  • Setter 通常用于计算属性或具有副作用的属性。 您正在劫持一个值并更改它。你面临一个清理问题——如果有人第二次设置一个已经被劫持的值会发生什么?
  • 不要编写棘手的代码。这段代码无疑是一个 "tricky" 解决方案。

最干净的解决方案可能就是在任何需要的地方使用 my_function。它是可读的、简短的、简单的普通编程:

someElement.innerHTML = my_function(value);

您也可以定义一个方法(我会在 属性 上做方法,因为它破坏了用户的价值),例如:

Element.prototype.setUpdatedHTML = function(html) {
    this.innerHTML = my_function(html);
}

这样当开发者遇到setUpdatedHTML明显是不规范的,他们可以更容易地去寻找劫持Element原型的人。

强制警告: 在任何生产级代码中,为 Element.prototype 的任何 属性 覆盖 setter 和 getter 肯定不是一个好主意。如果您使用任何依赖 innerHTML 的库来正常工作,或者如果项目中有其他开发人员不知道这些更改,事情可能会变得很奇怪。您还将失去在应用程序的其他部分使用 innerHTML "normally" 的能力。

就是说,由于您没有提供任何关于为什么要这样做的信息,我只是假设您知道这些注意事项并且您仍然想覆盖浏览器自身的功能,也许是出于开发目的。


解决方法:你正在为Element.prototype.innerHTML覆盖浏览器的原生setter,但是你还需要原来的setter来实现你的目标。这可以使用 Object.getOwnPropertyDescriptor 来完成,它类似于 Object.defineProperty.

的 "counterpart"

(function() {
  
  //Store the original "hidden" getter and setter functions from Element.prototype
  //using Object.getOwnPropertyDescriptor
  var originalSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
  
  Object.defineProperty(Element.prototype, 'innerHTML', {
    set: function (value) {
        // change it (ok)
        var new_value = my_function(value);
        
        //Call the original setter
        return originalSet.call(this, new_value);
    }
  });
                        
  function my_function(value) {
    //Do whatever you want here
    return value + ' World!';
  }
                        
})();


//Test
document.getElementById('test').innerHTML = 'Hello';
<div id="test"></div>