ちょっと未来のJavaScript

本エントリは JavaScript Advent Calendar 201314日目となります。
来年遂にXPが逝去されるということで、IE9以降のシェアが飛躍的に伸びることを祈りつつ、IE9以降でJavaScriptでできるようになることを気がつく限りまとめてみました。

DOM

addEventListener / removeEventListener

イベントを登録/削除するためのメソッド。IE8まではattachEventとdetachEventという似たような、でも割と細かいところで動きが違うメソッドを使う必要があったが、IE9から標準のaddEventListenerがサポートされている。
※ jQueryのon/offとかbind/unbindとだいたい同じ。
※ 第3引数はuseCaptureといって、trueにするとイベント伝播を上位のDOMから発生させることができる。まあ使うことないけど。
addEventListener, removeEventListener

1
2
3
4
5
6
7
8
function onClickFunction(e) {
    //クリック時の処理
}
// クリックイベント設定
document.getElementById('hoge').addEventListener('click', onClickFunction, false);

// クリックイベント削除
document.getElementById('hoge').removeEventListener('click', onClickFunction, false);

createEvent / dispatchEvent

イベントを作成/発火するためのメソッド。IE8まではcreateEventObjectとfireEventというこれまた似たような、でもやっぱり色々動きが異なるメソッドがあった。
※ jQueryの$.eventとかtriggerとだいたい同じ。
createEvent, dispatchEvent

1
2
3
4
5
6
7
8
9
10
11
// イベントオブジェクト作成
// 引数にはイベント種別を渡す。
var evt = document.createEvent('MouseEvents');

// イベントオブジェクト初期化
// 引数の内容はhttps://developer.mozilla.org/ja/docs/Web/API/event.initMouseEventを参照。
// マウス座標とかctrlキーが押されてるかどうかなど色々指定可能。
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);

// イベント発火
document.getElementById('hoge').dispatchEvent(evt);

Composition Events

composition Eventsは文字の変換イベントを取得できるメソッドです。
これまではFFにも対応したければsetIntervalで入力中にテキストフィールドを監視する必要があったのが、これを使えば解消することができます。
まあIE系ならkeydown + setTimeoutでもいいし、FFならinputイベント使えばいいって話もあるけど。
※ compositionstartイベントとcompositionupdateイベントは同時に発火するが、compositionupdateイベントとcompositionendイベントは同時に発火しない。
CompositionEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var textElement = document.getElementById('hoge');

// テキスト変換開始時イベント監視
textxElement.addEventListener('compositionstart', function(e) {
    //変換開始時処理
}, false);

// テキスト変換中イベント監視
textxElement.addEventListener('compositionupdate', function(e) {
    //変換中処理
}, false);

// テキスト変換完了時イベント監視
textxElement.addEventListener('compositionend', function(e) {
    //変換完了時処理
}, false);

Mutation Events

DOMの追加/変更/削除などDOMの状態監視ができるイベント。類似メソッドは今までなかったのでかなり画期的なんじゃないかと思う。Angularの処理を強引にjQueryで補足したいときとかに使えるかもね。
※ 多用すると処理が追えなくなる可能性あり。
※ IE9ではDOMNodeInsertedが動かないらしい。
※ パフォーマンスの関係でMutation Eventsは非推奨になり、最新仕様はMutation Observerになっている。そのためMutation EventsはPolyfillとして用いるべし。
Mutation Events, Mutation Observer

1
2
3
4
5
6
7
8
// エレメントの属性の変更を監視
document.addEventListener('DOMNodeInserted', function(e) {
    // 属性変更時処理
    console.log('追加された!');
});

document.body.appendChild(document.createElement('div'));
// -> 追加された!
DOMAttrModifiedはどのブラウザでも動作しない模様。  

DOMContentLoaded Event

DOMの読み込みを待ち合わせるイベント。
※ $(document).ready(function(){})とか$(function(){})と同じ。IE9以降なら内f部的にこれが使われてる。
DOMContentLoaded

1
2
3
document.addEventListener('DOMContentLoaded', function() {
    // DOM読み込み時処理
}, false);

querySelector / querySelectorAll

