インターン講義4日目「JavaScript で学ぶ イベントドリブン」

今日行なわれたインターン4日目の講義「JavaScript で学ぶ イベントドリブン」by id:cho45の資料と録画を公開します。今回は、JavaScriptの概要からDOM、イベントドリブンまでを2時間で網羅した密度の高い講義となっています。今回の放送は安定していましたので、音質・画質とも良好にできました。

明日もAM10:30より、JavaScript界の貴公子id:nanto_viによる「ユーザーインターフェース, HTML5」を放送しますので、是非ご覧ください。


自己紹介

  • id:cho45 - vimmer
  • うごメモチームのエンジニア (7月後半〜)
    • 少し前までブックマークチーム
  • Perl, JS (Scala, Ruby, etc...)
    • サーバサイド・クライアントサイドUI・スマートフォンなど
    • Java や AS も場合によっては書いています
  • 特技
    • 1行コードを書くごとにハマること

アジェンダ

  • JavaScript の言語について
  • DOM について
  • イベントについて
  • Ten.js について

JS 書いたことがある人?

AS 書いたことがある人?

目的

講義時間は限られているので

  • JS を学ぶ上でとっかかりをつかめること
    • リファレンスを提示します。概念を理解
  • 言語的部分、DOM 及びイベントドリブンなプログラミング

覚えようとするとクソ多いのでリファレンスひける部分は覚えない

JS とはどんな言語であるか?

  • 基本クライアントサイド=ブラウザで動く
    • 実装がたくさんある
  • はてなのエンジニアはみんな誰もがある程度書けます
    • ウェブアプリを書く上で必須なため
  • フロントエンドの重要性
    • 今回のインターンからフロントエンドが今日・明日とあります

先に JS のデバッグ方法

  • alert()
    • 古典的だが今でも最も役に立つ
    • その時点で処理がブロックするので、ステップを1つずつ確認するとき便利
  • console.log()
    • ブロックせずにコンソール領域へ直接出力
    • 他言語の print デバッグに相当する
    • 他のモダンブラウザでは標準でサポートされつつある
      • (Firefox では Firebug が必要)
  • これらを仕込んで、リロードしまくることで開発します
    • エラーコンソールをどうやって開くかからはじめましょう

クロスブラウザについて

ここでクロスブラウザについて覚えても仕方ないので、Firefox と Firebug で開発することを前提にします。

JavaScript 言語について

言語的特徴

  • 変数に型なし
    • プリミティブな型 + オブジェクティブな型
    • ただしプリミティブ型も自動的にオブジェクティブ型へ変換される
  • 関数はデータとして扱える (オブジェクト)
  • 文法は C に似てる
  • 言語的な部分はECMAScript として標準化されている
    • ActionScript も同じ。AS やったことあるなら JS は DOM さえ覚えれば良い
  • プロトタイプ指向なためクラスというものはない
    • クラス指向はプロトタイプに制限を加えたものなので実現はできる

1つずつ説明していきます

変数に型なし

Perl と一緒で、Java や C などと違う点

var foo = "";
foo = 1;
foo = {};

文字列を入れた変数にあとから数値を入れたりオブジェクトを入れられる

値自体には当然ちゃんと型があります

JSの型

  • undefined
  • number
  • boolean
  • string
  • object
    • null
    • ({})
    • []

number, boolean, string とかはプリミティブ値と呼ばれます

オブジェクト以外はプリミティブです。

JSの型 - typeof

typeof 演算子で調べることができます

typeof undefined;
typeof 0;
typeof true;
typeof {};
typeof [];
typeof null;
typeof "";
typeof new String("");
typeof alert;

それぞれどれになるでしょう?

undefined , number , boolean , string , object, function

JSの型 - typeof

typeof undefined //=> "undefined"
typeof 0;        //=> "number"
typeof true;     //=> "boolean"
typeof "";       //=> "string"
typeof {};       //=> "object"
typeof [];       //=> "object"
typeof null;     //=> "object"
typeof new String(""); //=> "object"
typeof alert;    //=>"function"

JS の型、object 型

  • Array や Regexp など
  • string, number をラップする String, Number
  • Function

プリミティブ型からオブジェクト型へは自動変換がかかります (null, undefined 以外)

