为什么在CSS/jQuery中"#.id"是一个糟糕的选择器,但在HTML锚点中却可以使用?

27

我正在使用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/>


7
在选择器中,“点”符号具有特定的含义:它表示接下来的词是一个类名。 - nnnnnn
1
'#\\.someMethodName' - Arun P Johny
4
我认为这是JSDoc创建者糟糕的设计决策。 - spender
1
@cst1992:请不要用“有趣”的编辑建议浪费审阅者的时间。SO是我们讨厌有趣的地方 - honk
5个回答

22

HTML5允许在ID属性值中包含句点,并且浏览器在数十年间一直可以处理这种情况(这就是为什么HTML 4的限制实际上是由基于其本身定义的SGML引起的,而在没有SGML旧有负担的HTML5中已经被放宽)。因此,问题不在于属性值。

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

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

字符集包括句点的 pchar。因此,.someMethodName 是一个有效的片段标识符,这就是为什么 <a href="#.someMethodName"> 起作用的原因。

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

  1. ID 选择器由 # 后跟 ident 组成,而 CSS 中的 ident 不能包含句点
  2. 因此,句点被保留用于类选择器(同样由句点后跟 ident 组成)。
简而言之,解析器期望在#后找到CSS标识符,但由于直接跟随它的.,使选择器无效。这令人惊讶,因为ID选择器的符号表示实际上是基于URI片段标识符的符号表示-正如它们都以#开头,并且它们都用于通过该标识符在文档中唯一标识一个元素。希望任何在URI片段中有效的内容也能在ID选择器中有效,这并不是不合理的-在大多数情况下确实是这样的。但是由于CSS具有自己的语法,这与URI语法不一定相关(因为它们是两个完全不相关的标准1),因此您会遇到此类边缘情况。
由于句点是片段标识符的一部分,因此您需要使用反斜杠进行转义,以便在ID选择器中使用它:
#\.someMethodName

请不要忘记在JavaScript字符串中转义反斜杠本身(例如,用于document.querySelector()和jQuery):

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

1几年前,围绕一个名为“使用CSS选择器作为片段标识符”的提案(我是其中的一员),成立了一个W3C社区小组。可以想象,这将两种技术有趣地结合在了一起。然而,这从未起飞,目前已知的实现只有一些浏览器扩展,可能甚至没有得到维护。


15

HTML5中,id属性可以采用任何形式,没有其他限制;特别地,id可以只由数字组成,以数字开头,以下划线开头,也可以只由标点符号组成等。

没有其他限制,id可以采用任何形式;特别地,id可以只由数字组成,以数字开头,以下划线开头,也可以只由标点符号组成等。

但是,由于它不是一个有效的CSS标识符,因此为了在querySelector()$()中使用它,您应该像这样对其进行转义:

#\\.someMethodName

Mozilla 开发者网络:

为了匹配不遵循 CSS 语法(例如使用冒号或空格不当)的 ID 或选择器,必须使用反斜杠进行转义。由于反斜杠是 JavaScript 中的转义字符,如果您要输入一个文字字符串,您必须对其进行两次转义(一次用于 JavaScript 字符串,另一次用于 querySelector):

请注意,这不是一个有效的HTML4 id 属性。


1
@tac 应该向他们报告什么?他们的代码符合HTML 5规范,在所有浏览器中都可以工作,但不是有效的HTML 4吗? - CodesInChaos
2
@CodesInChaos提醒大家,“这不是一个有效的HTML4 id属性”。问题的提出者清楚地表明了这是不直观的,破坏性的行为,使得应用JQ或CSS变得困难。 - cat
5
@tac:这和JSDoc的作者或HTML工作组有什么关系呢?HTML 4中的id属性甚至没有考虑CSS或jQuery的存在——限制来自于SGML中的NAME标记,而这个标记早在这两个东西出现之前就被设计好了。很明显这些ID只是用来作为命名锚点,不应该被用于其他任何目的。并且正如我在回答中提到的(也是唯一一个提到的),.someMethodName 是一个完全合法的URI片段标识符。要求更改ID只是为了让更容易编写选择器是很愚蠢的。 - BoltClock

2
你必须在查询元素之前使用 \\ 转义 .。 替换为:
document.querySelector('#.someMethodName');

To

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

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

ID和NAME标记必须以字母([A-Za-z])开头,后面可以跟任意数量的字母、数字([0-9])、连字符(“-”)、下划线(“_”)、冒号(“:”)和句点(“.”)。

因此,.[A-Za-z]是无效的。

1
这个规范已经被HTML5规范取代了很多年,后者取消了这些限制(因为浏览器从未强制执行它们,人们也乐意忽略它们)。在HTML中,id属性的唯一要求是不能包含任何空格,并且必须是唯一的。 - T.J. Crowder

2
关于HTML5 ID命名约定
在HTML5中,你可以给ID属性任意命名,语法上几乎没有限制:

除此之外,ID的形式没有其他限制;特别地,ID可以只由数字组成、以数字开头、以下划线开头、只由标点符号组成等等。


选择器函数期望什么


然而,当使用像Document.querySelector()这样的JavaScript选择器时,重要的是注意它评估其参数的语法。

返回与指定的选择器组匹配的文档中第一个元素(使用深度优先先序遍历文档节点的方式|通过文档标记中的第一个元素,并按照子节点数量的顺序迭代遍历顺序节点)。

element = document.querySelector(selectors);

  • element 是一个元素对象。
  • selectors 是一个包含一个或多个用逗号分隔的CSS选择器的字符串。

当你混淆选择器函数时
所以在这里,我们看到它试图解析CSS选择器。基本上,该函数将任何以#开头的字符串解释为一个,并将任何以.开头的字符串解释为一个id,因此当您尝试传递这样的字符串时:

#.someMethodName

它认为你试图将一个id和一个class解析为单个参数,并且抛出一个称之为语法错误的错误。

总之


总之,虽然您的ID值在技术上是有效的,但使用 . #会使那些JavaScript选择器函数(如$(selector)document.querySelector(selector)等)感到困惑。
为解决此问题,您需要让函数知道您正在尝试使用 . #作为字符而不是标识符,方法是转义非标识符字符:
#\\.someMethodName

这是一个正在运行中的演示,请点击此处

2
为什么浏览器本身可以接受这个锚点,但jQuery和querySelector不能呢?
因为#后面的内容不是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选择器的开头,但并不意味着每个#都是CSS ID选择器的开头。

如果浏览器确实将哈希用作CSS选择器,那么它应该在使用之前正确地转义它。


在这种情况下,它并不会真正将其用作CSS选择器 :P +1 - BoltClock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接