这听起来有点疯狂,但我想知道是否可以获取对注释元素的引用,以便我可以用 JavaScript 动态地将其替换为其他内容。
<html>
<head>
</head>
<body>
<div id="header"></div>
<div id="content"></div>
<!-- sidebar place holder: some id-->
</body>
</html>
在上面的页面中,我可以引用评论块并将其替换为本地存储中的某些内容吗?
我知道我可以有一个 div 占位符。只是想知道它是否适用于评论区。 谢谢。
var findComments = function(el) {
var arr = [];
for(var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if(node.nodeType === 8) {
arr.push(node);
} else {
arr.push.apply(arr, findComments(node));
}
}
return arr;
};
var commentNodes = findComments(document);
// whatever you were going to do with the comment...
console.log(commentNodes[0].nodeValue);
使用注释作为占位符似乎存在合理的(性能)问题 - 首先,没有可以匹配注释节点的 CSS 选择器,因此您将无法使用例如
document.querySelectorAll()
,这使得定位评论元素既复杂又缓慢。
我的问题是,是否有另一个元素可以内联放置,并且没有任何明显的副作用?我看到有些人使用
<meta>
标签,但我对此进行了研究,发现在 <body>
中使用它不是有效的标记。
所以我选择了
<script>
标签。
使用自定义
type
属性,因此它实际上不会作为脚本执行,并使用 data-
属性来获取将初始化占位符的脚本所需的任何初始化数据。
例如:
<script type="placeholder/foo" data-stuff="whatevs"></script>
然后只需查询这些标签 - 例如:
document.querySelectorAll('script[type="placeholder/foo"]')
然后根据需要替换它们 - 这是一个简单的 DOM 示例。
请注意,此示例中的
placeholder
不是任何定义的“真实”事物 - 您应该将其替换为例如vendor-name
确保您的 type
不会与任何“真实”物体发生碰撞。
Document#createNodeIterator()
:
var nodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_COMMENT
);
// Replace all comment nodes with a div
while(nodeIterator.nextNode()){
var commentNode = nodeIterator.referenceNode;
var id = (commentNode.textContent.split(":")[1] || "").trim();
var div = document.createElement("div");
div.id = id;
commentNode.parentNode.replaceChild(div, commentNode);
}
#header,
#content,
#some_id{
margin: 1em 0;
padding: 0.2em;
border: 2px grey solid;
}
#header::after,
#content::after,
#some_id::after{
content: "DIV with ID=" attr(id);
}
<html>
<head>
</head>
<body>
<div id="header"></div>
<div id="content"></div>
<!-- sidebar placeholder: some_id -->
</body>
</html>
编辑:使用 NodeIterator 而不是 TreeWalker
基于 hyperslug 的答案,您可以通过使用堆栈而不是函数递归来使其运行得更快。如本 jsPerf 所示,函数递归在 Windows 上的 Chrome 36 上慢了 42%,在 IE8 兼容模式下的 IE11 上慢了 71%。在 IE11 的边缘模式下,它的运行速度似乎慢了约 20%,但在所有其他测试情况下运行速度更快。
function getComments(context) {
var foundComments = [];
var elementPath = [context];
while (elementPath.length > 0) {
var el = elementPath.pop();
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === Node.COMMENT_NODE) {
foundComments.push(node);
} else {
elementPath.push(node);
}
}
}
return foundComments;
}
或者像 TypeScript 中所做的那样:
public static getComments(context: any): Comment[] {
const foundComments = [];
const elementPath = [context];
while (elementPath.length > 0) {
const el = elementPath.pop();
for (let i = 0; i < el.childNodes.length; i++) {
const node = el.childNodes[i];
if (node.nodeType === Node.COMMENT_NODE) {
foundComments.push(node);
} else {
elementPath.push(node);
}
}
}
return foundComments;
}
使用 document.evaluate 和 xPath:
function getAllComments(node) {
const xPath = "//comment()",
result = [];
let query = document.evaluate(xPath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0, length = query.snapshotLength; i < length; ++i) {
result.push(query.snapshotItem(i));
}
return result;
}
getAllComments(document.documentElement);
根据我的测试,使用 xPath 比 treeWalker 更快: https://jsben.ch/Feagf
如果使用jQuery,可以执行以下操作来获取所有评论节点
comments = $('*').contents().filter(function(){ return this.nodeType===8; })
如果您只想要正文的注释节点,请使用
comments = $('body').find('*').contents().filter(function(){
return this.nodeType===8;
})
如果您希望将注释字符串作为数组,则可以使用
map
:
comment_strings = comments.map(function(){return this.nodeValue;})
如果您只想从文档或文档的一部分获取所有注释的数组,那么这是我发现在现代 JavaScript 中实现此目的的最有效方法。
function getComments (root) {
var treeWalker = document.createTreeWalker(
root,
NodeFilter.SHOW_COMMENT,
{
"acceptNode": function acceptNode (node) {
return NodeFilter.FILTER_ACCEPT;
}
}
);
// skip the first node which is the node specified in the `root`
var currentNode = treeWalker.nextNode();
var nodeList = [];
while (currentNode) {
nodeList.push(currentNode);
currentNode = treeWalker.nextNode();
}
return nodeList;
}
我在 Chrome 80 中每秒执行超过 50,000 次操作,而堆栈和递归方法在 Chrome 80 中每秒执行的操作均少于 5,000 次。我有数以万计的复杂文档需要在 Node.js 中处理,这对于我。
这是一个老问题,但这是我对 DOM“占位符”的两点看法 IMO 评论元素非常适合这项工作(有效的 html,不可见,并且不会以任何方式误导)。 但是,如果您以相反的方式构建代码,则不需要遍历 dom 来查找注释。
我建议使用以下方法:
用您选择的标记标记您想要“控制”的位置(例如具有特定类的 div 元素)
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
以通常的方式查找占位符(querySelector/classSelector 等)
var placeholders = document.querySelectorAll('placeholder');
var refArray = [];
[...placeholders].forEach(function(placeholder){
var comment = document.createComment('this is a placeholder');
refArray.push( placeholder.parentNode.replaceChild(comment, placeholder) );
});
在此阶段,您渲染的标记应如下所示:
<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
用标题替换第二条评论
let headline = document.createElement('h1');
headline.innerText = "I am a headline!";
refArray[1].parentNode.replaceChild(headline,refArray[1]);
我建议使用
createTreeWalker
和内部的自定义过滤器:
function findCommentNode(commentText) {
return document
.createTreeWalker(
document.querySelector('html'),
128, /* NodeFilter.SHOW_COMMENT */
{
acceptNode: (node) => node.textContent.trim() === commentText.trim()
? 1 /* NodeFilter.FILTER_ACCEPT */
: 2 /* NodeFilter.FILTER_REJECT */
}
)
.nextNode();
}
然后:
const node = findCommentNode('some comment')