- JavaScript の言語について
- DOM について
- イベントについて
- jQuery について
- MVC アーキテクチャについて
- Web で JS を使うとき、HTML の知識が前提となることが多い
- http://www.kanzaki.com/docs/htminfo.html * 少なくとも「簡単なHTMLの説明」は押さえておきたい。
講義時間は限られているので
- JS を学ぶ上でとっかかりをつかめること
- リファレンスを提示します。概念を理解
- 言語的部分、DOM 及びイベントドリブンなプログラミング
覚えようとするとクソ多いのでリファレンスひける部分は覚えない
- 基本クライアントサイド=ブラウザで動く
- 実装がたくさんある
- はてなのエンジニアはみんな誰もがある程度書けます
- ウェブアプリを書く上で必須なため
- フロントエンドの重要性
-
デバッガ
-
Firefox なら Firebug、Google Chrome ならデベロッパー ツール
-
スクリプトを中断する場所 (ブレークポイント) を指定でき、そこから1行ずつ実行できる
-
スクリプト中に
debugger;
と書いておけばそこで中断する -
console.log()
-
ブロックせずにコンソール領域へ直接出力
-
他言語の print デバッグに相当する
-
最近のブラウザでは標準でサポートされつつある
-
document.title
-
view-source:http://ma.la/files/shibuya.js/dec.html
-
console.log()
が一般的でなかったころ -
alert()
-
古典的だが今でも役に立つこともある
-
その時点で処理がブロックするので、ステップをひとつずつ確認するとき便利
-
これらを仕込んで、リロードしまくることで開発します
-
エラーコンソールをどうやって開くかからはじめましょう
ここでクロスブラウザについて覚えても仕方ないので、Firefox と Firebug で開発することを前提にします。
- 変数に型なし
- プリミティブな型 + オブジェクティブな型
- ただしプリミティブ型も自動的にオブジェクティブ型へ変換される
- 関数はデータとして扱える (オブジェクト)
- 文法は C に似てる
- 言語的な部分はECMAScript として標準化されている
- ActionScript も同じ。AS やったことあるなら JS は DOM さえ覚えれば良い
- プロトタイプ指向なためクラスというものはない
- クラス指向を模倣することはできる
ひとつずつ説明していきます
Perl と一緒で、Java や C などと違う点
var foo = "";
foo = 1;
foo = {};
文字列を入れた変数にあとから数値を入れたりオブジェクトを入れられる
値自体には当然ちゃんと型があります
undefined
null
number
boolean
string
object
({})
[]
number
, boolean
, string
とかはプリミティブ値と呼ばれます
オブジェクト以外はプリミティブです。
typeof
演算子で調べることができます
typeof undefined;
typeof 0;
typeof true;
typeof {};
typeof [];
typeof null;
typeof "";
typeof new String("");
typeof alert;
それぞれどれになるでしょう?
"undefined"
, "number"
, "boolean"
, "string"
, "object"
typeof undefined //=> "undefined"
typeof 0; //=> "number"
typeof true; //=> "boolean"
typeof ""; //=> "string"
typeof {}; //=> "object"
typeof []; //=> "object"
typeof null; //=> "object"
typeof new String(""); //=> "object"
typeof alert; //=>"object"
Array
やRegExp
などstring
,number
をラップするString
,Number
Function
プリミティブ型からオブジェクト型へは自動変換がかかります (null
, undefined
以外)
"foobar".toLowerCase();
としたとき、"foobar"
は string
プリミティブであるにも関わらず、メソッドを呼べるのはメソッドを呼ぼうとしたときに自動的に String
オブジェクトへ変換されるからです。
new String("foobar").toLowerCase();
とはいえ大抵はオブジェクトへの変換を気にする必要はありません。
そもそもオブジェクト型って何かというと、名前と値のセット (プロパティ) を複数持ったものです。
連想配列とかハッシュとか言えばだいたい想像できると思います。
var obj = {}; // オブジェクトリテラル記法。new Object() と同じ
obj['foo'] = 'bar'; // foo という名前に 'bar' という値をセットしている。
obj.foo = 'bar'; // これも上と全く同じ意味
var obj = {
foo : 'bar'
};
配列もJSではオブジェクトとして扱えます。
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();
JS では関数もデータになれ、Function
オブジェクトのインスタンス扱いです。(第一級のオブジェクトというやつです)
変数に代入でき、引数として関数を渡すことが可能です。
var fun = function (callback) {
alert('1');
callback();
alert('3');
};
fun(function () {
alert('2');
});
function foobar() {
}
と
var foobar = function () {
};
はほぼ同じです (呼べるようになるタイミングが違います)
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 のように個々の要素がほぼ同じだけど微妙に違う場合に便利
- 自由すぎる
プロトタイプにしたいオブジェクトに初期化関数を組み合せることでオブジェクトをクローンできる
function Foo() { }
Foo.prototype = {
foo : function () { alert('hello!') }
};
var instanceOfFoo = new Foo();
instanceOfFoo.foo(); //=> 'hello!'
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
という暗黙的に渡される引数のようなものがあります- Perl の
$self
みたいなやつです - 普通はレシーバーが渡されます
foo.bar()
のfoo
のことをレシーバといいます- 明示的に渡せすこともできる (
apply
,call
)
var a = { foo : function () { alert( a === this ) } };
a.foo(); //=> true
a.foo.call({}); //=> false
- HTML の
script
要素で
<!DOCTYPE html>
<title>JS test</title>
<script type="text/javascript" src="script.js"></script>
質問など
- 変数に型なし
- 関数はデータとして扱える (オブジェクト)
- プロトタイプ指向
- 文法的に
- そもそも?
(あとで書いてみると疑問になることが多いと思うので聞いてください)
- 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 で標準化されている
一番上にはドキュメントノード (文書ノード)
Node
- 全ての DOM の構成要素のベースクラス
Element
- HTML の要素を表現する
Attr
- HTML の属性を表現する
Text
- HTML の地のテキストを表現する
Document
- HTML のドキュメントを表現する
DocumentFragment
- 文書木に属さない木の根を表現する
Text
も Attribute
も Node
のうちです。これらがツリー構造 (文書木) になっています。
-
document.createElement('div')
-
要素ノードをつくる
-
document.createTextNode('text')
-
テキストノードをつくる
-
element.appendChild(node)
-
要素に子ノードを追加する
-
element.removeChild(node)
-
要素の子ノードを削除する
-
element.getElementsByTagName('div')
-
指定した名前をもつ要素を列挙
-
document.getElementById('foo')
-
指定したIDをもつ要素を取得
-
node.cloneNode(true);
-
指定したノードを子孫ノード込みで複製
<div id="container"></div>
var elementNode = document.createElement('div');
var textNode = document.createTextNode('foobar');
elementNode.appendChild(textNode);
var containerNode = document.getElementById('container');
containerNode.appendChild(elementNode);
↓
<div id="container"><div>foobar</div></div>
- ブラウザの画面に表示されるのは文書木に属するノード
- ノードを作った段階では、そのノードはまだ文書木に属していない
<div id="sample">
<span>foobar</span>
</div>
alert(document.getElementById('sample').firstChild); //=> ?
言語的な部分は仕様を読むのが正確でてっとりばやい(和訳あるし)
ちょい読んでみましょう
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(function () {
alert('2');
}, 100);
alert('1');
https://developer.mozilla.org/ja/DOM/window.setTimeout
document.body.addEventListener('click', function (e) {
alert('clicked!');
}, false);
https://developer.mozilla.org/ja/DOM/element.addEventListener
mousedown
mousemove
mouseup
click
dblclick
load
などなど。いっぱいあります。http://esw.w3.org/List_of_events
<p id="outer">Hello, <span id="inner">world</span>!</p>
inner
をクリックしたというのは、outer
をクリックしたということでもある- イベントは実際に発生したノードから親に向かって浮上 (バブル) していく
- バブルしないイベントもある (
focus
、load
、etc.)
http://www.w3.org/TR/2011/WD-DOM-Level-3-Events-20110531/
- DOM の構築
- 画像のロード
などが終わった直後に発生するイベント
基本的に load
イベントが発生しないと、要素に触れません。
window.addEventListener('load', function (e) {
alert('load');
}, false);
みたいに書くのが普通
document.body.addEventListener('click', function (e) {
alert(e.target);
}, false);
コールバックに渡されるオブジェクト
target
: イベントのターゲット (クリックされた要素)clientX
,clientY
: クリックされた場所の座標stopPropagation()
: イベントの伝播 (含むバブリング) をとめるpreventDefault()
: イベントのデフォルトアクションをキャンセルする- デフォルトアクション : リンクのクリックイベントなら、「リンク先のページへ移動」
https://developer.mozilla.org/en/DOM/event をみるといいです
function Notifier(element, message) {
this.message = message;
var self = this;
element.addEventListener('click', function (event) {
self.notify();
}, false);
}
Notifier.prototype.notify = function () {
alert(this.message);
};
new Notifier(document.body, 'Clicked!');
addEventListener('click', this.notify, false)
ではnotify
中のthis
が何を指すかわからない- 最近のブラウザなら
this.notify.bind(this)
とも書ける
- 非同期プログラミンング
- 並列性なし
- イベントドリブン
- イベントオブジェクト
- 所謂 AJAX というやつのキモ
- JS から HTTP リクエストを出せる
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/foo', true);
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText);
} else {
alert('error');
}
}
};
xhr.send(null);
通常どんなJSフレームワークもラッパーが実装されてます
とはいえ一回は生で使ってみましょう
POST するリクエスト body を自力で 作ります
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/foo', true);
xhr.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&'
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);
みたいなのが普通。multipartも送れるけどまず使わない
- JSON : オブジェクトのシリアライズ形式の一種。JS のオブジェクトリテラル表記と一部互換性がある
最近のブラウザなら
JSON
オブジェクトがありますが、古いブラウザにも対応するときは自分でeval
します https://developer.mozilla.org/ja/Using_native_JSON
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/status.json', true);
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var json = eval('(' + xhr.responseText + ')');
} else {
alert('error');
}
}
};
xhr.send(null);
XMLHttpRequest
- http 経由じゃないと XHR うまくうごかない
- 世界的によく使われているライブラリ
- はてなでも採用事例が増えてきている
- 書き方がちょっと独特
// ページが読み込まれたときに
$(function ($) {
// 文書中のすべての p 要素の背景色と文字色を変える
$('p').css({ backgroundColor: '#ff0', color: '#000' });
});
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<!-- 開発中はこちらのほうがデバッグが楽かも? -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
jQuery === $ //=> どちらも同じ jQuery 関数を指す
$(function ($) { ... });
//=> 文書読み込み完了時に関数を実行
$('css selector');
//=> CSS セレクタで要素を選択し、
// それらの要素が含まれる jQuery オブジェクトを作成
$('<p>HTML fragment</p>');
//=> HTML 要素を内容込みで作成し、
// その要素が含まれる jQuery オブジェクトを作成
$('.foo').on('click', function (event) { ... });
//=> foo クラスを持つ要素の click イベントを指定
//=> イベントを登録する要素は実行時点で存在したもののみ
$('.foo').click(function (event) { ... });
//=> 上と同じ
$(document).on('click', '.foo', function (event) { ... });
//=> foo クラスを持つ要素の click イベントを指定
//=> 実行時点で存在したかに関わらず、文書中のすべての
// foo クラスを持つ要素の click イベントを監視
$.get(url, { foo: 42 }).done(function (res) {
alert(res);
});
$.post(url, { foo: 42 }).done(function (res) {
alert(res);
});
$.ajax({ url: url, ... });
- jQuery
- Model-View-Controller
- これまでの課題でやった Web アプリケーションの MVC とはちょっと違う
- 「Web アプリケーションの MVC」を「MVC2」と呼ぶこともある
- Web API (HTTP リクエストを送り、テキストや JSON で結果を受け取るような API) をそのまま Model として使うこともある
- 規模が小さい場合、View と Controller は一緒くたに実装することもある
/api/time.json
にアクセスすると{ "time": "2012-04-01 12:00:00" }
というJSON 形式で現在時刻を返す API があるとする- ボタンを押すと時刻表示を更新するようなウィジェットを作る
<script>
var TimeWidget = function (button, output) {
this.button = $(button);
this.output = $(output);
this.button.click(this.onClick.bind(this));
};
TimeWidget.prototype = {
onClick: function (event) {
$.get('/api/time.json').done(this.onGetTime.bind(this));
},
onGetTime: function (res) {
this.output.text(res.time);
}
};
$(function () {
new TimeWidget($('#time-update-button'), $('#time-output'));
});
</script>
<p>
<input id="time-update-button" type="button" value="表示を更新">
<span id="time-output"></span>
</p>
- jQuery を使えば大抵のオブジェクト上で observer パターンを実装できる
<script>
// Model に相当
function Toggler() {
this.enabled = true;
}
Toggler.prototype.toggle = function () {
this.enabled = !this.enabled;
// jQuery の機能を使って自身の状態が変化したことを通知する
$(this).triggerHandler('update');
};
// View-Controller に相当
function ToggleDisplay(toggler, output, messages) {
this.toggler = toggler;
this.output = output;
this.messages = messages;
// jQuery の機能を使って Model の状態を監視する
$(this.toggler).on('update', this.onUpdate.bind(this));
}
ToggleDisplay.prototype.onUpdate = function () {
this.output.text(this.messages[this.toggler.enabled ? 0 : 1]);
};
$(function () {
var toggler = new Toggler();
new ToggleDisplay(toggler, $('#display-ja'), ['有効', '無効']);
new ToggleDisplay(toggler, $('#display-en'), ['Enabled', 'Disabled']);
setInterval(function () {
toggler.toggle();
}, 1000);
});
</script>
<p id="display-ja" lang="ja"></p>
<p id="display-en" lang="en"></p>
- 1 秒ごとに「有効」「無効」(または "Enabled", "Disabled") の表示が切り替わる
- MVC アーキテクチャ
- ページ継ぎ足し機構を作れ (ダイアリー、グループにあるようなもの)
- DOM を理解する
- XHR を使える
- イベントを理解する (
click
,load
) jQuery、Ten といったフレームワークを使ってもよい
手順
- 継ぎ足しを実行するトリガーとなる要素にイベントを設定 ++ 次のページの識別子をどうにかして取得
- サーバサイドに API を作る (
/api/page?id=...
) みたいな ++ ページの識別子を受け取ってそのページの内容を返す XMLHttpRequest
で API を叩く- 表示を更新する
++
createElement
,removeChild
,appendChild
...
スクリプトファイル置き場
static/js/diary.js
などに JS ファイルを設置すると、- HTML からは
<script type="text/javsacript" src="/js/diary.js"></script>
でその JS ファイルを参照できます
- 動くこと (1.5)
- 設計 (1)
- UIへの配慮 (1)
タイマーを管理する Timer
クラスをつくれ。
- コールバックの概念を理解する jQuery、Ten といったフレームワークを使ってはいけない
var timer = new Timer(time);
//=> time ミリ秒のタイマーを作る
timer.addListener(callback1);
//=> タイマーが完了したときに呼ばれる関数を追加する
//=> callback : Function => タイマーが完了したときに呼ばれる関数
timer.addListener(callback2);
//=> タイマーが完了したときに呼ばれる関数は、複数指定できる
timer.start();
//=> タイマーをスタートさせる。
//=> start() してからコンストラクタに指定したミリ秒後に addListener に指定したコールバックが呼ばれる
timer.stop();
//=> タイマーをストップさせる。
var timer = new Timer(1000);
timer.addListener(function (e) {
alert(e.realElapsed); //=> start() 時から実際に経過した時間
timer.start(); //=> 再度スタートできる
});
timer.start();
document.body.addEventListener('click', function () {
timer.stop(); //=> クリックで止まる。
}, false);
以上のようなインターフェイスの Timer
クラスを作れ。(ライブラリを使わずに)
Timer
のaddListener
は自分で実装しろということです (DOM のメソッドではない)setTimeout()
を使うことになると思います
- 動くこと (1.5)
- 設計 (1)
removeListener
を実装 (1)
- その場編集機能を作れ
- 要素の作成、追加、削除
- こまめなフィードバックでストレスを感じさせないように
- その場編集用 API の設計 jQuery、Ten といったフレームワークを使ってもよい
- 動くこと (1)
- 設計 (1)
- UIへの配慮 (1)