Edited at

jQueryからネイティブJavaScriptへ置き換えの第一歩

More than 1 year has passed since last update.

この記事はJavaScript Advent Calendar 2016の9日目の記事です。

jQueryのセレクタはめちゃくちゃ楽なので多用しているのですが、最近ちょこちょこRiot.jsを使い始めたこともあり、jQueryを使うのもいいけど出来れば外部ライブラリは最小限にして書きたいよねーってことで、jQueryでこうやって書いてたの、ネイティブのJavaScriptで実現する場合どうやるんだっけ?という備忘録。


  • jQuery3.1.1

  • GoogleChrome54で検証

  • HTML5が解釈できるWebブラウザなら動くはず (IE?知らない子ですね……)


セレクタ


Selectors API

jQueryでお馴染みCSSセレクタライクのセレクタをネイティブなJavaScriptでも使えるように実現しているのがSelectors APIです。

覚えるのはたった2つだけ!


:point_up: element.querySelector(All)としても使えます。



jQuery

$('div#hoge'); // <- jQueryオブジェクト

$('div.fuga'); // <- jQueryオブジェクト


SelectorsAPI

document.querySelector('div#hoge'); // <- element

document.querySelectorAll('div.fuga'); // <- NodeList
// そのほか
document.querySelectorAll('*');
document.querySelectorAll('div, p');
document.querySelectorAll('#hoge .fuga');
document.querySelector('#hoge').querySelectorAll('.fuga'); // $('#hoge').find('.fuga');


Selectors APIで使えないセレクタ

以下のセレクタはjQueryの独自拡張のため、Selectors APIでは使えません。

jQuery Extensions


フォーム絡みのもの

$(':button'); // -> querySelectorAll('button, input[type="button"]')

$(':checkbox'); // -> querySelectorAll('input[type="checkbox"]')
$(':file'); // -> querySelectorAll('input[type="file"]')
$(':image'); // -> querySelectorAll('input[type="image"]')
$(':hidden'); // -> querySelectorAll('[type="hidden"]')
$(':password'); // -> querySelectorAll('[type="password"]')
$(':radio'); // -> querySelectorAll('[type="radio"]')
$(':reset'); // -> querySelectorAll('[type="reset"]')
$(':text'); // -> querySelectorAll('[type="text"]')
$(':input');
$(':submit');


タグ絡みのもの

:header


要素の状態を表すもの


:animated, :visible, :hidden, :selected


要素の位置を表すもの


:first, :last, :parent, :odd, :even


その他


[name!="value"],
:contains(), :eq(), :gt(), :lt(), :has(),

ほとんどフォーム処理ですね。

selected,visible,hiddenあたりはないと困るかもしれません。


:point_up: 解釈できないセレクタが指定された場合、DOMException例外が発生します。



Selectors API以外で要素を取得する

大変便利なSelectors APIですが、速度面的な意味から単純な要素取得は昔ながらのDOM関数を使った方がよいみたいです。


ID

文書中の指定したIDの要素を取得する。


jQuery

$('#hoge'); // <- jQueryオブジェクト



ネイティブJS

document.getElementById('hoge'); // <- element

// Selectors API
document.querySelector('#hoge'); // <- element

IDは文書中でユニークでなくてはならないので、他のどのメソッドよりも早いようです。Elementオブジェクトを返します。

指定したIDの要素が見つからなかった場合はnullが返ってきます。


クラス

文書中の指定したクラスを含む要素を全て取得する。


jQuery

$('.fuga'); // <- jQueryオブジェクト



ネイティブJS

document.getElementsByClassName('fuga'); // <- HTMLCollection

// Selectors API
document.querySelectorAll('.fuga'); // <- NodeList


タグ名

文書中の指定したタグを持つ要素を全て取得する。


jQuery

$('div'); // <- jQueryオブジェクト



ネイティブJS

document.getElementsByTagName('div'); // <- HTMLCollection

// Selectors API
document.querySelectorAll('div'); // <- NodeList


Name

文書中の指定したnameを持つ要素を全て取得する。


jQuery