CSSのselectorでDOM探索ができるメソッド。完全にjQuery感覚で使えます。まあgetElementByIdとかgetElementsByClassNameとか別のが使える場合はそっち使った方が速いです。
querySelectorAll
※ 追記 querySelectorはIE8からサポートされてましたm(__)m。@teppeisさんご指摘ありがとうございますー!

1
var list = document.querySelectorAll('ul li');

classListはIE10から、datasetはIE11からの実装のようです。。。

ECMA Script 5

Array

Array型の追加メソッドはunderscoreとか使ってる人ならピンとくるはず。  
  • forEach
    引数に関数をとり、値とインデックスがループでまわって引数に入ってくる。ようはfor文回すのに、iとかlengthとか必要ないし、 ループ内スコープの変数が使えるってこと。
    forEach
1
2
3
4
5
6
7
8
9
var arr = ['a', 'b', 'c', 'd', 'e'];

arr.forEach(function(value, index) {
    // function内部なので、スコープがループの中で閉じる
    var foo = 'bar';

    console.log(value, index);
    // -> 値とインデックスがコンソールに出力される
});
  • filter
    配列の中から指定した条件を満たすものだけを取り出して新たな配列を作成することができる。非破壊的メソッド。
    filter
1
2
3
4
5
6
var arr = [2, 45, 21, 67, 32, 2, 43];

arr.filter(function(value, index) {
    // 偶数のものだけ取り出す。
    return value % 2 === 0;
});
  • indexOf
    配列を指定された値で検索するメソッド。返却値は見つかった最初のインデックスで、ない場合は-1が返る。
    検索を始めるインデックスを第二引数で指定することも可能。
    indexOf
1
2
3
4
5
6
var arr = ['abc', 'def', 'ghi', 'jkl'];

console.log(arr.indexOf('def'));
// -> 1
console.log(arr.indexOf('mno'));
// -> -1
  • some
    配列の各要素に対してテストを実行し、一つでもテストに合格する要素があればtrueを返し、一つもなければfalseを返す。
    some
1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [2, 45, 21, 67, 32, 2, 43];

// 60以上の値があるかどうか判定
console.log(arr.some(function(value) {
    return 60 < value;
}));
// -> true

// 70以上の値があるかどうか判定
console.log(arr.some(function(value) {
    return 70 < value;
}));
// -> false
  • every
    配列の各要素に対してテストを実行し、全要素がテストに合格であればtrueを返し、一つでも欠ければfalseを返す。
    every
1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [2, 45, 21, 67, 32, 2, 43];

// 全ての値が1以上かどうか判定
console.log(arr.every(function(value) {
    return 1 < value;
}));
// -> true

// 全ての値が5以上かどうか判定
console.log(arr.every(function(value) {
    return 5 < value;
}));
// -> false
  • map
    配列の各要素に対して処理を行い、その結果からなる新しい配列を生成する。非破壊的メソッド
    map
1
2
3
4
5
6
7
var arr = [2, 45, 21, 67, 32, 2, 43];

// 全ての値が1以上かどうか判定
console.log(arr.map(function(value) {
    return value * 10;
}));
// -> [20, 450, 210, 670, 320, 20, 430]
  • reduce
    配列の隣り合う値を左から順に呼び出し、その結果から単一の値をつくる。
    ※ 右から実行するreduceRightもある。
    reduce
1
2
3
4
5
6
7
8
var arr = [2, 45, 21, 67, 32, 2, 43];

// 全ての値が1以上かどうか判定
console.log(arr.reduce(function(value1, value2) {
    console.log(arguments);
    return value1 + value2;
}));
// -> 212

Object

  • defineProperty / defineProperties
    -Object.hoge = function(){};の形で書くのに比べ、writable属性、enumerable属性、configurable属性の設定ができたり、アクセサ(配列のlengthみたいなやつ)がつくれたりする。
    • writable: 上書き可能かどうか
    • enumerable: for inループなどのObjectのプロパティ列挙時に表示されるかどうか。
    • configurable: プロパティの設定を変更することができるかどうか。
    • get: プロパティのゲッターとなるメソッド。(アクセサの場合のみ)
    • set: プロパティのセッターとなるメソッド。(アクセサの場合のみ)
      ※ 個人的にはprototype拡張の際によく使うが、様々な用途で使えるとは思う。
      defineProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var MyClass = function() {
    this.members = {};
};

