是否可以通过JavaScript获取评论元素/块的引用?

问题描述 投票:0回答:9

这听起来有点疯狂,但我想知道是否可以获取对注释元素的引用,以便我可以用 JavaScript 动态地将其替换为其他内容。

<html>
<head>
</head>
<body>
<div id="header"></div>
<div id="content"></div>
<!-- sidebar place holder: some id-->
</body>
</html>

在上面的页面中,我可以引用评论块并将其替换为本地存储中的某些内容吗?

我知道我可以有一个 div 占位符。只是想知道它是否适用于评论区。 谢谢。

javascript dom comments
9个回答
41
投票
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);

19
投票

使用注释作为占位符似乎存在合理的(性能)问题 - 首先,没有可以匹配注释节点的 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
不会与任何“真实”物体发生碰撞。


11
投票

有文档节点遍历的API:

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


10
投票

基于 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;
}

3
投票

使用 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


2
投票

如果使用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;})

1
投票

如果您只想从文档或文档的一部分获取所有注释的数组,那么这是我发现在现代 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 中处理,这对于我。

https://jsperf.com/getcomments/6


0
投票

这是一个老问题,但这是我对 DOM“占位符”的两点看法 IMO 评论元素非常适合这项工作(有效的 html,不可见,并且不会以任何方式误导)。 但是,如果您以相反的方式构建代码,则不需要遍历 dom 来查找注释。

我建议使用以下方法:

  1. 用您选择的标记标记您想要“控制”的位置(例如具有特定类的 div 元素)

    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    
  2. 以通常的方式查找占位符(querySelector/classSelector 等)

var placeholders = document.querySelectorAll('placeholder');

  1. 用评论替换它们并保留这些评论的参考:

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 -->
  1. 现在您可以使用构建的 refArray 直接访问每条评论,并做您想做的任何事情......例如:

用标题替换第二条评论

let headline = document.createElement('h1');
headline.innerText = "I am a headline!";
refArray[1].parentNode.replaceChild(headline,refArray[1]);


0
投票

我建议使用

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')
© www.soinside.com 2019 - 2024. All rights reserved.