JavaScriptの「querySelectorAll」を使って特定の要素を除外したかったので、属性セレクタ(=""
)と:not()を使ったのですが、文法エラーで詰まりました。
初めはquerySelectorAll()内では要素除外の:not()は使えないのかな?とも思ったのですが、notは使えるようで、結局コードの書き方に問題がありました。
解決策を海外のフォーラムで見つけたので、当記事でもその内容を共有します。
原因はquerySelectorAll()と、属性セレクタの両方でダブルクオテーション(")を併用していた事にありました。
この記事の目次
querySelectorAl()内のセレクタでエラーが出る
今回の内容は切り口が難しく、どのように書けばよいのか難しいのですが、自分用の備忘録もかねて記事にしました。
当記事の内容は、JavaScriptでquerySelectorAllを使ったコードの実行時に
Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Document': 'a[href^=#]' is not a valid selector.
というエラーが出たり、Linterの文法チェックで
Expected an identifier and instead saw ''])'.
というエラーが出た時の対処法になる可能性が高いと思います。
結論から書くと、エラー原因はquerySelectorAll内のエスケープ処理にありました。
シングルクオテーション(')とダブルクオテーション(")の使い方でもあります。
JavaScriptでは「'」と「"」のどちらを使ってもコードが動きますが、この仕様のせいで解決するまで時間がかかりました...。では見ていきましょう・w・
原因はquerySelectorAllのエスケープ処理
今回実際にダメだったコードが以下のコードです。
const c=b.querySelectorAll("a[href]:not(a[href*="."])");
一見何も間違っていないように見えますが...
怒られました。エラー文を読むとセレクタの指定部分が間違っていそうな感じですが、大雑把なのでよく分かりません...。
ここで色々検索したところ、以下のページに似た質問がありました。
外部リンク
重要な回答部分を抜粋したのが以下です。
Whenever the value in an attribute selector contains problematic characters (like .,;:+-*/ etc.), you must quote the attribute value. This does not only apply for CSS attribute selectors, but also for the HTML attribute on the HTML element.
【日本語訳】
属性セレクタの値に問題文字(. , ; : + - * /など)が含まれる場合、属性値を引用符で囲む必要があります。これはCSSの属性セレクタだけでなく、HTML要素上のHTML属性にも適用されます。
querySelectorAll("")の中の属性値に同じ符号("")があるとダメみたいです。
つまり、先ほどのコードを
const c=b.querySelectorAll("a[href]:not(a[href*="."])");
ではなく、
const c=b.querySelectorAll("a[href]:not(a[href*='.'])");
という風にすればOKということです。ややこしいので詳しく見ていきましょう。
対処法1.querySelectorAllでクオテーションを別々に使う
JavascriptのquerySelectorAll()は、
document.querySelectorAll('~ほげほげ~')
document.querySelectorAll("~ほげほげ~")
という風にシングルクオテーションでも、ダブルクオテーションでも動きます。
そしてどちらかしかダメ!という決まりもないようです。
で、この中に:not(a[href*='.'])
のような属性セレクタを使う場合、この'や"の符号がquerySelectorAll()
に使っているものと被っていたらダメなのです。
//↓ダメな例 document.querySelectorAll("a[href*=example.com]")[0].href; document.querySelectorAll("a[href]:not(a[href*="."])"); document.querySelectorAll('a[href]:not(a[href*='.'])'); //↓OKな例 document.querySelectorAll("a[href*='example.com']")[0].href; document.querySelectorAll("a[href]:not(a[href*='.'])"); document.querySelectorAll('a[href]:not(a[href*="."])');
querySelectorAllに「'」か「"」のどちらかを使うかによって属性値の指定に使う符号をもう片方のものにしてやらないと上手くいかないんですね。ややこしいねぇ~...。
対処法2.問題文字をバックスラッシュ(\\)でエスケープ
記事冒頭で触れた
Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Document': 'a[href^=#]' is not a valid selector.
というエラーでa[href^=#]
という部分が怒れている場合は、該当の特殊文字(#)をバックスラッシュでエスケープ処理してやることでも解決できます。
//↓ダメな例 document.querySelectorAll('a[href^=#]'); //↓OKな例 document.querySelectorAll('a[href^=\\#]');
エスケープ処理はバックスラッシュ1つなのでは?と思ったのですが、今回のような場合は“文字列リテラル”なので2回エスケープしないとダメでした。
特殊文字のエスケープ
(~前略~)バックスラッシュは JavaScript のエスケープ文字でもあるので、文字列リテラルを入力する場合、それを 2 回エスケープする必要があります (1 回目は JavaScript の文字列のため、2 回目は querySelector() のため)。
ダブルクオテーション(")で囲んで"#"にしてもOKですけどね。
以上をChromeのデベロッパーツールでテストした結果は以下の通りです。
querySelectorAllは属性値のエスケープが必要な場合もあるので注意しましょう。
いやはやquerySelectorAllのセレクタ指定も意外と奥が深いですね!
まとめ
以上、querySelectorAllのセレクタ指定はエスケープ処理が絡んで結構ややこしい!...という話でした。
querySelectorAllに要素除外の:not()を使うと意外と便利です。
example.comを持つリンク“以外”の全リンクを取得するには以下のように書けます。
const links = document.querySelectorAll("a[href]:not(a[href*='example.com'])");
これ、よくある前方一致でも後方一致でもないんですねぇ~
マニアックだけど意外とよく起きそうな内容ではあると思います。
検索にヒットさせるのが難しそうな感じの記事ですがね・w・