$('[name=first_name]'); // <- jQueryオブジェクト



ネイティブJS

document.getElementsByName('first_name'); // <- NodeList

// Selectors API
document.querySelectorAll('[name=first_name]'); // <- NodeList


NodeListとHTMLCollection

複数要素を取得するメソッドはNodeListHTMLCollectionが返ってきます。

まとめると、



  • querySelectorAllNodeListが返ってくる。


  • getElementsByTagNamegetElementsByClassNameHTMLCollectionが返ってくる。


  • getElementsByNameNodeListが返ってくる。(ちなみにIE11だとHTMLCollectionが返ってくる)

ややこしい・・・

どちらもlengthプロパティを持ち、角括弧を使うことによりelementを取り出すことが出来ます。ArrayオブジェクトっぽいですがArrayオブジェクトを継承しているわけではない、配列のようなオブジェクト(Array-likeオブジェクト)です。ふざけんなって感じですね。


elementの取り出し

<div class="fuga">fuga1</div>

<div class="fuga">fuga2</div>
<script type="text/javascript">
// NodeList
let node = document.querySelectorAll('.fuga'); // <- NodeList
console.log(node.length); // <- 2
node[0].style.backgroundColor = 'red'; // 最初のdiv.fuga要素の背景色を赤にする
// HTMLCollection
let list = document.getElementsByClassName('fuga'); // <- HTMLCollection
console.log(list.length); // <- 2
list[list.length-1].style.backgroundColor = 'blue'; // 最後のdiv.fuga要素の背景色を青にする
</script>

NodeListとHTMLCollectionはパッと見あまり差異がないように見えるのですが、NodeListは静的であり、DOMへの変更がNodeList オブジェクトの内容に影響を与えないです。これだけだとよくわからないと思いますが、かいつまんで説明しますと、NodeList取得後に要素の追加や削除されたDOMの変更が反映されないのがNodeList、変更が反映されるのがHTMLCollectionということです。

↓詳しくは下記のサイトで説明されています。

NodeListとHTMLCollectionも別物なので気を付けよう。(DOMおれおれAdvent Calendar 2015 – 13日目)


要素の操作

セレクタで要素を取得できるようになったので、要素の操作をしてみましょう。jQueryではjQueryオブジェクト、ネイティブのJavaScriptはElementオブジェクトが返ってくるので操作方法が異なります。

:warning: 注意 :warning:

getElementByIdquerySelectorで要素が見つからず取得できなかった場合、nullが返ってくるのでコードを続けて記述する場合は注意が必要です。


例:文書中に存在しないID(no-id)を指定した場合

// jQueryの場合

$('#no-id'); // <- 空のjQueryオブジェクト
$('#no-id').css('background-color'); // <- undefined
$('#no-id').css('background-color', 'red'); // エラーにはならない

// ネイティブJavaScriptの場合
document.getElementById('no-id'); // <- null
document.getElementById('no-id').style.backgroundColor; // エラー!
document.getElementById('no-id').style.backgroundColor = 'red'; // エラー!

// ちゃんとnullチェックを行う
let elm = document.getElementById('no-id');
if (elm) {
elm.style.backgroundColor = 'red';
}



HTML


取得


jQuery

$('div#hoge').html();



ネイティブJS

document.querySelector('div#hoge').innerHTML;



設定


jQuery

$('div#hoge').html('<span>hogehoge</span>');



ネイティブJS

document.querySelector('div#hoge').innerHTML = '<span>hogehoge</span>';



テキスト


取得


jQuery

$('div#hoge').text();



ネイティブJS

document.querySelector('div#hoge').textContent;



設定

raw textが設定されます(HTMLタグはエスケープ処理されてテキストとして表示される)


jQuery

$('div#hoge').text('<span>hogehoge</span>');



ネイティブJS

document.querySelector('div#hoge').textContent = '<span>hogehoge</span>';



フォーム値


取得


jQuery

$('input[name="first_name"]').val();



ネイティブJS

document.querySelector('input[name="first_name"]').value;



設定


jQuery

$('input[name="first_name"]').val('azusa');