"foobar".toLowerCase();

としたとき、"foobar" は stringプリミティブであるにも関わらず、メソッドを呼べるのはメソッドを呼ぼうとしたときに自動的に String オブジェクトへ変換されるからです。

new String("foobar").toLowerCase();

とはいえ大抵はオブジェクトへの変換を気にする必要はありません。

object 型

そもそもオブジェクト型って何かというと、名前と値のセット (プロパティ) を複数持ったものです。

連想配列とかハッシュとか言えばだいたい想像できると思います。

var obj = {}; // オブジェクトリテラル記法。new Object() と同じ
obj['foo'] = 'bar'; // foo という名前に 'bar' という値をセットしている。
obj.foo = 'bar'; // これも上と全く同じ意味
var obj = {
   foo : 'bar'
};

配列もJSではオブジェクトで実装されています

undefined と null

var foo;
typeof foo; //=> undefined

未定義値。

var foo = null
typeof foo; //=> object

何も入ってないことを示す

(object が入ってるを示したいが空にしときたいとか)

関数はデータ

JS では関数もデータになれ、Function オブジェクトのインスタンス扱いです。(第一級のオブジェクトというやつです)

変数に代入でき、引数として関数を渡すことが可能です。

var fun = function (msg) {
	alert(arguments.callee.bar + msg);
};
// object なので
fun.bar = 'foobar';

fun();

関数はデータ (2)

JS では関数もデータになれ、Function オブジェクトのインスタンス扱いです。(第一級のオブジェクトというやつです)

変数に代入でき、引数として関数を渡すことが可能です。

var fun = function (callback) {
	alert('1');
	callback();
	alert('3');
};
fun(function () {
	alert('2');
});

関数の定義

function foobar () {
}

var foobar = function () {
};

はほぼ同じです (呼べるようになるタイミングが違います)

関数: arguments

function foobar () {
	alert(arguments.callee === foobar); //=> true
	alert(arguments.length); //=> 2
	alert(arguments[0]);     //=> 1
	alert(arguments[1]);     //=> 2
}
foobar(1, 2);

変数のスコープ

関数スコープです。for などのループでスコープを作らないことに注意

function (list) {
	for (var i = 0, len = list.length; i < len; i++) {
		var foo = list[i];
	}
}

は以下と同じ

function (list) {
	var i, len, foo;
	for (i = 0, len = list.length; i < len; i++) {
		foo = list[i];
	}
}

変数のスコープ:ハマりポイント

var foo = 1;
(function () {
	alert(foo); //=> undefined
	var foo = 2;
	alert(foo); //=> 2
})();

は以下と同じ

var foo = 1;
(function () {
	var foo = undefined;
	alert(foo); //=> undefined
	foo = 2;
	alert(foo); //=> 2
})();

小ネタ: 配列的なオブジェクトを配列にする

var arrayLike = {
  '0' : 'aaa',
  '1' : 'bbb',
  '2' : 'ccc',
  '3' : 'ddd',
};
arrayLike.length = 4;
var array = Array.prototype.slice.call(arrayLike, 0);

Array が持つ関数の多くはレシーバに Array コンストラクタを使って作られたことを要求しない

Array の基本的要件は

  • length プロパティを持っていること
  • 数字 (の文字列) を配列の要素のプロパティとして持っていること

arguments や NodeList なども Array ではない Array-like なもの

プロトタイプ指向

他のあるオブジェクトを元にして新規にオブジェクトをクローン (インスタンス化) していくオブジェクト指向技術

クラス指向はクラスからしかインスタンス化できないが、プロトタイプ指向ではあらゆるオブジェクトをインスタンス化できる

メリット
  • 柔軟
  • HTML にように個々の要素がほぼ同じだけど微妙に違う場合に便利
デメリット
  • 自由すぎる

JS におけるプロトタイプ

プロトタイプにしたいオブジェクトに初期化関数を組み合せることでオブジェクトをクローンできる

function Foo () { }
Foo.prototype = {
   foo : function () { alert('hello!') }
};

var instanceOfFoo = new Foo();
instanceOfFoo.foo(); //=> 'hello!'

JS におけるプロトタイプ (プロトタイプチェーン)

