Edited at

You Don't Need jQuery


注意とお願い

この記事の内容はもはや古いです。ここに書いている方法では動かないものをいくつか見つけました。参考にする際は動作をよく確認してから使ってください。

ひとつお願いがあります。「あれ、動かないぞ」というコードを見つけたら是非コメントか編集リクエストで教えてください。解決方法までなくても結構です。「これはもう動かないよ」という印をつけたいのです。


この記事はYou Don't Need jQueryの日本語訳と同じ内容です。

先日ひょんなことからYou Don't Need jQueryの日本語訳をさせていただきました。著者のCam Songさんからも快諾をいただけたので1、Qiitaでも公開させていただきます。

なお、本家の英語の説明は継続的にメンテされているので、この記事の情報は古くなっている可能性があります。


追記


この記事は当初は「もうjQueryは必要ない」というタイトルで公開していました。このタイトルに関して不快に思われた方が多いようなので、タイトルを変更させていただきました。

不快な気分になられた方は申し訳ありません。一点、意訳で「もう」を加えて必要以上に煽りっぽくなってしまったのは翻訳の問題ですのでその点に関して原著者には非はない、ということだけ補足させてください。



jQueryは必要ない(You Don't Need jQuery)

フロントエンドの開発環境はめまぐるしく進化していて、最近のブラウザでは十分な質、量のDOM/BOM APIが実装されています。もうDOM操作やイベント処理のためにjQueryを覚える必要はありません。また、ReactやAngularそしてVueなどのフロントエンドライブラリの流行により、DOMを直接操作することはアンチパターンとなりました。jQueryはそれほど重要ではなくなったのです。このプロジェクトは、jQueryでの書き方の代わりとなるネイティブでの書き方(IE10以上)をまとめます。


目次


  1. Translations

  2. セレクタ

  3. CSSとスタイル

  4. DOM操作

  5. Ajax

  6. イベント

  7. ユーティリティ関数

  8. Promise

  9. アニメーション

  10. 選択肢

  11. 対応ブラウザ


Translations


セレクタ

classセレクタ、idセレクタ、属性セレクタのような主要セレクタはdocument.querySelectorもしくはdocument.querySelectorAllで代替できます。

jQueryのセレクタと比べて以下の違いがあります。



  • document.querySelectorはセレクタにマッチする最初のエレメントを返す


  • document.querySelectorAllはセレクタにマッチする全てのエレメントのNodeListを返す。Array.prototype.slice.call(document.querySelectorAll(selector));で配列に変換できる。

  • セレクタにマッチする要素がなかった場合、jQueryとdocument.querySelectorAllは[]を返すが、document.querySelectorはnullを返す。したがってNull Pointer Exceptionに注意する必要がある。もしくはdocument.querySelector(selector) ||[]のように||を使ってデフォルト値を指定しておく。


注意:document.querySelectordocument.querySelectorAllはかなり遅いです。もし、パフォーマンスが必要ならdocument.getElementByIddocument.getElementsByClassNamedocument.getElementsByTagNameを使ってください。




  • 1.0 セレクタによる選択

  // jQuery

$('selector');

// Native
document.querySelectorAll('selector');



  • 1.1 クラス名による選択

  // jQuery

$('.class');

// Native
document.querySelectorAll('.class');

// or
document.getElementsByClassName('class');



  • 1.2 idによる選択

  // jQuery

$('#id');

// Native
document.querySelector('#id');

// or
document.getElementById('id');



  • 1.3 属性による選択

  // jQuery

$('a[target=_blank]');

// Native
document.querySelectorAll('a[target=_blank]');



  • 1.4 子孫要素の選択

  // jQuery

$el.find('li');

// Native
el.querySelectorAll('li');



  • 1.5 兄弟要素の選択


    • 兄弟要素

    // jQuery
    
    $el.siblings();

    // Native
    Array.prototype.filter.call(el.parentNode.children, function(child) {
    return child !== el;
    });


    • 直前の兄弟要素

    // jQuery
    
    $el.prev();

    // Native
    el.previousElementSibling;


    • 直後の兄弟要素

    // jQuery
    
    $el.next();

    // Native
    el.nextElementSibling;



  • 1.6 祖先要素の選択


指定要素からdocument方向に遡って走査し、セレクタにマッチする最初の祖先要素を返します。

  // jQuery

$el.closest(selector);

// Native - 最近のブラウザのみ。IEでは動かない。
el.closest(selector);

// Native - IE10+
function closest(el, selector) {
const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

while (el) {
if (matchesSelector.call(el, selector)) {
return el;
} else {
el = el.parentElement;
}
}
return null;
}



  • 1.7 Parents Until

指定要素からセレクタにマッチする祖先要素までdocument方向に遡って走査し、フィルタにマッチする祖先要素を全て取得します。ただし、セレクタで指定された要素は含みません。

  // jQuery

$el.parentsUntil(selector, filter);

// Native
function parentsUntil(el, selector, filter) {
const result = [];
const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

// parentから走査を開始する
el = el.parentElement;
while (el && !matchesSelector.call(el, selector)) {
if (!filter) {
result.push(el);
} else {
if (matchesSelector.call(el, filter)) {
result.push(el);
}
}
el = el.parentElement;
}
return result;
}



  • 1.8 フォーム


    • input/textarea

    // jQuery
    
    $('#my-input').val();

    // Native
    document.querySelector('#my-input').value;



    • .radio内でのe.currentTargetのインデックスを返す

    // jQuery
    
    $(e.currentTarget).index('.radio');

    // Native
    Array.prototype.indexOf.call(document.querySelectorAll('.radio'), e.currentTarget);



  • 1.9 iframeのコンテンツ


$('iframe').contents()はiframeのcontentDocumentを返します。



  • Iframe contents

    // jQuery
    
    $iframe.contents();

    // Native
    iframe.contentDocument;




  • Iframe Query

    // jQuery
    
    $iframe.contents().find('.css');

    // Native
    iframe.contentDocument.querySelectorAll('.css');



    • 1.10 bodyを取得する



  // jQuery

$('body');

// Native
document.body;



  • 1.11 属性の設定、取得


    • 属性値を取得する

    // jQuery
    
    $el.attr('foo');

    // Native
    el.getAttribute('foo');


    • 属性値を設定する

    // jQuery, DOMを変化させずメモリ上で動作することに注意
    
    $el.attr('foo', 'bar');

    // Native
    el.setAttribute('foo', 'bar');



    • data-属性を取得する

    // jQuery
    
    $el.data('foo');

    // Native (`getAttribute`を使う)
    el.getAttribute('data-foo');
    // Native (IE11以上のサポートなら`dataset`を使ってもよい)
    el.dataset['foo'];



⬆ back to top


CSSとスタイル



  • 2.1 CSS


    • スタイルを取得する

    // jQuery
    
    $el.css("color");

    // Native
    // NOTE: 既知のバグ デフォルト値が'auto'の場合、値が指定されていなくても'auto'が返る
    const win = el.ownerDocument.defaultView;
    // nullは疑似要素でないことを示している
    win.getComputedStyle(el, null).color;


    • スタイルを設定する

    // jQuery
    
    $el.css({ color: "#ff0011" });

    // Native
    el.style.color = '#ff0011';


    • スタイルを一括取得、一括設定する

    複数のスタイルを一括で設定したいなら、oui-dom-utilsのsetStyles関数を参考にすると良いでしょう。

    + クラスを追加する

    // jQuery
    
    $el.addClass(className);

    // Native
    el.classList.add(className);


    • クラスを削除する

    // jQuery
    
    $el.removeClass(className);

    // Native
    el.classList.remove(className);


    • クラスの有無をチェックする

    // jQuery
    
    $el.hasClass(className);

    // Native
    el.classList.contains(className);


    • クラスの有無を切り替える

    // jQuery
    
    $el.toggleClass(className);

    // Native
    el.classList.toggle(className);



  • 2.2 横幅と高さ


横幅(width)と高さ(height)の書き方はほぼ同じなので、高さ(height)の例のみを示します。



  • ウィンドウの高さ

    // window height
    
    $(window).height();
    // jQueryのようにスクロールバーを除いた高さ
    window.document.documentElement.clientHeight;
    // スクロールバーを含めるなら
    window.innerHeight;



  • ドキュメントの高さ

    // jQuery
    
    $(document).height();

    // Native
    document.documentElement.scrollHeight;




  • エレメントの高さ

    // jQuery
    
    $el.height();

    // Native
    function getHeight(el) {
    const styles = window.getComputedStyle(el);
    const height = el.offsetHeight;
    const borderTopWidth = parseFloat(styles.borderTopWidth);
    const borderBottomWidth = parseFloat(styles.borderBottomWidth);
    const paddingTop = parseFloat(styles.paddingTop);
    const paddingBottom = parseFloat(styles.paddingBottom);
    return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom;
    }
    // integerで取得(`border-box`の時は`height`が、`content-box`の時は`height + padding + border`が返る)
    el.clientHeight;
    // decimalで取得(`border-box`の時は`height`が、`content-box`の時は`height + padding + border`が返る)
    el.getBoundingClientRect().height;



    • 2.3 PositionとOffset




  • Position

    offset parentを起点として、エレメントの座標を取得する。

    // jQuery
    
    $el.position();

    // Native
    { left: el.offsetLeft, top: el.offsetTop }




  • Offset

    documentを起点として、エレメントの座標を取得する。

    // jQuery
    
    $el.offset();

    // Native
    function getOffset (el) {
    const box = el.getBoundingClientRect();

    return {
    top: box.top + window.pageYOffset - document.documentElement.clientTop,
    left: box.left + window.pageXOffset - document.documentElement.clientLeft
    }
    }



    • 2.4 スクロール位置



縦スクロールバーの位置を取得する。

  // jQuery

$(window).scrollTop();

// Native
(document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;

⬆ back to top


DOM操作

DOMからエレメントを削除する。

  // jQuery

$el.remove();

// Native
el.parentNode.removeChild(el);



  • 3.2 Text


    • テキストを取得する

    子孫エレメントも含めた全テキスト内容を取得する。

    // jQuery
    
    $el.text();

    // Native
    el.textContent;


    • テキストを設定する

    エレメントのコンテントを指定されたテキストに設定する。

    // jQuery
    
    $el.text(string);

    // Native
    el.textContent = string;




  • 3.3 HTML


    • HTMLを取得する

    // jQuery
    
    $el.html();

    // Native
    el.innerHTML;


    • HTMLを設定する

    // jQuery
    
    $el.html(htmlString);

    // Native
    el.innerHTML = htmlString;



  • 3.4 Append


最後の子要素としてエレメントを追加する。

  // jQuery

$el.append("<div id='container'>hello</div>");

// Native
el.insertAdjacentHTML("beforeend","<div id='container'>hello</div>");

最初の子要素としてエレメントを追加する。

  // jQuery

$el.prepend("<div id='container'>hello</div>");

// Native
el.insertAdjacentHTML("afterbegin","<div id='container'>hello</div>");



  • 3.6 insertBefore

指定要素の後ろに新しいノードを追加する。

  // jQuery

$newEl.insertBefore(queryString);

// Native
const target = document.querySelector(queryString);
target.parentNode.insertBefore(newEl, target);



  • 3.7 insertAfter

指定要素の前に新しいノードを追加する。

  // jQuery

$newEl.insertAfter(queryString);

// Native
const target = document.querySelector(queryString);
target.parentNode.insertBefore(newEl, target.nextSibling);

セレクタにマッチするならtrueを返す。

  // is関数は複数エレメントや関数にも対応するが、matches関数は単一エレメントのみに使える

$el.is(selector);

// Native
el.matches(selector);

エレメントのディープコピーを生成する。

  // jQuery

$el.clone();

// Native
el.cloneNode();

// パラメータには`true`が渡され、深い複製を生成します。
// 浅い複製を生成するには、`false`を渡します。

全ての子ノードを削除する。

  // jQuery

$el.empty();

// Native
el.innerHTML = '';

エレメントを指定のHTMLで囲む。

  // jQuery

$('.inner').wrap('<div class="wrapper"></div>');

// Native
 Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
var wrapper = document.createElement('div');
wrapper.className = 'wrapper';
el.parentNode.insertBefore(wrapper, el);
el.parentNode.removeChild(el);
wrapper.appendChild(el);
});

