动态更改innerHTML设置

Change innerHTML set on the fly

本文关键字:设置 innerHTML 动态      更新时间:2023-09-26

我需要使用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集?

我也试过代理的方式,但我不能使它工作。

更多细节:

我正在做一个实验项目,它使用反向代理来生成和添加CSP策略到一个网站,所以:

  • 网站的所有者会意识到这些"覆盖"
  • 我需要处理客户端生成的任何js代码,这些代码可能触发政策
  • 我需要在内容安全策略引擎评估之前修改它!(这是需要这个"不那么好"解决方案的主要问题)

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

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


解决方案:您为Element.prototype.innerHTML重写了浏览器的本机setter,但是您还需要原始setter来实现您的目标。这可以使用Object来完成。getOwnPropertyDescriptor,它是Object.defineProperty的"对应"。

(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>

对于任意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通常用于计算属性或具有副作用的属性。你劫持了一个值并改变了它。你面临一个处理问题——如果有人第二次设置已经被劫持的值,会发生什么?
  • 不要写复杂的代码。这个代码无疑是一个"棘手"的解决方案。

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

someElement.innerHTML = my_function(value);

您也可以定义一个方法(我将使用method over property,因为它从用户那里获取值),如:

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

这样,当开发人员遇到setUpdatedHTML时,它显然是非标准的,他们可以更容易地去寻找劫持元素原型的人。