かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript jQueryを使わずに要素選択するやつのメモ。

雰囲気で使ってたから、ちょうどいい機会だしと思い簡単に調べてみたメモ

document.getElementById

  • id名で要素を選択して取得する
  • idは#idNameではなく、idName
  • Elementを返す
  • idはHTML上ではユニーク(な筈)仮にidが重複して存在している場合は最初にマッチした要素が返る
  • 存在しないidで取得できない場合はnullが返る
  • documentオブジェクトのみで利用可能
  • プリミティブだからたぶん高速

HTML

<div id="app">
  <h1 id="title"></h1>
</div>
<div id="main" class="main1"></div>
<div id="main" class="main2"></div>

JavaScript

const app = document.getElementById('app');
// => <div id="app"></div>
document.getElementById('main');
// => <div id="main" class="main1"></div>
document.getElementById('nowhere_guy');
// => null
app.getElementById('title');
// => Uncaught TypeError

getElementsByClassName

  • クラス名で要素を選択し取得する
  • クラス名の指定に.不要
  • HTMLCollectionを返す
    • .lengthで要素数を返す
    • 配列のようにインデックスを指定すると該当要素にアクセスできる。e.g. document.getElementsByClassName(names)[0]なら見つかった要素の1つめ
    • 素数外のインデックスを指定するとundefined (エラーにはならない)
  • 該当する要素が存在しない場合はHTMLCollection []を返す。
    • length0
    • インデックスで要素にアクセスするとundefined
  • "A B"のようにスペース区切りで複数のクラス名を渡した場合は、AかつBのクラスを持つものを取得
  • document以外でも使用できる。
    rootElem.getElementsByClassName(names)親要素rootElemの子要素から該当する要素を探す
  • 配列のようにインデックスで要素にアクセスできるけどforEachなどのメソッドを持っていないので、ループで回す場合はforを使うか、Array.fromで配列に変換してループで回す

HTML

<div id="app">
  <h1 id="title">Get Element</h1>
  <p class="text red">text1</p>
  <p class="text blue">text2</p>
</div>
<p class="small text red">text3</p>

JavaScript

const texts = document.geteElementsByClassName('text');
// => HTMLCollection(3) [p.text.red, p.text.blue, p.small.text.red]
console.log( texts.length );
// => 3
console.log( texts[0] );
// => <p class="text red">text1</p>
console.log(texts[100]);
// => undefined

const notFoundClass = document.getElementsByClassName('notfound');
console.log( notFoundClass );
// => HTMLCollection []
console.log( notFoundClass.length );
// => 0
console.log( notFoundClass[0] );
// => undefined

const app = documents.getElementById('app');
app.getElementsByClassName('text');
// => HTMLCollection(2) [p.text.red, p.text.blue]

const red_text = document.getElementsByClassName('text red');
// => HTMLCollection(2) [p.text.red, p.small.text.red]

red_text.forEach((elm) => console.log(elm));
// => getelement.html:42 Uncaught TypeError

for(let i=0, l=red_text.length; i<l; i+=1) {
  console.log( red_text[i].textContent );
}
// => text1
// => text3

const red_text_arg = Array.from( red_text );
red_text_arg.forEach((elm) => console.log(elm.textContent));
// => text1
// => text3

querySelector

document.querySelector(selectors);

  • #idName, .className, input[type="text"]のようなCSSセレクターで取得する要素を指定する
  • Elementを返す
  • 要素が存在しない場合はnullを返す
  • 複数の要素が該当する場合は最初に見つかった要素を返す
  • targetElem.querySelector()とした場合は、targetElemの子要素から要素を探す

HTML

<div id="app">
  <h1 class="title">Title</h1>
  <p class="text red">text1</p>
  <p class="text blue">text2</p>
  <div class="content">
    <p class="text blue">text3</p>
  </div>
</div>

JavaScript

const app = document.querySelector('#app');
// => <div id="app"></div>
app.querySelector('.content .text');
// => <p class="text blue">text3</p>
document.querySelector('#notFoundID');
// => null
app.querySelector('.text.blue');
// => <p class="text blue">text2</p>

querySelectorAll

  • 渡されたCSSセレクターにマッチする要素を集合(NodeList)として取得する
  • Array-likeなNodeListオブジェクトを返す
    • .lengthで要素数を返す
    • 配列のようにインデックスを指定すると該当要素にアクセスできる。
    • 素数外のインデックスを指定するとundefined (エラーにはならない)
  • 該当する要素が存在しない場合はNodeList []を返す。
    • length0
    • インデックスで要素にアクセスするとundefined
  • targetElem.querySelector()とした場合は、targetElemの子要素から要素を探す
  • getElementsByClassNameで取得できるHTMLCollectionとは違いNodeListforEachメソッドを持っているので、そのままループ処理をすることができる。
    for...of...でもループさせることは出来る。
    • ※ 当初はforEachメソッドも持っていなかったが、使えるようになった
    • map, filter, reduce, someなどの配列のメソッドは持っていないのでTypeErrorになる

HTML

