querySelector 違法直覺的行為、擬類選擇器 :scope 與 HTML5 style 元素的 scoped 屬性

日本 Google 工程師 Roland Steiner 正在實作[1] HTML5 的 scoped 屬性[2],
他也是實作 <ruby> 的人,雖然日本人對 <ruby> 的行排版還有些怨言,不過這個
以後再談。scoped 屬性在實作中發現了一個設計反直覺的地方,要從
querySelector 講起。

[1] https://bugs.webkit.org/show_bug.cgi?id=49142
[2]
http://www.whatwg.org/specs/web-apps/current-work/multipage/semantics.html#attr-style-scoped

很多先認識 jQuery 才知道 querySelector 的人(比如說我),可能會被對以下
的行為感到震驚[3]:

<body>
  <header>
     <p>Yeah!</p>
  </header>
  <p>Yay!</p>
</body>

var header = document.getElementsByTagName("header")[0];
header.querySelectorAll("body p") // 回傳 [<p>Yeah!</p>] 而不是空陣列

選擇器竟然可以選到 header 外面的東西這件事是很反直覺的,跟 jQuery 的
$("header").find("body p") 不一樣。這曾在三年前由 jQuery 之父 John Resig
在 W3C Web API 工作組的公開郵件群 public-webapi 開砲之後吵得很兇[4],不
過既然所有瀏覽器的 querySelector 都是這樣的,要改掉這個行為已經因為向後
兼容的問題不太可能,所以對於一般 Web 開發者只有兩個選擇:

1) 不要用 querySelector,用 jQuery 等等架構,反正這些架構也會用間接使用
querySelector 增加效率
2) 把這個行為硬記下來,總之就是 element.querySelectorAll 的意思是「先對
文件裡所有元素使用選擇器再過濾到只剩下局部的元素」,而 jQuery.find 是
「只對局部的元素使用選擇器」,局部的元素是指 element 的所有子嗣(不包括
自己)。

[3] http://blog.csdn.net/FuDesign2008/archive/2011/05/26/6446402.aspx
[4] http://lists.w3.org/Archives/Public/public-webapi/2008Apr/thread#msg251

(以下的功能目前都沒有實作)

為了解決這個分歧,Opera 的 Lachlan Hunt 在未完成的 Selector API Level 2
(Level 1 有 querySelector 的定義,而且已經穩定了)用了以下的策略:

1) 定義擬類 :scope[5],(大約)就是選到呼叫的 querySelector 的那個元素
2) 用 :scope 嘗試定義了一個跟 jQuery.find 比較行為相近的
queryScopedSelector(未完成)

[5] http://dev.w3.org/2006/webapi/selectors-api2/#the-scope-pseudo-class

:scope 的意義基本上就跟[3]提到的迂迴戰法 querySelector("[id=__sizzle__]"
+ query) 差不多,只是用 :scope 取代那個怪 id,而
queryScopedSelector(query) 基本上就是 querySelector(":scope" + query)。
Lachy(Lachlan Hunt 的網名)提到 :scope 這個東西還可以這樣用:

   element.querySeletorAll(".a:scope section, .b:scope article");

也就是可以由 element 本身或是其祖先節點的狀態決定選取的內容。


HTML5 引入 style 元素的 scoped 屬性,用於聯合供搞(Web syndication)的時
候供應資料的網站可以提供一個樣式讓含有資料的網站在局部區塊使用這個樣式。
草案目前是將有 scoped 的 style 元素也是定義[6]成「先對文件裡所有元素使用
選擇器再過濾到只剩下局部的元素」,這裡局部的元素是指 style 的父節點的所
有子嗣。所以如果照目前的規範,仍會發生 querySelector 的那種反常現象:

<div class="foo">
    <article>
        <style scoped>
            .foo p { display: none; }
        </style>
        <p>To be or not to be, that is the question.</p>
    </article>
<div>

根據目前規範,<p> 被外部的 .foo 影響所以會被 .foo p 選到而 display:none
不會顯示。

Google 的 Dimitri Glazkov(好像是 Chrome 瀏覽器的其中一個專案經理?)前
天給了提案[7],就在 <style scoped> 裡面的任一個選擇器,如果裡面沒有
:scope 就瀏覽器自動在前面加一個 ":scope"。

總之,目前 scoped 還沒有實作,而且 WebKit 應該會是第一個。規範的情形還沒
確定下來所以大家未來可以多注意一下,Dimitri 的方案被採用的機會應該很大。
另外目前規範定義 scoped 的 style 元素裡面樣式的特異性跟沒 scoped 的樣式
是一樣的。

[6]
http://www.whatwg.org/specs/web-apps/current-work/multipage/semantics.html#attr-style-scoped
[7]
http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-June/thread.html#32100


個人對 :scope 跟 scoped 屬性的一些想法:

1) ":scope" 明顯該是 ":scope " 不過這只是小錯。另外,不太希望 <style> 裡
面出現 ">p { color: black; }" 這種看起來很怪的樣式,目前沒什麼很理性的理
由。如果 :scope 是在解析時間加上去的應該就會排除本來就不能解析的東西。至
於 queryScopedSelector 吃不吃 ">p" 這種東西是另 外一個問題(這就沒意見)。

2) ":scope" 很難懂,Roland 也有被這個擬類別困擾到。我覺得應該用 :this 或
是 :self 之類一看就明白的東西(雖然在上面 scoped 屬性的例子裡 :this 不是
style 元素而是 article 元素)。Lachy 在 Selectors Level 2 把
querySelector 變成可以吃 querySelector("...", [ele1, ele2,...]) 讓
:scope 可以選到 ele1, ele2, ...

我還不清楚為什麼會設計這樣的東西,第一感覺是有點複雜,要做這樣的事的話我
會傾向直接

nodeList.forEach(function(ele) { ele.addClass("foo"); })

再用 .foo。不過這要看使用情節。

自從 CSS 工作組弄完 CSS 2.1 之後終於又開始繼續發展 CSS 了,也是該把
:scope 加進去 Selector Level 4 [8] 吵一吵命名了(最近在吵 :matches vs :any)

3) 最好不要在有 queryScopedSelector 這麼長的名字了,這是 DOM 的傳統嗎?
(getElementById、、、)。querySelctor 也是一個又長又難以理解的名字。用
jQuery 的 find 不好嗎?

[8] http://dev.w3.org/csswg/selectors4/

參考閱讀:
style 元素 scoped 屬性的介紹、使用情節與工具的連結
http://www.impressivewebs.com/scoped-styles-html5/
Lachy 寫的 :scope 的設計備忘錄
http://lists.w3.org/Archives/Public/public-webapi/2008May/0057
另一串吵 :scope 必要性跟有 scoped 屬性的樣式的特異性的大戰
http://lists.w3.org/Archives/Public/www-style/2008Jul/thread#msg193
w3ctech 講 querySector 的文章 http://w3ctech.com/w/index.php?doc-view-
104 瀏覽器沒有違反標準!


此致

呂 康豪(Kenny), 中文興趣小組W3C連絡人
推特: http://twitter.com/kanghaolu
噗浪: http://www.plurk.com/kennyluck
新浪微博: http://t.sina.com.cn/1950042164

Received on Sunday, 19 June 2011 14:55:00 UTC