セレクタにマッチしたエレメントの親要素をDOMから削除する。マッチしたエレメント自体は残す。

  // jQuery

$('.inner').unwrap();

// Native
Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
Array.prototype.slice.call(el.childNodes).forEach(function(child){
el.parentNode.insertBefore(child, el);
});
el.parentNode.removeChild(el);
});

セレクタにマッチしたエレメントの内容を与えられた内容に置き換える。

  // jQuery

$('.inner').replaceWith('<div class="outer"></div>');

// Native
Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
var outer = document.createElement('div');
outer.className = 'outer';
el.parentNode.insertBefore(outer, el);
el.parentNode.removeChild(el);
});

⬆ back to top


Ajax

Fetch APIはXMLHttpRequestを置き換える新たな規格です。ChromeとFirefoxで動きます。レガシーなブラウザでもpolyfillを使えます。

IE9以上ならgithub/fetch、IE8以上ならfetch-ie8、jsonpを利用したいならfetch-jsonpを試してみてください。



  • 4.1 マッチしたエレメントをサーバから取得したHTMLに置き換える。

  // jQuery

$(selector).load(url, completeCallback)

// Native
fetch(url).then(data => data.text()).then(data => {
document.querySelector(selector).innerHTML = data
}).then(completeCallback)

⬆ back to top