function Foo () { }
Foo.prototype = {
   foo : function () { alert('hello!') }
};

function Bar () { }
Bar.prototype = new Foo();
Bar.prototype.bar = function () { this.foo() };

var instanceOfBar = new Bar();
instanceOfBar.bar(); //=> 'hello!'
  • 新規に作られたオブジェクトは自身のプロトタイプへの暗黙的な参照を持っている
  • プロトタイプにしたいオブジェクトに初期化関数を組み合せることでオブジェクトをクローンできる
  • 暗黙的参照は既定オブジェクトである Object まで暗黙的な参照を持っている
    • => プロトタイプチェイン

プロトタイプチェーンは長くなると意味不明になりがちなので、あんまりやらないことが多い気がします。(継承やりすぎると意味不明なのがひどくなった感じです)

this について

  • this という暗黙的に渡される引数のようなものがあります
  • Perl の $self みたいなやつです
  • 普通はレシーバーが渡されます
    • foo.bar() の foo のことをレシーバといいます
  • 明示的に渡せすこともできる
  • this はどんなときも必ず typeof this == "object" です
    • primitive を渡すと変換
    • null を渡すとグローバルオブジェクト
var a = { foo : function () { alert( a === this ) } };
a.foo(); //=> true
a.foo.call({}); //=> false

JS の使われかた

  • HTML の
  • script 要素で
<!DOCTYPE html>
<title>JS test</title>
<script type="text/javascript" src="script.js"></script>

JS については以上です

質問など

  • 変数に型なし
  • 関数はデータとして扱える (オブジェクト)
  • プロトタイプ指向
  • 文法的に
  • そもそも?

(あとで書いてみると疑問になることが多いと思うので聞いてください)

DOM について

DOM とは

  • Document Object Model の略です
  • HTML とか CSS を扱うときの API を定めたものです
  • ブラウザはDOM APIをJSで扱えるようにしています
  • DOM にはレベルというものがあります
    • DOM Level 0 = 標準化されてなかったものの総称
    • DOM Level 1 = とても基本的な部分 (Element がどーとか)
    • DOM Level 2 = まともに使えるDOM (Events とか)
    • DOM Level 3 = いろいろあるが実装されてない

今は高レベルのDOMといえそうなものでは、HTML5として仕様が策定されていたりする

DOM Level 0 の多くも HTML5 で標準化されている

DOM の基本的な考えかた

一番上にはドキュメントノード

DOM の構成要素

  • Node
    • 全てのDOMの構成要素のベースクラス
  • Element
    • HTML の要素を表現する
  • Attr
    • HTML の属性を表現する
  • Text
    • HTML の地のテキストを表現する
  • Document
    • HTML のドキュメントを表現する

Text も Attribute も Node のうちです。これらがツリー構造になっています。

よく使うメソッド

  • document.createElement('div')
    • 要素ノードをつくる
  • document.createTextNode('text')
    • テキストノードをつくる
  • element.appendChild(node)
    • 要素に子ノードを追加する
  • element.removeChild(node)
    • 要素の子ノードを削除する
  • element.getElementsByTagName('div')
    • 指定した名前をもつ要素を列挙
  • document.getElementById('foo')
    • 指定したIDをもつ要素を取得

例えばテキストノードを要素に追加する場合

var elementNode = document.createElement('div');
var textNode    = document.createTextNode('foobar');
elementNode.appendChild(textNode);

<div>foobar</div>

cloneNode(true);

空白もノードです

<div id="sample">
<span>foobar</span>
</div>
alert(document.getElementById('sample').firstChild); //=> ?

続きはリファレンスで

言語的な部分は仕様を読むのが正確でてっとりばやい(和訳あるし)

ちょい読んでみましょう

続きはリファレンスで (2)

DOM 的な部分は Mozilla Developer Center がよくまとまっている(部分的に和訳ある)

DOM も仕様を読むのは参考になる (和訳あり)

イベント

並列性

  • JS に並列性はない・組み込みのスレッドはない
  • 同時に処理されるコードは常に1つ
    • 1つのコードが実行中だと他の処理は全て止まるということ
  • 多くのブラウザはループ中スクロールさえできない
    • Opera 10.60 では止まらないようになっている

非同期プログラミング