ネイティブJS

document.querySelector('input[name="first_name"]').value = 'azusa';



スタイル操作

ネイティブJavaScriptにおけるstyleプロパティの詳細については下記参照。

CSS Properties Reference


取得


jQuery

$('#hoge').css('background-color');



ネイティブJS

document.getElementById('hoge').style.backgroundColor;



設定


jQuery

$('#hoge').css('background-color', '#fff');



ネイティブJS

document.getElementById('hoge').style.backgroundColor = '#fff';



クラス操作


クラス追加


jQuery

$('#hoge').addClass('fuga');



ネイティブJS

document.getElementById('hoge').classList.add('fuga');



クラス削除


jQuery

$('#hoge').removeClass('fuga');



ネイティブJS

document.getElementById('hoge').classList.remove('fuga');



Toggle


jQuery

$('#hoge').toggleClass('fuga');



ネイティブJS

document.getElementById('hoge').classList.toggle('fuga');



クラスの存在確認


jQuery

$('#hoge').hasClass('fuga'); // <- bool



ネイティブJS

document.getElementById('hoge').classList.contains('fuga'); // <- bool



属性(Attribute)


取得


jQuery

$('a.fuga.piyo1').attr('href');



ネイティブJS

document.querySelector('a.fuga.piyo1').getAttribute('href');



設定


jQuery

$('a.fuga.piyo1').attr('href', 'http://qiita.com/');



ネイティブJS

document.querySelector('a.fuga.piyo1').setAttribute('href', 'http://qiita.com/');



削除


jQuery

$('a.fuga.piyo1').removeAttr('href');



ネイティブJS

document.querySelector('a.fuga.piyo1').removeAttribute('href');



要素の作成


jQuery

$('<div>'); // <- jQueryオブジェクト



ネイティブJS

document.createElement('div'); // <- element


余談ですが、SVGの要素を生成する場合は、SVGElementを生成するためにnamespaceを指定しないといけないため、jQueryでは生成できません。

そのため、document.createElementNSを使って生成します。


SVG例(斜めの線を引く)

let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

svg.setAttribute('viewbox', '0 0 100 100');
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', 10);
line.setAttribute('y1', 10);
line.setAttribute('x2', 90);
line.setAttribute('y2', 90);
line.setAttribute('stroke', 'black');
svg.append(line);
document.body.append(svg);


子要素の追加/移動


新規要素を追加


HTML

<div id="hoge">

