受javascript影响后如何恢复到原来的DOM

Posted

技术标签:

【中文标题】受javascript影响后如何恢复到原来的DOM【英文标题】:How can I get back to the original DOM after being affected by javascript 【发布时间】:2014-08-31 12:23:44 【问题描述】:

想象一下,我有一个加载的 html 页面,它已经受到 javascript 在初始化时添加/删除动态元素或新的类/属性/id 到元素的影响(例如:原始源代码 [html] 标记在 javascript 加载后没有类[html] 标签有 class="no-responsive full-with")。想象一下,之后我手动添加/修改一些 id 值(通过我的应用程序)。想象一下,我需要能够将原始源代码(没有任何修改)保存在数据库中,但需要手动添加 id 属性。

基本上,我需要在通过 php 加载的 HTML 源代码中的元素中添加给定的 id 属性。

你们知道如何做这样的事情吗?

【问题讨论】:

***.com/questions/4397577/… @pawel——我怀疑这将在一个重要的页面中工作,除非它被设计成这样处理。 您可以从 location.href ajax 原始 html,为您更改的元素构建一个 xpath,在您 ajax 的 domdocument 中找到该 xpath,old.parentNode.insertBefore(new,old);old 。消除();将dom序列化为字符串,保存。谷歌获取 getXpath(elm) 函数... @dandavis xpath 也可以与片段一起使用吗?我正在使用 jQuery。 @human:我不认为片段具有文档所具有的所有 dom 方法,因此使用依赖于 getElementsByTagName() 之类的现有代码的现有代码可能很难将 xpath 应用于片段可用。 【参考方案1】:

这里没有简单的解决方案。复杂解决方案的确切性质将取决于您的全套要求。

更新概念

您说过,除了改变事物之外,您还将添加和删除元素。因此,您不能纯粹在结构上(例如,通过子索引)将更改的元素与原始元素相关联,因为它们可能会发生变化。

所以我可能会这样处理它:

在页面加载之后,在进行任何修改之前,给每个元素一个唯一的标识符。使用 jQuery 真的很容易(没有它也不是特别难):

var uniqueId = 0;
$("*").attr("data-uid", function() 
    return ++uniqueId;
);

现在页面上的每个元素都有一个唯一的标识符。接下来,复制 DOM 并为其获取一个 jQuery 包装器:

var clone = $("html").clone();

现在您有了一种可靠的方法,可以通过唯一 ID 将 DOM 中的元素与其原始版本(我们的克隆)相关联。允许用户进行更改。

当您准备好了解所做的更改时,您可以这样做:

// Look for changes
clone.find("*").addBack().each(function() 
    // Get this clone's unique identifier
    var uid = $(this).attr("data-uid");

    // Get the real element corresponding to it, if it's
    // still there
    var elm = $("[data-uid=" + uid + "]")[0];

    // Look for changes
    if (!elm) 
        // This element was removed
    
    else 
        if (elm.id !== this.id) 
            // This element's id changed
        
        if (elm.className !== this.className) 
            // This element's className changed
        
        // ...and so on...
    
);

这将告诉您有关已删除和更改的元素。如果您还想查找添加的元素,只需执行以下操作:

var added = $(":not([data-uid])");

...因为他们没有这个属性。

您可以使用clone中的信息来重构原始DOM的字符串:

clone.find("[data-uid]").addBack().removeAttr("data-uid");
var stringToSend = clone[0].outerHTML;

outerHTML 受到任何现代浏览器的支持,最新添加它的是 v11 中的 Firefox。)

...当然还有上面记录变化的信息。

Live proof of concept

HTML:

<p class="content">Some content</p>
<p class="content">Some further content</p>
<p>Final content</p>
<input type="button" id="makeChange" value="Make Change">
<input type="button" id="seeResults" value="See Results">

JavaScript:

// Probably unnecessary, but I wanted a scoping
// function anyway, so we'll give the parser time
// to completely finish up.
setTimeout(function() 
    // Assign unique identifer to every element
    var uniqueId = 0;
    $("*").attr("data-uid", function() 
        return ++uniqueId;
    );

    // Clone the whole thing, get a jQuery object for it
    var clone = $("html").clone();

    // Allow changes
    $("#makeChange").click(function() 
        this.disabled = true;
        $("p:eq(1)").attr("id", "p1");
        $("p:eq(2)").addClass("foo");
        alert("Change made, set an id on one element and added a class to another");
    );

    // See results
    $("#seeResults").click(function() 
        this.disabled = true;

        // Look for changes
        clone.find("*").addBack().each(function() 
            // Get this clone's unique identifier
            var uid = $(this).attr("data-uid");

            // Get the real element corresponding to it, if it's
            // still there
            var elm = $("[data-uid=" + uid + "]")[0];

            // Look for changes
            if (!elm) 
                display("Element with uid " + uid + ": Was removed");
            
            else 
                if (elm.id !== this.id) 
                    display("Element with uid " + uid + ": <code>id</code> changed, now '" + elm.id + "', was '" + this.id + "'");
                
                if (elm.className !== this.className) 
                    display("Element with uid " + uid + ": <code>className</code> changed, now '" + elm.className + "', was '" + this.className + "'");
                
            
        );
    );

    function display(msg) 
        $("<p>").html(String(msg)).appendTo(document.body);
    
, 0);

较早的答案

假设服务器每次请求页面时都为您提供相同的文本,您可以通过 ajax 获取未更改的文本客户端。这给我们留下了如何将id 属性应用于它的问题。

如果您需要原始内容,但必须是相同的来源(例如,如果标签名称改变大小写 [div 可能变为 DIV] 或属性在它们周围获得/失去引号,那也没关系),您可以使用来自服务器的源(通过 ajax 检索)来填充文档片段,并在将 id 值应用于该片段的同时将它们应用于主文档。然后将片段的来源发送到服务器。

使用来自服务器的完整 HTML 填充片段并不像应有的那么容易。假设html 上面没有任何类或任何东西,那么:

var frag, html, prefix, suffix;
frag = document.createDocumentFragment();
html = document.createElement("html");
frag.appendChild(html);
prefix = stringFromServer..match(/(^.*<html[^>]*>)/);
prefix = prefix ? prefix[1] : "<!doctype html><html>";
suffix = stringFromServer.match(/(<\/html>\s*$)/);
suffix = suffix ? suffix[1] : "</html>";
html.innerHTML = stringFromServer.replace(/^.*<html[^>]*>/, '').replace(/<\/html>\s*$/, '');

在那里,我们获取服务器的字符串,获取最外层的 HTML 部分(或使用默认值),然后将内部 HTML 分配给片段内的 html 元素(尽管我想得越多,我看到的就越少完全需要一个片段——你可能可以只删除片段部分)。 (旁注:上面的正则表达式部分标识html 元素的开始标记&lt;html[^&gt;]*&gt;,是那些“足够好”的东西之一。它并不完美,尤其是如果你在带引号的属性值中有一个&gt;,如下所示:&lt;html data-foo="I have a &gt; in me"&gt;,这是完全有效的。解决这个问题需要更难的解析,所以我在上面假设你不这样做,因为它相当不寻常。 )

然后您可以通过html.querySelectorhtml.querySelectorAll 找到其中的元素,以便将您的id 属性应用于它们。形成相关的选择器会很有趣,可能很多位置的东西。

完成后,取回要发送到服务器的 HTML 如下所示:

var stringToSend = prefix + html.innerHTML + suffix;

【讨论】:

这解决了部分问题,这很好,创建一个动态对象并存储原始源代码,但我不能假设 html 标签不会有任何类,最棘手的部分是找到如果 ids 和 class 可能略有不同,则原始源代码中的元素。 @dandavis 评论了一些关于 xpath 的评论,这可能是它的一种解决方案。 @human:是的,xpath 可能是一个选项。关于在&lt;html&gt; 上上课,上面处理了这个问题(使用prefix)。如果文档的 结构 不会改变(您只是添加 ids 和更改类),您总是可以通过找出从根开始的路径来访问元素:例如,“好的,用户正在将id="foo" 添加到body 元素的第一个孩子的第17 个孩子的第二个孩子的第三个孩子。”这些索引很容易确定,并输入到:nth-child 选择器querySelector。添加或删除元素的情况是否必须处理? 在某些情况下,由于用户将提供自己的 html 代码,因此可能会添加或删除元素。 @human:我想我想出了一些东西给你,看看更新。 完美运行。谢谢@T.J.Crowder

以上是关于受javascript影响后如何恢复到原来的DOM的主要内容,如果未能解决你的问题,请参考以下文章

git reset 版本回退的三种用法总结

js 和 css 是如何影响DOM树构建的?

外部 Javascript 文件如何影响 HTML 文件的 DOM?

浏览器原理 21 # DOM树:JavaScript是如何影响DOM树构建的?

JavaScript DOM循环引用问题的精确解释

iFrame javascript 影响父 DOM