// メンバーを登録するsetMemberメソッドを作成
Object.defineProperty(MyClass.prototype, 'setMember', {
    writable: false,
    enumeable: false,
    configurable: false,
    value: function(id, name) {
        this.members[id] = name;
    }
});

// メンバーを取得するgetMemberメソッドを作成
Object.defineProperty(MyClass.prototype, 'getMember', {
    writable: false,
    enumeable: false,
    configurable: false,
    value: function(id) {
        return this.members[id];
    }
});

// 現在のメンバー数を返却するcountアクセサを追加。値を指定された場合は何もしない。
Object.defineProperty(MyClass.prototype, 'count', {
    get: function() {
        return Object.keys(this.members).length;
    },
    set: function() {}
});

var myClass = new MyClass();

myClass.setMember('foo', 'bar');
myClass.setMember('hoge', 'hogehoge');
console.log(myClass.getMember('foo'));
// -> 'bar'

console.log(myClass.count);
// -> 2

// setメソッドは何もしないので値を登録しても値は変化しない。
myClass.count = 10;
console.log(myClass.count);
// -> 2
  • create
    指定したオブジェクトのプロトタイプ及び、プロパティを持つオブジェクトを生成する。簡単に既存のオブジェクトを継承して、新しいプロトタイプも持つオブジェクトがつくれる。
    definePropertyと同様に、各属性の設定や、アクセサもつくれる。
    create
1
2
3
4
5
6
7
8
9
10
11
12
13
// 通常の配列を追加
var arr = ['a', 232, 'b', true, 1111];
// 配列を拡張して、0から数えたlengthプロパティを新たに追加。
var customArr = Object.create(arr, {
    lengthFromZero: {
        get: function() {
            return this.length - 1;
        },
        set: function() {}
    }
});

console.log(customArr.lengthFromZero);
  • getOwnPropertyNames
    enumerable属性に関わらず、オブジェクトのプロパティを配列で返却する。
    getOwnPropertyNames
1
2
console.log(Object.getOwnPropertyNames(Array.prototype));
// -> ["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight"]
  • keys
    オブジェクトの列挙可能なプロパティを配列で返却する。for-inループと違い、プロトタイプのプロパティはとってこない。
    keys
1
2
3
4
5
6
var obj = {
    foo: 'bar',
    hoge: 'hogehoge'
};
console.log(Object.keys(obj));
// -> ['foo', 'hoge']
  • freeze
    オブジェクトを凍結する。具体的にはプロパティの追加/編集/削除、設定変更を不可能にする。プロパティがObjectの場合、その中身までは凍結できない。
    freeze
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {
    foo: 'bar',
    hoge: 'hogehoge',
    list: {
        foofoo: 'barbar'
    }
};

// オブジェクトを凍結
Object.freeze(obj);

// obj.list.foofooのみ編集可能。
obj.hoge = 'hogehogehoge';
obj.name = 'taro';
obj.list.foofoo = 'barbarbarbar';
console.log(obj);

Function

  • bind Functionのthisを引数のオブジェクトでバインドする。apply/callと違ってその場でfunctionコールせず、thisでbindされたfunctionオブジェクトを返却してくれる。
    ※ $.proxyとか_.bindと同じ。
    bind
1
2
3
4
5
6
7
8
function foo() {
    console.log(this);
}

var bar = foo.bind({hoge: 'hogehoge'});

bar();
// -> {hoge: 'hogehoge'}

String

  • trim / trimRight / trimLeft
    念願のtrimがようやくjsに。文字列の前後の空白を削除する。非破壊的メソッド
    ※ 驚くべきことに全角空白にも対応している!!!
    trim
1
2
3
4
var foo = '      bar      ';

console.log(foo.trim());
// -> bar

Date

  • now
    UTC(協定世界時)での1970年1月1日00時00分00秒から現在までの経過ミリ秒を数値で取得するメソッド。new Date().getTime()と同じ。相違点はスピード。
    now
1
console.log(Date.now());

IE8とどうしてもまだ付き合わなきゃいけない方へ