<div id="app">
  <h1 class="title">Title</h1>
  <p class="text red">text1</p>
  <p class="text blue">text2</p>
  <div class="content">
    <p class="text blue">text3</p>
  </div>
</div>
<footer>
  <p class="small text red">text4</p>
</footer>

JavaScript

const textNodeList = document.querySelectorAll('.text');
// => NodeList(4) [p.text.red, p.text.blue, p.text.blue, p.small.text.red]
console.log( notFoundNodeList.length );
// => 4
console.log( textNodeList[0] );
// => <p class="text red">text1</p>
console.log( textNodeList[100] );
// => undefined

const notFoundNodeList = document.querySelectorAll('.notFoundClass');
// => NodeList []
console.log( notFoundNodeList.length );
// => 0
console.log( notFoundNodeList[0] );
// => undefined

const app = document.querySelector('#app');
const appTextNodes = app.querySelectorAll('.text.blue');
appTextNodes.forEach((elm) => console.log(elm));
// => <p class="text blue">text2</p>
// => <p class="text blue">text3</p>
for(let val of appTextNodes) {
  console.log(val);
}
// => <p class="text blue">text2</p>
// => <p class="text blue">text3</p>

appTextNodes.map((elm, i) => elm.innerHTML = `text-${i}`);
// => Uncaught TypeError

mapなどを使いたい場合は[...obj]Array.from()で配列化してやればOK

const appText = app.querySelectorAll('.text.blue');
[...appText].map((elm, i) => elem.innerHTML = `text-${i}`);
// => <p class="text blue">text-0</p>
// => <p class="text blue">text-1</p>

Array.from(appTextNodes).map((elm, i) => {
  elm.classList.replace('blue', 'green');
  console.log(elm);
});
// => <p class="text green">text-0</p>
// => <p class="text green">text-1</p>

 

感想とポエム

querySelector, querySelectorAllを使えばjQueryと同じ感覚でHTMLの要素を取得できるようになっているので所謂「脱jQuery」しやすい環境かなという印象です。
querySelectorjQueryのようにCSSセレクターで要素を取得だったので、複数の要素も取得できそうなイメージを持っていました。実際には最初にマッチした要素1つしか取得できないのでjQuery感覚で覚えてしまっているとハマりどころになりそうです。(複数の要素取得するつもりで、querySelector(.foo).forEach()ってやっちゃうとエラーになりますし)
後はクラス名で要素を取得しようとした時にナウいquerySelectorAllで取得できるNodeListオブジェクトはforEachできるけど、伝統的なgetElementsClassNameで取得できるHTMLCollectionオブジェクトはforEachしたらエラーで死ぬってのもちょっと紛らわしいんで統一感欲しかった感はあります。(querySelector系使っていけってメッセージなのかもしれないけど…

そして、jQueryとは違い要素が存在していないと、その後のメソッドでエラーになり処理そのものが止まってしまうので、取得した要素が絶対に在る事が担保できる状態で取得した要素を使う必要があるでしょう。
その点jQueryは良くも悪くもjQueryオブジェクトでラップしているので要素が本当に存在しているかどうかを厳密にチェックせずとも雑に使って大丈夫ですし、取得する要素が単数でも複数でもあまり意識する必要が無い。という部分は気にかける部分が減るという意味で利点だと思います。(VDOMのフレームワークで実際のDOM操作を気にかけなくて済むのも、気にかける部分が減るという意味で同じでしょう)

なもんでブラウザのAPIが揃ってきたから、この案件の対応ブラウザの条件ならjQueryは不要ってのは理解できるのだけれど、
jQueryは重いから不要とか、jQueryは辛いからもう使うべきじゃないって、jQuery悪の枢軸みないなワードを目にすることもありますが、前者は他のライブラリ使ったりwebpackしても、それらの容量あるんだし気になるような大きさじゃなくない?って思うし、後者はたしかに状態管理の為にDOMにデータ紐づけて管理するのは辛いと思うけど、それjQueryそのものじゃなくてDOMに直接イベント付けたりして状態管理する手法が辛いって事じゃない?ってチョット過激なポジショントークのように感じてます。jQuery使ってたらJavaScript覚えられないってのもフレームワークのお作法の範囲で使っていたら、深い部分を理解する必要がないので同じことでしょう。

状態管理するまでもないよう動きつけるだけで、後方互換も重要視されるような"ほうむぺぃじ"製作とかならDOM操作のPolyfillとしてjQueryは別に悪くない選択肢にまだまだ使える場面があり得るよねーって印象で、結局は関わっている案件や作りたいもののに依るんじゃないかなーって感想。

定量的に考えることが得意なはずのエンジニア界隈でライブラリや言語やエディタの話になると宗教問題みたいな論調が幅を利かすのチョット謎。やっぱ思い入れ? (IEの6, 7, 8, 9, 10対応みたいなバージョンがたくさん生きてた時代jQueryには大変お世話になったので悪の枢軸みたいに言われるのチョットなんだなかーって....これは私の思い入れ


[参考]

セレクター…