待たない/待てないプログラミングのこと

  • 待てないのでコールバックを渡す
  • 待てないのでイベントを設定する (方法としてはコールバック)
  • 待たないので他の処理を実行できる

イベント

JS で重要なのは「イベント」の処理方法です。

JS では非同期プログラミングをしなければなりません。

イベントドリブン

  • JS ではブラウザからのイベントをハンドリングします
メリット
  • 同時に2つのコードが実行されないので同期とかがいりません
    • 変数代入で変に悩まなくてよい
  • イベントが発火するまで JS レベルでは一切 CPU を食わない
デメリット
  • 1つ1つの処理を最小限にしないと全部止まります
    • JS関係は全て止まります
    • ブラウザUIまで止まることが多いです
  • コールバックを多用するので場合によっては読みにくい
    • あっちいったりこっちいったり

イベントの例

  • setTimeout(callback, time)
    • 一定時間後にコールバックをよばせる
    • (非同期。DOM Events ではない)
  • element.addEventListener(eventName, callback, useCapture)
    • あるイベントに対してコールバックを設定する
    • イベントはあらかじめ定められている

setTimeout

setTimeout(function () {
    alert('2');
}, 100);
alert('1');

addEventListener

document.body.addEventListener('click', function (e) {
    alert('clicked!');
}, false);

イベントの例

  • mousedown
  • mousemove
  • mouseup
  • click
  • dblclick
  • load

などなど。いっぱいあります。http://esw.w3.org/List_of_events

load イベントについて

  • DOM の構築
  • 画像のロード

などが終わった直後に発生するイベント

基本的に load イベントが発生しないと、要素に触れません。

window.addEventListener('load', function (e) {
   alert('load');
}, false);

みたいに書くのが普通

イベントオブジェクトの構成要素

document.body.addEventListener('click', function (e) {
    alert(e.target);
}, false);

コールバックの渡されるオブジェクト

質問

  • 非同期プログラミンング
  • 並列性なし
  • イベントドリブン
  • イベントオブジェクト

XMLHttpRequest

XMLHttpRequest

  • 所謂 AJAX というやつのキモ
  • JS から HTTP リクエストを出せる

生 XMLHttpRequest の使いかた

var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/foo', true);
req.onreadystatechange = function (e) {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      alert(xhr.responseText);
    } else {
      alert('error');
    }
  }
};
req.send(null);

通常どんなJSフレームワークもラッパーが実装されてます

とはいえ一回は生で使ってみましょう

XMLHttpRequest での POST

POST するリクエストbodyを自力で 作ります

var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/foo', true);
req.onreadystatechange = function (e) { };
var params = { foo : 'bar', baz : 'Hello World' };
var data = ''
for (var name in params) if (params.hasOwnProperty(name)) {
  data += encodeURIComponent(name) + "=" + encodeURIComponent(params[name]) + "&";
}
// data //=> 'foo=bar&baz=Hello%20World&'

req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.send(data);

みたいなのが普通。multipartも送れるけどまず使わない

JSON をリモートから読みこむ

ブラウザはやってくれないので自分で eval します

var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/status.json', true);
req.onreadystatechange = function (e) {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      var json = eval('(' + xhr.responseText + ')');
    } else {
      alert('error');
    }
  }
};
req.send(null);

質問

  • XMLHttpRequest
ハマりポイント
  • http 経由じゃないと XHR うまくうごかない

Ten.js

Ten.js

  • はてな社内JSフレームワーク
  • Ajax のクロスブラウザ
  • Event のクロスブラウザ
  • クラス指向プログラミング

HatenaStar.js にも含まれています。

つまりスターが表示されているページではどこでもこのフレームワークを使うことができる

Ten.js - クラス

var Foo = new Ten.Class({
  initialize: function (foo) {
    // constructor
    this.foo = foo;
  }
}, {
  message : function () {
    // instance method
    alert(this.foo)
  }
});

Ten.js - イベントハンドリング

new Ten.Observer(document.body, 'click', function (e) {
   e.stop();
});
new Ten.XHR('/api/foo', {}, function (res) {
});

Ten.js - Observer

var obj = {};
Ten.EventDispatcher.implementEventDispatcher(obj);
obj.addEventListener('complete', function () { });

obj.dispatchEvent('complete');

