querySelector 找不到特殊 ID?用 CSS.escape 與 getElementById 分流
驗證於
本文目錄
HTML 裡有 id="user:42",document.querySelector('#user:42') 卻拋出 SyntaxError。原因是 querySelector 接受 CSS selector:冒號會進入 selector 的語法解析,不能直接當成 ID 文字。
若只要依 ID 找元素,先用 document.getElementById(id)。需要接上子元素、class 等 CSS 條件時,再寫 document.querySelector('#' + CSS.escape(id) + ' > button'),只跳脫動態 ID 的部分。
HTML id 和 CSS selector 是兩種輸入
MDN querySelector 文件指出,HTML 的 id 值未必是合法 CSS identifier;selector 語法無效時會拋出例外,語法有效但無匹配時則回傳 null。
還有一種容易漏掉的情況:id="item.a" 直接拼成 #item.a,語法本身有效,意思卻是「id 為 item、class 有 a」。程式會正常執行,但找到的是另一個元素。排錯時除了看有沒有例外,也要確認回傳節點的身分。
getElementById直接接受 ID 字串,呼叫時省略 #。單純依唯一 ID 找元素,這個入口比較貼近需求;呼叫時元素也必須已經進入對應文件,大小寫必須一致。
CSS.escape 放在動態值,保留 selector 的結構
以下是完整的瀏覽器檢查頁。保存為 .html 後開啟,畫面預期顯示「檢查通過」;任何一項不符會拋出 Error,應到開發者工具 Console 查看。本文只檢閱範例並檢查 script 語法,尚未執行瀏覽器 DOM 驗收。
<!doctype html><html lang="zh-Hant"><meta charset="utf-8"><title>特殊 ID 檢查</title><div id="item.a"><button>正確目標</button></div><div id="item" class="a">另一個元素</div><div id="user:42">冒號 ID</div><div id="123">數字 ID</div><pre id="result">尚未檢查</pre><script> function check(condition, message) { if (!condition) throw new Error(message); } function byIdSelector(id) { if (typeof id !== 'string' || id.length === 0) { throw new TypeError('需要非空 ID 字串'); } return '#' + CSS.escape(id); }
check(typeof CSS !== 'undefined' && typeof CSS.escape === 'function', '環境需要 CSS.escape'); const exact = document.getElementById('item.a'); check(document.querySelector('#item.a') !== exact, '原始 selector 代表另一個意思'); check(document.querySelector(byIdSelector('item.a')) === exact, '跳脫後應找回原元素'); check(document.querySelector(byIdSelector('item.a') + ' > button').textContent === '正確目標', '子元素條件應保留');
for (const id of ['user:42', '123']) { check(document.querySelector(byIdSelector(id)) === document.getElementById(id), 'ID 查找結果應相同:' + id); } check(document.querySelector(byIdSelector('missing')) === null, '缺席應回傳 null'); document.getElementById('result').textContent = '檢查通過';</script></html>CSS.escape(id) 依 CSSOM 規範處理 identifier 的序列化。把整條 '#' + id + ' > button' 都交進去,會連自己要保留的 CSS 語法一起跳脫,所以應把動態資料與固定結構分開。
範例限制輸入為非空字串,是這個 helper 的應用契約;實際元件若接受缺席 ID,可以在呼叫端先回傳 null,另外驗收該分支。動態插入的節點則要等掛載完成再查找。
encodeURIComponent 和 RegExp.escape 各有用途
URL 編碼處理網址資料,RegExp.escape 處理正則表達式的字面內容,CSS.escape 處理 CSS identifier。三者不能因為名稱都有「跳脫」就互換。若資料最後進入正則表達式,可看 RegExp.escape 的輸入契約;這裡的接收者是 selector parser。
也要留意回傳 null 的後續處理。在呼叫 .focus()、.scrollIntoView() 或讀取屬性前,先決定找不到元素時要提示、略過還是等待掛載;scrollIntoView 的元素查找問題也需要先取得正確節點。
實際排查順序可以很短:記錄原始 ID、確認它已掛載且唯一、用 getElementById 比對,再測跳脫後的 selector。若 ID 單查成功、複合條件失敗,接著檢查節點階層與 class,而不是繼續替 ID 加反斜線。
參考資料: