Why is “#.id” a bad selector in CSS/jQuery yet it

2019-02-16 04:39发布

问题:

I'm using JSDoc. It generates ids with a period as in

<a id=".someMethodName"></a>

If another part of the page has

<a href="#.someMethodName"></a> 

That works perfectly. Clicking the second anchor scrolls to the first.

But, neither document.querySelector nor jQuery will find the anchor.

Why does the browser itself accept this anchor but jQuery and querySelector do not?

test("document.querySelector('#.someMethodName')", function() {
  document.querySelector('#.someMethodName');
});
test("$('#.someMethodName')", function() {
  $('#.someMethodName');
});

function test(msg, fn) {
  try {
    var result = fn();
    log(msg, result);
  } catch(e) {
    log(msg, e);
  }
}

function log() {
  var pre = document.createElement("pre");
  pre.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
  document.body.appendChild(pre);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#.someMethodName">click here to go to anchor and see errors</a>
<pre>
put
some
text
here
so
the
page
is
long
enough
that
when
we
click
the
anchor
the
browser
has
as
a
place
to
scroll
that
is
off
screen
otherwise
we'd
have
no
way
to
see
if
it
worked
or
not
</pre>
<a id=".someMethodName">we should scroll to here</a>
<p>did we make it?</p>
<hr/>

回答1:

HTML5 permits having a period in an ID attribute value, and browsers have handled this without any issues for decades (which is why the restriction in HTML 4 — itself defined not by HTML but by SGML on which it is based — was relaxed in HTML5, now free from the legacy baggage of SGML). So the problem isn't in the attribute value.

The grammar of a fragment identifier as defined by RFC 3986 is:

fragment    = *( pchar / "/" / "?" )

Where the character set of pchar includes the period. So .someMethodName is a valid fragment identifier, which is why <a href="#.someMethodName"> works.

But #.someMethodName is not a valid selector, and the reason is twofold:

  1. An ID selector consists of a # followed by an ident, and an ident in CSS cannot contain a period.
  2. The period is therefore reserved for a class selector (which similarly consists of a period followed by an ident).

In short, the parser is expecting a CSS ident after the # but not finding one because of the . that directly follows it, making the selector invalid. This is surprising because the notation of an ID selector is in fact based on the URI notation for a fragment identifier — as evident in the fact that both of them start with a # sign, as well as the fact that they are both used to reference an element uniquely identified within the document by that identifier. It's not unreasonable to expect anything that works in a URI fragment to also work in an ID selector — and in most cases it is true. But because CSS has its own grammar which doesn't necessarily correlate with the URI grammar (because they're two completely unrelated standards1), you get edge cases such as this one.

As the period is part of the fragment identifier, you will need to escape it with a backslash in order to use it in an ID selector:

#\.someMethodName

Don't forget that you need to escape the backslash itself within a JavaScript string (e.g. for use with document.querySelector() and jQuery):

document.querySelector('#\\.someMethodName')
$('#\\.someMethodName')

1 Several years ago a W3C Community Group was formed (of which I am a member) around a proposal known as Using CSS Selectors as Fragment Identifiers that, as you can imagine, married the two technologies in an interesting way. This never took off, however, and the only known implementations are some browser extensions that probably aren't even being maintained.



回答2:

For HTML5 is a valid id attribute:

There are no other restrictions on what form an ID can take; in particular, IDs can consist of just digits, start with a digit, start with an underscore, consist of just punctuation, etc

Since it isn't a valid CSS identifier, in order to use it with querySelector() or $() you should escape it like this:

#\\.someMethodName

Mozilla Developer Network:

To match ID or selectors that do not follow the CSS syntax (by using a colon or space inappropriately for example), you must escape the character with a back slash. As the backslash is an escape character in JavaScript, if you are entering a literal string, you must escape it twice (once for the JavaScript string, and another time for querySelector):

Be conscious that it isn't a valid HTML4 id attribute



回答3:

You have to escape . with \\ before querying for elements. Replace

document.querySelector('#.someMethodName');

To

document.querySelector('#\\.someMethodName');

Also note that technically, for HTML 4 required ID value format is specified below:

ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").

So .[A-Za-z] is invalid one..



回答4:

Regarding HTML5 ID naming conventions


In HTML5, you can name your ID attributes anything you want with little to no restrictions on syntax:

There are no other restrictions on what form an ID can take; in particular, IDs can consist of just digits, start with a digit, start with an underscore, consist of just punctuation, etc


What selector functions are expecting


However, when using JavsScript selectors such as Document.querySelector(), it's important to note the syntax of how it evaluates its arguments.

Returns the first element within the document (using depth-first pre-order traversal of the document's nodes|by first element in document markup and iterating through sequential nodes by order of amount of child nodes) that matches the specified group of selectors.

element = document.querySelector(selectors);

  • element is an element object.
  • selectors is a string containing one or more CSS selectors separated by commas.


When you confuse the selector functions


So here, we see that it's attempting to parse CSS selectors. Basically, the function interprets any string starting with a # as a class and any string starting with a # as an id, so when you try to pass it a string like this:

#.someMethodName

It thinks you're trying to parse an id and a class all as a single argument, and throws an error calling it a syntax error.


In Conclusion


So in conclusion, while your ID values are technically valid, using . and # will confuse those JavaScript selector functions like $(selector) and document.querySelector(selector) etc.

To remedy this issue, you need to let the function know that you're trying to use . or # as a character instead of an identifier by escaping the non-identifier character(s):

#\\.someMethodName

Working demo of this in action



回答5:

Why does the browser itself accept this anchor but jQuery and querySelector do not?

Because the hash isn't a CSS selector, it's a # followed by an ID.

The browser happily scrolls to that element because it isn't using the hash, unchanged, as a CSS selector. It's probably not using a CSS selector at all, but rather its internal method for looking up elements by ID — the one that is also called by document.getElementById, which also doesn't care about the dot. Proof:

document.getElementById(".someMethodName").style.color = "green";
<div id=".someMethodName">I'm green because I was found by <code>document.getElementById(".someMethodName")</code></div>

While it's true that CSS uses a # to mark the beginning of an ID selector, that doesn't mean every # everywhere is the beginning of a CSS ID selector.

If the browser did use the hash as a CSS selector, it would presumably escape it correctly prior to using it.