イベント

名前空間(namespace)と委譲(delegation)を利用した完全な代替手段が必要なら、 https://github.com/oneuijs/oui-dom-events を参照してください。



  • 5.0 ドキュメントが読み込まれたときの動作(DOMContentLoaded)

  // jQuery

$(document).ready(eventHandler);

// Native
// DOMContentLoadedがすでに完了していないか確認する
if (document.readyState !== 'loading') {
eventHandler();
} else {
document.addEventListener('DOMContentLoaded', eventHandler);
}



  • 5.1 イベントをバインドする(on)

  // jQuery

$el.on(eventName, eventHandler);

// Native
el.addEventListener(eventName, eventHandler);



  • 5.2 イベントをアンバインドする(off)

  // jQuery

$el.off(eventName, eventHandler);

// Native
el.removeEventListener(eventName, eventHandler);



  • 5.3 イベントを発火させる(trigger)

  // jQuery

$(el).trigger('custom-event', {key1: 'data'});

// Native
if (window.CustomEvent) {
const event = new CustomEvent('custom-event', {detail: {key1: 'data'}});
} else {
const event = document.createEvent('CustomEvent');
event.initCustomEvent('custom-event', true, true, {key1: 'data'});
}

el.dispatchEvent(event);

⬆ back to top


ユーティリティ関数

