这里没有简单的解决方案。复杂解决方案的确切性质将取决于您的全套要求。
更新概念
您说过,除了改变事物之外,您还将添加和删除元素。因此,您不能纯粹在结构上(例如,通过子索引)将更改的元素与原始元素相关联,因为它们可能会发生变化。
所以我可能会这样处理它:
在页面加载之后,在进行任何修改之前,给每个元素一个唯一的标识符。使用 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 元素的开始标记<html[^>]*>,是那些“足够好”的东西之一。它并不完美,尤其是如果你在带引号的属性值中有一个>,如下所示:<html data-foo="I have a > in me">,这是完全有效的。解决这个问题需要更难的解析,所以我在上面假设你不这样做,因为它相当不寻常。 )
然后您可以通过html.querySelector 和html.querySelectorAll 找到其中的元素,以便将您的id 属性应用于它们。形成相关的选择器会很有趣,可能很多位置的东西。
完成后,取回要发送到服务器的 HTML 如下所示:
var stringToSend = prefix + html.innerHTML + suffix;