为什么“#.id”在 CSS/jQuery 中是一个错误的选择器,但它在 HTML 锚中有效?

Why is "#.id" a bad selector in CSS/jQuery yet it works in an HTML anchor?

我正在使用 JSDoc。它生成带有句点的 ID,如

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

如果页面的另一部分有

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

效果很好。单击第二个锚点会滚动到第一个。

但是,document.querySelector 和 jQuery 都找不到锚点。

为什么浏览器本身接受这个锚点但 jQuery 和 querySelector 不接受?

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/>

在查询元素之前,您必须使用 \ 转义 .。 替换

document.querySelector('#.someMethodName');

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

另请注意,从技术上讲,HTML 4 所需的 ID 值格式指定如下:

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 (".").

所以.[A-Za-z]是无效的..

因为 HTML5 是一个有效的 id 属性:

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

因为它不是有效的 CSS identifier,为了将它与 querySelector()$() 一起使用,您应该像这样转义它:

#\.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):

注意它不是有效的 HTML4 id 属性

关于 HTML5 ID 命名约定


在 HTML5 中,您可以 name your ID attributes anything you want 几乎没有语法限制:

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


需要什么选择器函数


但是,当使用 Document.querySelector() 等 JavsScript 选择器时,请务必注意 的语法,它如何评估其参数。

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.

当你混淆选择器函数时


所以在这里,我们看到它正在尝试解析 CSS selectors。基本上,该函数将任何以 # 开头的字符串解释为 class 并将任何以 # 开头的字符串解释为 id,所以当你尝试向它传递一个像这样的字符串时:

#.someMethodName

它认为您正在尝试将 id 和 class 全部解析为单个参数,并抛出一个错误,称其为语法错误。


总结


总之,虽然您的 ID 值在技术上是有效的,但使用 .# 会混淆那些 JavaScript 选择器函数,例如 $(selector)document.querySelector(selector)

要解决此问题,您需要通过转义非标识符字符(小号):

#\.someMethodName

Working demo of this in action

HTML5 允许在 ID 属性值中使用句点,浏览器几十年来一直在毫无问题地处理这个问题(这就是为什么 HTML 4 中的限制本身不是由 HTML 但由它所基于的 SGML — 在 HTML5 中放宽了,现在摆脱了 SGML 的遗留包袱)。所以问题不在属性值中。

RFC 3986定义的片段标识符的语法是:

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

其中pchar的字符集包含句点。所以 .someMethodName 是一个有效的片段标识符,这就是 <a href="#.someMethodName"> 起作用的原因。

但是#.someMethodName不是有效的选择器,原因有两个:

  1. ID 选择器由 # 后跟一个 ident 和 an ident in CSS cannot contain a period.
  2. 组成
  3. 句点因此保留给 class 选择器(它同样由一个句点后跟一个标识符组成)。

简而言之,解析器期望在 # 之后有一个 CSS 标识,但由于直接跟在它后面的 . 而找不到,这使得选择器无效。这是令人惊讶的,因为 ID 选择器的表示法实际上是基于片段标识符的 URI 表示法——事实证明它们都以 # 符号开头,而且它们是两者都用于引用由该标识符在文档中唯一标识的元素。期望在 URI 片段中起作用的任何东西在 ID 选择器中也起作用并不是没有道理的——在大多数情况下,它 是正确的。但是因为 CSS 有自己的语法,不一定与 URI 语法相关(因为它们是两个完全不相关的标准 1),你会遇到这样的边缘情况一.

由于句点是片段标识符的一部分,您需要用反斜杠将其转义才能在 ID 选择器中使用它:

#\.someMethodName

不要忘记您需要在 JavaScript 字符串中对反斜杠本身进行转义(例如与 document.querySelector() 和 jQuery 一起使用):

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

1 几年前,W3C Community Group 围绕一项名为 的提案成立(我是其中的成员)使用 CSS 选择器作为片段标识符 ,如您所想,这两种技术以一种有趣的方式结合在一起。然而,这从未成功,唯一已知的实现是一些可能甚至没有被维护的浏览器扩展。

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

因为散列不是 CSS 选择器,它是 # 后跟一个 ID。

浏览器愉快地滚动到该元素,因为它没有使用未更改的散列作为 CSS 选择器。它可能根本不使用 CSS 选择器,而是使用其内部方法来按 ID 查找元素——document.getElementById 也调用该方法,它也不关心点。证明:

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

虽然 CSS 使用 # 来标记 ID 选择器的开头,但这并不意味着每个 # 都是 [=33 的开头=] ID选择器。

如果浏览器确实使用散列作为CSS选择器,它可能会在使用它之前正确转义它。