殆どのユーティリティ関数はネイティブのAPIで置き換えることができます。表記の一貫性やパフォーマンスを重視した他のライブラリを使う選択肢もあります。lodashがおすすめです。



  • 6.1 基本的なユーティリティ関数


    • isArray



配列かどうか判定する。

  // jQuery

$.isArray(array);

// Native
Array.isArray(array);


  • isWindow

windowかどうか判定する。

  // jQuery

$.isWindow(obj);

// Native
function isWindow(obj) {
return obj != null && obj === obj.window;
}


  • inArray

配列の中で、指定された値が最初に現れたインデックスを返す。(見つからなければ-1を返す)。

  // jQuery

$.inArray(item, array);

// Native
Array.indexOf(item);


  • isNumeric

数値かどうか判定する。

typeofを使ってください。ライブラリを使う場合、typeofは正確でない場合があります。

  // jQuery

$.isNumeric(item);

// Native
function isNumeric(item) {
return typeof item === 'number';
}


  • isFunction

JavaScript関数オブジェクトかどうか判定する。

  // jQuery

$.isFunction(item);

// Native
function isFunction(item) {
return typeof item === 'function';
}


  • isEmptyObject

空のオブジェクトである(列挙できる要素がない)か判定する。

  // jQuery

$.isEmptyObject(obj);