es5のshimを使ってくださってる方がいるので、これを入れると上記のメソッド達はだいたい使えます。
https://github.com/kriskowal/es5-shim

DOM系のやつもそれぞれshimを作ってる方がいたり、あとは自分でpolyfill作ってしまってもよいかもですね。

まとめ的な

HTML5とかCSS3はまだだいぶ弱いですが、DOM系とES5系でそれなりに強化されるので、小さいサイトの制作であればjQueryを使わない選択肢があってもいいんじゃないかなーと思ってます。
まあこのへんが前からサポートされてるスマートフォン向けサイトでも、Zeptoすらあまり使われずjQueryが当たり前に使われてる現状を考えると難しそうですが。。。
ネイティブのjsでかかれたプラグインが充実してくれば変わってくるのかな。

HTML5

HTML5のAPIについてはいろんなところにサンプルあるので詳細は割愛しますー。
やはりCanvasとSVGが目立つところですかね。

canvas

SVG

Sectioning Elements

Geolocation

Video(H.264) not Mpeg4, WebM

Audio(AAC and MP3)

IE10+ (HTML5)

ちゃんと洗い出1してないのでこれだけではないと思いますが、IE10まで移行できれば強力なAPIが目白押しですね。

History API

websocket

input[type=xxx]

Form VDalidation

Pointer Events

XHR2

Drag and Drop

Native Binary Data

Web Workers

CORS

Sandboxed iframe

Application cache

Indexed DB

File API

pagevisibility API

CSS3 Animations

さらにES6も使いたい方へ

ES6では、定数、ブロックスコープ変数、Class、Module、Promise、generator、iterator、配列内包表記、arrow function、Map、WeakMap、Set、of loopなどなどまさに夢のようなAPI達がこれでもかと使うことができます。現在の実装状況は下記を参照すると良いです。
http://kangax.github.io/es5-compat-table/es6/ 見ていただければわかりますが、FFの実装が最も進んでいます。Chromeも頑張ってはいますが、FlagをONにしなきゃいけないので非現実的ですね。
まとめると以下の方法が現在ES6を使うための手法です。

主要な機能は大体使えます。具体的な使い方はこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
  <body>
    <script src="https://traceur-compiler.googlecode.com/git/bin/traceur.js"
        type="text/javascript"></script>
    <script src="https://traceur-compiler.googlecode.com/git/src/bootstrap.js"
        type="text/javascript"></script>
    <script type="text/traceur">
      class Greeter {
        constructor(message) {
          this.message = message;
        }

        greet() {
          let element = document.querySelector('#message');
          element.innerHTML = this.message;
        }
      };

      let greeter = new Greeter('Hello, world!');
      greeter.greet();
    </script>
  </body>
</html>
  • typescriptを使う。 MicroSoftの開発しているtypescriptはES6の先行実装的な思想で作られてるjsのプリコンパイラなので、これを使う手もあります。
    typescript
  • node.jsを使う。
    Gruntなどサーバを立てる用途でなくてもnodeを使う機会は確実に増えている今日この頃、これを使わない手はありません。
    node起動時に—harmonyオプションをつけるか、Gruntなど内部的にNodeを呼ぶやつで使う場合はpackage.jsonをちょっと工夫してやるとES6の機能が開放されます。
1
2
3
4
5
{
  "scripts": {
    "start": "node --harmony ./node_modules/.bin/grunt start"
  }
}
  • FirefoxOSアプリ開発を行う。
    上記でも言ったようにFirefoxの実装は最も進んでいる状況なので、FirefoxOS向けアプリ開発ではES6の機能がガンガン使えます。
    本来Web標準で作れることのメリットであるクロスプラットフォームの互換性は失われますが、そんなものは現場からすると初めから幻想なのでムシムシ。
  • RhinoもしくはNashornを使う。
    これはだいぶマニアックな方法ですがMozillaがJVM上でjavascriptで書いたコードをJavaに変換して実行するライブラリを出しています。
    MozillaということはFirefoxと大体同じ実装状況だと言えるので、ES6の機能もだいぶサポートされています。
    Java7までがRhino、Java8からがNashornになります。
    ちなみにClosure CompilerはRhinoを使ってますね。

というわけでここまで読んでいただいてありがとうございましたー!