<!-- ココ(div#hogeの末尾)にdiv.piyo1要素追加 -->
<div class="fuga piyo1">piyo2</div>
<!-- ココ(div#hogeの末尾)にdiv.piyo3要素追加 -->
</div>


jQuery

// div.piyo1要素生成

let $div1 = $('<div>').addClass('fuga piyo1').text('piyo1');
// div.piyo3要素生成
let $div3 = $('<div>').addClass('fuga piyo3').text('piyo3');

// div.piyo1要素追加
$('#hoge').prepend($div1);
// div.piyo3要素追加
$('#hoge').append($div3);



ネイティブJS

// div.piyo1要素生成

let div1 = document.createElement('div');
div1.classList.add('piyo1');
div1.textContent = 'piyo1';

// div.piyo3要素生成
let div3 = document.createElement('div');
div3.classList.add('piyo3');
div3.textContent = 'piyo3';

// 追加
document.getElementById('hoge').prepend(div1);
document.getElementById('hoge').append(div3);
// または
document.getElementById('hoge').insertAdjacentHTML("afterbegin", div1.outerHTML);
document.getElementById('hoge').insertAdjacentHTML("beforeend", div3.outerHTML);



既存要素を移動


HTML

<div id="hogehoge" class="fuga">div#hogehoge</div>

<div class="fuga piyo1">div.fuga.piyo1-1</div>
<div class="fuga piyo1">div.fuga.piyo1-2</div>
<div id="hoge">
<!-- ココ(div#hogeの末尾)にdiv.piyo1要素移動 -->
<div class="fuga piyo2">piyo2</div>
<!-- ココ(div#hogeの末尾)にdiv.piyo3要素移動 -->
</div>
<div class="fuga piyo3">div.fuga.piyo3-1</div>
<div class="fuga piyo3">div.fuga.piyo3-2</div>


jQuery

/** 単要素の移動 */

// 要素の先頭に移動
$('#hoge').prepend($('#hogehoge'));
// 要素の末尾に移動
$('#hoge').append($('#hogehoge'));

/** 複数要素の移動 */
// 要素の先頭に移動
$('#hoge').prepend($('div.piyo1'));
// 要素の末尾に移動
$('#hoge').append($('div.piyo3'));


ネイティブJSでの複数要素の移動は若干面倒くさい…


ネイティブJS

/** 単要素の移動 */

// 要素の先頭に移動
document.getElementById('hoge').prepend(document.getElementById('hogehoge'));
// 要素の末尾に移動
document.getElementById('hoge').append(document.getElementById('hogehoge'));

/** 複数要素の移動 */
// 要素の先頭に移動
let divs1 = document.querySelectorAll('div.piyo1');
for (let i = divs1.length-1; i >= 0; i--) {
document.getElementById('hoge').prepend(divs1[i]);
}
// 要素の末尾に移動
let divs3 = document.querySelectorAll('div.piyo3');
for (let i = 0; i < divs3.length; i++) {
document.getElementById('hoge').append(divs3[i]);
}



要素の挿入\移動


指定した要素の前後に新規要素を追加


HTML

<!-- ココ(#hogeの前)にdiv.piyo1を追加 -->

<div id="hoge">hoge</div>
<!-- ココ(#hogeの後)にdiv.piyo3を追加 -->


jQuery

// div.piyo1要素生成

let $div1 = $('<div>').addClass('fuga piyo1').text('piyo1');
// div.piyo3要素生成
let $div3 = $('<div>').addClass('fuga piyo3').text('piyo3');

// div.piyo1要素追加
$('#hoge').before($div1);
// div.piyo3要素追加
$('#hoge').after($div3);


ネイティブJSでは指定した要素の後に追加するというメソッドがない。

そのため、nextSiblingを使って実現している。


ネイティブJS

// div.piyo1要素生成

let div1 = document.createElement('div');
div1.classList.add('piyo1');
div1.textContent = 'piyo1';

// div.piyo3要素生成
let div3 = document.createElement('div');
div3.classList.add('piyo3');
div3.textContent = 'piyo3';

let hoge = document.getElementById('hoge');
// div.piyo1要素追加
hoge.parentNode.insertBefore(div1, hoge);
// div.piyo3要素追加
hoge.parentNode.insertBefore(div3, hoge.nextSibling);

// または
document.getElementById('hoge').insertAdjacentHTML("beforebegin", div1.outerHTML);
document.getElementById('hoge').insertAdjacentHTML("afterend", div3.outerHTML);



指定した要素の前後に既存要素を移動


HTML

<!-- ココ(div#hogeの末尾)にdiv.piyo1要素移動 -->

<div id="hoge">
<div class="fuga piyo1">div.fuga.piyo1-1</div>
<div class="fuga piyo1">div.fuga.piyo1-2</div>
<div id="hogehoge" class="fuga">div#hogehoge</div>
<div class="fuga piyo3">div.fuga.piyo3-1</div>
<div class="fuga piyo3">div.fuga.piyo3-2</div>
</div>
<!-- ココ(div#hogeの末尾)にdiv.piyo3要素移動 -->


jQuery

/** 単要素の移動 */

// 要素の先頭に移動
$('#hoge').before($('#hogehoge'));
// 要素の末尾に移動
$('#hoge').after($('#hogehoge'));

/** 複数要素の移動 */
// 要素の先頭に移動
$('#hoge').before($('div.piyo1'));
// 要素の末尾に移動
$('#hoge').after($('div.piyo3'));


ネイティブJSでの複数要素の移動は若干面倒くさい…


ネイティブJS

let hoge = document.querySelector('#hoge');

/** 単要素の移動 */
// 要素の先頭に移動
hoge.parentNode.insertBefore(document.querySelector('#hogehoge'), hoge);
// 要素の末尾に移動
hoge.parentNode.insertBefore(document.querySelector('#hogehoge'), hoge.nextSibling);

/** 複数要素の移動 */
// 要素の先頭に移動
let divs1 = document.querySelectorAll('div.piyo1');
for (let i = 0; i < divs1.length; i++) {
hoge.parentNode.insertBefore(divs1[i], hoge);
}
// 要素の末尾に移動
let divs3 = document.querySelectorAll('div.piyo3');
for (let i = divs3.length-1; i >= 0; i--) {
hoge.parentNode.insertBefore(divs3[i], hoge.nextSibling);
}



要素の削除


HTML

<div class="fuga piyo1">div.fuga.piyo1-1</div>

<div class="fuga piyo1">div.fuga.piyo1-2</div>
<div id="hogehoge" class="fuga">div#hogehoge</div>
<div class="fuga piyo3">div.fuga.piyo3-1</div>
<div class="fuga piyo3">div.fuga.piyo3-2</div>


jQuery

/** 単要素の削除 */

$('#hogehoge').remove();
/** 複数要素の削除 */
$('div.fuga').remove();


ネイティブJS

/** 単要素の削除 */

document.querySelector('#hogehoge').remove();
/** 複数要素の削除 */
let fugas = document.querySelectorAll('div.fuga');
for (let i = 0; i < fugas.length; i++) {
fugas[i].remove();
}
// または
let fugas = document.getElementsByClassName('fuga');
while (fugas.length) {
fugas[0].remove();
}

/** これはNG (NodeListは静的なのでlengthが変わらず無限ループに陥る) */
let fugas = document.querySelectorAll('div.fuga');
while (fugas.length) {
fugas[0].remove();
}



繰り返し処理

jQueryオブジェクトではeachメソッドを使うことができ、簡単に繰り返し処理が実現可能です。


jQuery

$(".fuga").each(function(){

if ($(this).css('background-color') == 'red') { // $(this)は子要素のjQueryオブジェクト
$(this).css('background-color', 'black');
}
...
});

かんたんですね。

一方、ネイティブのJavaScriptではNodeListまたはHTMLCollectionが返ってくるのでそれらを処理します。

上述した通り、これらはArrayオブジェクトではないのでforEachは当然使えません。


ネイティブJS(ダメな例)

document.getElementsByClassName('fuga').forEach(function(elm, val, arr){ // エラー!

if (elm.style.backgroundColor == 'red') {
elm.style.backgroundColor = 'black';
}
...
});

for...of構文が使えるのでそちらを使います。


ネイティブJS

let list = document.getElementsByClassName('fuga');

for (let elm of list) {
if (elm.style.backgroundColor == 'red') { // elmはElementオブジェクト
elm.style.backgroundColor = 'black';
}
...
}

ただこの書き方はわりかし新しめの書き方でブラウザによっては使えない(IEは完全に未サポート、Edgeは12~)ので、ダメな場合は昔ながらのfor構文を使います。


ネイティブJS

let list = document.getElementsByClassName('fuga');

for (let i = 0; i < list.length; i++) {
if (list[i].style.backgroundColor == 'red') {
list[i].style.backgroundColor = 'black';
}
...
}

↓こんな方法もあるみたいです。

配列ライクなオブジェクトをforEachするときのイディオム


一括操作

jQueryだと指定されたクラスを持つ要素への一括操作は


jQuery

$(".fuga").css('background-color', 'red');


の一行で書けます。べんりですね!

しかし、ネイティブのJavaScriptはできません。


ネイティブJS(ダメな例)

document.querySelectorAll('.fuga').style.backgroundColor = 'red'; // エラー!


querySelectorAllの戻り値はNodeListなのでstyleプロパティがないためです。

素直に繰り返し処理するしかなさそうですね。


ネイティブJS

for (let elm of document.querySelectorAll('.fuga')) {

elm.style.backgroundColor = 'red';
}


参考

は~、やっぱjQuery使うか。。。