// Native
function isEmptyObject(obj) {
for (let key in obj) {
return false;
}
return true;
}


  • isPlainObject

{}もしくはnew Objectで生成されたオブジェクトであるか判定する。

  // jQuery

$.isPlainObject(obj);

// Native
function isPlainObject(obj) {
if (typeof (obj) !== 'object' || obj.nodeType || obj != null && obj === obj.window) {
return false;
}

if (obj.constructor &&
!{}.hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')) {
return false;
}

return true;
}


  • extend

二つ以上のオブジェクトをマージする。

object.assignはECMAScript6のAPIですが、polyfillも利用できます。

  // jQuery

$.extend({}, defaultOpts, opts);

// Native
Object.assign({}, defaultOpts, opts);


  • trim

前後の空白を除去する。

  // jQuery

$.trim(string);

// Native
string.trim();


  • map

配列やオブジェクトを新しい配列に変換する。

  // jQuery

$.map(array, function(value, index) {
});

// Native
array.map(function(value, index) {
});


  • each

配列やオブジェクトに対して繰り返し処理を行う。

  // jQuery

$.each(array, function(value, index) {
});

// Native
array.forEach(function(value, index) {
});


  • grep

フィルター関数に合致したエレメントだけを返す。

  // jQuery

$.grep(array, function(value, index) {
});

// Native
array.filter(function(value, index) {
});


  • type

JavaScript「クラス」名を判定します。

  // jQuery

$.type(obj);

// Native
Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();


  • merge

二つの配列をマージする。

  // jQuery

$.merge(array1, array2);

// Native
// 重複した要素は削除されない
function merge() {
return Array.prototype.concat.apply([], arguments)
}


  • now

現在の時刻を返す。

  // jQuery

$.now();

// Native
Date.now();


  • proxy

関数内で実行されるthisを任意のオブジェクトに変更する。

  // jQuery

$.proxy(fn, context);

// Native
fn.bind(context);


  • makeArray

配列形式のオブジェクトを配列に変換する。

  // jQuery

$.makeArray(arrayLike);

// Native
Array.prototype.slice.call(arrayLike);

// ES6なら
Array.from(arrayLike);

ある要素が他の要素の子孫であるか判定する。

  // jQuery

$.contains(el, child);

// Native
el !== child && el.contains(child);



  • 6.3 globaleval

JavaScriptコードをグローバル空間で実行する。

  // jQuery

$.globaleval(code);

// Native
function Globaleval(code) {
let script = document.createElement('script');
script.text = code;

document.head.appendChild(script).parentNode.removeChild(script);
}

// evalはcurrentコンテキストで実行される。$.globalevalのコンテキストはグローバルである。
eval(code);



  • 6.4 parse


    • parseHTML



文字列をDOM nodeの配列として返します。

  // jQuery

$.parseHTML(htmlString);

// Native
function parseHTML(string) {
const tmp = document.implementation.createHTMLDocument();
tmp.body.innerHTML = string;
return tmp.body.children;
}


  • parseJSON

JSON文字列をJavaScriptに変換します。

  // jQuery

$.parseJSON(str);

// Native
JSON.parse(str);

⬆ back to top


Promise

promiseは非同期処理の最終的な処理結果を表します。jQueryにはpromiseを扱うための独自の方法があります。ネイティブのJavaScriptではPromises/A+規格に則り、薄く、最小限のAPIを実装しています。



  • 7.1 done, fail, always

doneはpromiseが成功(resolved)したとき、fallは失敗(rejected)したとき、alwaysはどちらの場合も呼び出されます。

  // jQuery

$promise.done(doneCallback).fail(failCallback).always(alwaysCallback)

// Native
promise.then(doneCallback, failCallback).then(alwaysCallback, alwaysCallback)

whenは複数のpromiseを扱うときに使います。すべてのpromiseの結果が返ったときに成功となります(失敗が含まれてても成功となります)。

  // jQuery

$.when($promise1, $promise2).done((promise1Result, promise2Result) => {})

// Native
Promise.all([$promise1, $promise2]).then([promise1Result, promise2Result] => {});