これは userscript とかで非常に便利です。

Ten.js - selector

var foo = Ten.querySelector('.foo');

モダンブラウザの document.querySelector が IE でも動く版

一部のセレクタだけの対応なので気を付けよう

質問

  • Ten.js

課題1 (3.5点)

  • TODO 管理の完了・未完了を画面遷移なしでできるようにせよ
    • DOM を理解する
    • XHR を使える
    • イベントを理解する (click, load)
ヒント

手順

  1. 各タスクを囲う要素に対しイベントを設定 (loadイベントで)
    1. task_id どうにかして取得
  2. サーバサイドにAPIを作る (/api/status?id=..) みたいな
    1. task_id を受け取って status を変更する
  3. XMLHttpRequest でAPIを叩く (POST)
  4. 表示を更新する
    1. createElement, removeChild, appendChild...
配点
  • 動くこと (1.5)
  • 設計 (1)
  • UIへの配慮 (1)

課題2 (3.5点)

タイマーを管理する Timer クラスをつくれ。

  • コールバックを概念を理解する
仕様
new Timer(time);
    //=> time ミリ秒のタイマーを作る
timer.addEventListener(name, callback);
    //=> タイマーが完了したときに呼ばれる関数を追加する (何度も呼べる。全て呼ばれる)
    //=>  name : String => 'timer' を指定する
    //=>  callback : Function => タイマーが完了したときに呼ばれる関数
timer.start();
    //=> タイマーをスタートさせる。
    //=> start() してからコンストラクタに指定したミリ秒後に addEventListener に指定したコールバックが呼ばれる
timer.stop();
    //=> タイマーをストップさせる。
var timer = new Timer(1000);
timer.addEventListener('timer', function (e) {
  alert(e.realElapsed); //=> 実際に経過した時間
  timer.start(); //=> 再度スタートできる
});
timer.start();

document.body.addEventListener('click', function () {
  timer.stop(); //=> クリックで止まる。
}, false);

以上のようなインターフェイスの Timer クラスを作れ。(ライブラリを使わずに)

ヒント
  1. Timer の addEventListener は自分で実装しろということです (DOM のメソッドではない)
  2. setTimeout() を使うことになると思います
配点
  • 動くこと (1.5)
  • 設計 (1)
  • removeEventListener を実装 (1)

課題3 (3点)

  • JavaScript のシンタックスハイライターを JavaScript で作れ
    • JS のトークナイザーを理解する
    • DocumentFragment を理解する
    • JS で正規表現を使ってみる
    • 重い処理がどうなるか見てみる
<pre class="code">
var foo = function () {
    alert('Hello');
};
</pre>

を以下のように

<pre class="code">
<span class="keyword">var</span> foo <span class="operator">=</span> <span class="function">function</span> () {
    alert(<span class="literal">'Hello'</span>);
};
</pre>
  1. code クラスがついた pre 要素をハイライトさせてみよう
  2. innerHTML 禁止で
  3. DocumentFragment 使ってください
  4. http://s.hatena.ne.jp/js/HatenaStar.js をハイライトさせてみよう
ヒント

ハイライトのスタイル書くのがめんどうだと思うので以下のをつかってもいいです

pre.code {
	color: #666666;
	background: #efefef;
}

pre.code .comment {
	color: #507c6f;
}

pre.code .string {
	color: #968258;
}

pre.code .number {
	color: #24c3b5;
}

pre.code .identifer {
	color: #333333;
}

pre.code .symbol {
	color: #479cdb;
}

pre.code .variable {
	color: #479cdb;
}

pre.code .constant {
	color: #479cdb;
}

pre.code .special_variable {
	color: #14779d;
}

pre.code .keyword {
	font-weight: bold;
	color: #4e7585;
}

pre.code .literal {
	color: #968258;
}

pre.code .operator {
	color: #29845a;
}
配点
  • 動くこと (1)
  • 設計 (1)
  • 速度に配慮すること (1)

課題4 (オマケ)

1 と 2 Ten.js の Ten.Observer および、Ten.Class を使い構造化プログラミングせよ。

もし後半で「自分はJSを書きそうだなあ」と思ったらやってみてください。配点がないので3までしっかりやってください。

配点
  • 0