Is it possible to get reference to comment element

2019-01-07 19:14发布

This sounds a little crazy, but I'm wondering whether possible to get reference to comment element so that I can dynamically replace it other content with JavaScript.

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

In above page, can I get reference to the comment block and replace it with some content in local storage?

I know that I can have a div place holder. Just wondering whether it applies to comment block. Thanks.

6条回答
Explosion°爆炸
2楼-- · 2019-01-07 19:51

This is an old question, but here's my two cents on DOM "placeholders" IMO a comment element is perfect for the job (valid html, not visible, and not misleading in any way). However, traversing the dom looking for comments is not necessary if you build your code the other way around.

I would suggest using the following method:

  1. Mark the places you want to "control" with markup of your choice (e.g a div element with a specific class)

    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    
  2. Find the placeholders the usual way (querySelector/classSelector etc)

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

  1. Replace them with comments and keep reference of those comments:

var refArray = [];

[...placeholders].forEach(function(placeholder){ var comment = document.createComment('this is a placeholder'); refArray.push( placeholder.parentNode.replaceChild(comment, placeholder) ); });

at this stage your rendered markup should look like this:

<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
<!-- this is a placeholder -->
  1. Now you can access each of those comments directly with your built refArray and do whatevere it is you wanna do... for example:

replace the second comment with a headline

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

查看更多
再贱就再见
3楼-- · 2019-01-07 19:55

If you use jQuery, you can do the following to get all comment nodes

comments = $('*').contents().filter(function(){ return this.nodeType===8; })

If you only want the comments nodes of the body, use

comments = $('body').find('*').contents().filter(function(){
     return this.nodeType===8;
 })

If you want the comment strings as an array you can then use map:

comment_strings = comments.map(function(){return this.nodeValue;})
查看更多
女痞
4楼-- · 2019-01-07 20:03

Building off of hyperslug's answer, you can make it go faster by using a stack instead of function recursion. As shown in this jsPerf, function recursion is 42% slower on my Chrome 36 on Windows and 71% with IE11 in IE8 compatibility mode. It appears to run about 20% slower in IE11 in edge mode but faster in all other cases tested.

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

Or as done in TypeScript:

public static getComments(context: any): Comment[] {
    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;
}
查看更多
smile是对你的礼貌
5楼-- · 2019-01-07 20:14

There is an API for document nodes traversal: Document#createNodeIterator():

var nodeIterator = document.createNodeIterator(
    document.body,
    NodeFilter.SHOW_COMMENT,    
    { acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } }
);

// 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 place holder: some_id -->
</body>
</html>


Edit: use a NodeIterator instead of a TreeWalker

查看更多
疯言疯语
6楼-- · 2019-01-07 20:15

It seems there are legitimate (performance) concerns about using comments as placeholders - for one, there's no CSS selector that can match comment nodes, so you won't be able to query them with e.g. document.querySelectorAll(), which makes it both complex and slow to locate comment elements.

My question then was, is there another element I can place inline, that doesn't have any visible side-effects? I've seen some people using the <meta> tag, but I looked into that, and using that in <body> isn't valid markup.

So I settled on the <script> tag.

Use a custom type attribute, so it won't actually get executed as a script, and use data-attributes for any initialization data required by the script that's going to initialize your placeholders.

For example:

<script type="placeholder/foo" data-stuff="whatevs"></script>

Then simply query those tags - e.g.:

document.querySelectorAll('script[type="placeholder/foo"]')

Then replace them as needed - here's a plain DOM example.

Note that placeholder in this example isn't any defined "real" thing - you should replace that with e.g. vendor-name to make sure your type doesn't collide with anything "real".

查看更多
做个烂人
7楼-- · 2019-01-07 20:17
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);
查看更多
登录 后发表回答