Deferredはpromiseを作成する方法の一つです。

  // jQuery

function asyncFunc() {
var d = new $.Deferred();
setTimeout(function() {
if(true) {
d.resolve('some_value_compute_asynchronously');
} else {
d.reject('failed');
}
}, 1000);
return d.promise();
}

// Native
function asyncFunc() {
return new Promise((resolve, reject) => {
setTimeout(function() {
if (true) {
resolve('some_value_compute_asynchronously');
} else {
reject('failed');
}
}, 1000);
});
}

// Deferred way
function defer() {
let resolve, reject;
let promise = new Promise(function() {
resolve = arguments[0];
reject = arguments[1];
});
return { resolve, reject, promise };
}
function asyncFunc() {
var d = defer();
setTimeout(function() {
if(true) {
d.resolve('some_value_compute_asynchronously');
} else {
d.reject('failed');
}
}, 1000);
return d.promise;
}

⬆ back to top


アニメーション



  • 8.1 show、hide

  // jQuery

$el.show();
$el.hide();

// Native
// show関数の詳細を見たければ次のURLを参照してください
// https://github.com/oneuijs/oui-dom-utils/blob/master/src/index.js#L363
el.style.display = ''|'inline'|'inline-block'|'inline-table'|'block';
el.style.display = 'none';

エレメントが表示されていないなら表示し、表示されているなら非表示にします。

!!2018-12-25 Chrome バージョン: 71.0.3578.98で動作しないことを確認

  // jQuery

$el.toggle();

// Native
if (el.ownerDocument.defaultView.getComputedStyle(el, null).display === 'none') {
el.style.display = ''|'inline'|'inline-block'|'inline-table'|'block';
}
else {
el.style.display = 'none';
}



  • 8.3 fadeIn、fadeOut

  // jQuery

$el.fadeIn(3000);
$el.fadeOut(3000);

// Native
el.style.transition = 'opacity 3s';
// fadeIn
el.style.opacity = '1';
// fadeOut
el.style.opacity = '0';

エレメントのopacityを調整してください。

  // jQuery

$el.fadeTo('slow',0.15);
// Native
el.style.transition = 'opacity 3s'; // 'slow'は3秒だということにしている
el.style.opacity = '0.15';



  • 8.5 fadeToggle

フェードイン・フェードアウトを伴ってエレメントの表示・非表示を切り替えます。

  // jQuery

$el.fadeToggle();

// Native
el.style.transition = 'opacity 3s';
let { opacity } = el.ownerDocument.defaultView.getComputedStyle(el, null);
if (opacity === '1') {
el.style.opacity = '0';
}
else {
el.style.opacity = '1';
}



  • 8.6 スライドアップ、スライドダウン

  // jQuery

$el.slideUp();
$el.slideDown();

// Native
let originHeight = '100px';
el.style.transition = 'height 3s';
// slideUp
el.style.height = '0px';
// slideDown
el.style.height = originHeight;



  • 8.7 slideToggle

スライドを伴って、エレメントの表示・非表示を切り替えます。

  // jQuery

$el.slideToggle();

// Native
let originHeight = '100px';
el.style.transition = 'height 3s';
let { height } = el.ownerDocument.defaultView.getComputedStyle(el, null);
if (parseInt(height, 10) === 0) {
el.style.height = originHeight;
}
else {
el.style.height = '0px';
}

CSSプロパティで定義されたアニメーションを表示します。

  // jQuery

$el.animate({params}, speed);

// Native
el.style.transition = 'all' + speed;
Object.keys(params).forEach(function(key) {
el.style[key] = params[key];
})


選択肢



  • You Might Not Need jQuery - ネイティブのJavaScriptでイベント、エレメント、Ajaxを扱うサンプル集(英語)


  • npm-dom and webmodules - npmで利用できるDOMモジュールを集めたOrganizationです


対応ブラウザ

Chrome
Firefox
IE
Opera
Safari

Latest ✔
Latest ✔
10+ ✔
Latest ✔
6.1+ ✔


ライセンス

MIT





  1. 「もちろん。MITライセンスなんだから好きにしていいよ」とのこと。かっこいい!