開発環境
選択肢はいくつか考えられるが、今回はVisual Studio 2015 Enterpriseをchoice。
理由をいくつか挙げると、
- テキストエディターだと入力補完がないからきつい(半分ゆとり世代の血がながれている?)
- IE開発者ツール、FirebugでもDebugは可能だが、使い勝手が今だに馴染めない
- EclipseだとChutzpah()が使えない ()JavaScriptのテストランナーによるテスト結果をIDE上に表示するツール
- Web Essentialsが使える
Chutzpahは必須じゃないけど、あればやっぱり便利なツール。Eclipseでも利用可能だが、静的解析ツールのJsLintは導入。Web EssentialsはWeb開発に関する様々な便利機能が盛り込まれていて、到底一言では語り尽くせない。とりあえず静的解析はやってくれるらしい。2015ではLESSやCoffeeScriptのコンパイル、Gulp/Gruntなどのタスクランナー連携ツールは外出しされた用でレビュー評価はイマイチだったが、とりあえず便利そうなので導入してみる。
Visual Studioの拡張機能はここからダウンロードすることが可能。ダウンロードするとVSIX形式のファイルになっているので、ダブルクリックするとインストーラーが走りだす仕組み。
JavaScriptの記述方法
- <script>タグでHTMLファイルに組み込む
<script type="text/javascript">
<!--
JavaScriptのコード
//-->
</script>
- 外部ファイルに書き込む
<script type="text/javascript" src="ファイル名" [charset="文字コード"]></script>
- タグを記載する位置
- bodyタグの配下(任意の位置)
- bodyタグの配下(/bodyタグの直前)
- headタグの配下
JavaScriptで関数の作成
定義方法
- ① function命令で定義
もっとも基本的なアプローチ方法。
function 関数名([引数1[,引数2...]]){
// 関数定義
[return 戻り値]
}
- ② Functionコンストラクタ経由で定義
JavaScriptの組み込みオブジェクトであるFunctionオブジェクトのコンストラクタを使うパターン。①と違い、引数や関数本体を文字列として定義することが可能だが、を特別な理由がない限りeval関数と同じように濫用すべきではない。
var 変数名 = new Function([引数1[,引数2...]], 関数定義);
eval関数
与えられた文字列をJavaScriptとして評価・実行する仕組み。
一見すると便利で使い勝手が良さそうだが、第3者が自由に任意のスクリプトを実行できるリスクがあること、処理速度が遅いなどのデメリットがあるため、eval関数の使用は避けるべき。
- ③ 関数リテラル表現で定義(匿名関数、無名関数) 関数を変数に代入したり、ある関数の引数として渡したり、あるいは戻り値として関数を返すやり方。これによって、JavaScriptは柔軟なコーディングが可能になっている。
// 関数の定義
var 変数名 = function 関数名([引数1[,引数2...]]){
// 関数定義
[return 戻り値]
}
// 関数を利用する
document.writeln(関数名('test'));
関数定義における注意点
① return命令は途中で改行しない
JavaScriptは文末のセミコロンが必須ではない(良い感じに処理してしまうため、実行できてしまう)ため、途中で改行があると意図した結果が得られない可能性がある。break、continueについても同じ。
② 関数はデータ型の一種
JavaScriptでは関数はデータ型の一種であるため、以下のようなコードの記述も可能になっている。
// 関数リテラルの定義
var triangle = function(base, height){
return base * height/2;
}
document.writeln(triangle(5,2)); // 関数呼び出し
triangle = 0; // 一見すると関数に値を代入しているので間違っているように見えるが、
document.writeln(triangle); // Javascriptではこの書き方が許されている
ちなみに、関数を引数、戻り値として扱う関数は高階関数と呼ばれる。
fucntion arrayWalk(data, f){
for(var key in data){
// 匿名関数の呼び出し
f(key, data[key]);
}
}
var ary = [1,2,4,8,16];
arrayWalk(
ary,
function(key, value){ // 引数に関数を定義(匿名関数)
document.writeln(key + ':' + value);
}
)
③ function命令は静的な構造を宣言する(コンパイル時に評価する)
function命令による関数定義は、いわゆる代入演算子(=)による変数への代入とは異なり、静的な構造を宣言する。
document.writenln(triangle(5,2));
function triangle(base, height){
return base * height/2;
}
上述の例の場合、通常の変数なら1行目でエラーになるが、実際には上のコードはエラーにはならない。これはfunction命令がコンパイル時に関数を登録していることに他ならない。ただし、関数を定義した<script>タグは呼び出し側のブロックと同じかそれより前に記述しなければならないことに注意する。
④ 関数リテラル/Functionコンストラクタは実行時に評価される
上のソースコードを関数リテラル、もしくはFunctionコンストラクタで記述するとエラーになる。これは、関数リテラル、Functionコンストラクタが実行時に評価されるため。
スコープ
変数のスコープにはグローバルとローカルの2つがある。
グローバル変数・・・関数外で定義した変数、もしくは「Var」キーワードなしに宣言した変数
ローカル変数・・・関数内で定義した変数
argumentsオブジェクト
JavaScriptにおける引数の特徴
引数の数を気にしない
- 引数の数をチェックしない。例えば引数を1つ受け取る関数に対して引数を2つ渡しても、2つ目の引数は無視する
- 上述ので引数を0個にしても呼び出しは可能。
- ただし、大抵の場合はエラーになるため、if文を使って変数がundefinedの場合は既定値を代入するなどの対処を施したりもする
- 引数の数をチェックする場合は、argumentsオブジェクトのlengthプロパティで引数の数を取得することが可能
再帰呼び出しを定義するcalleeプロパティ
- calleeプロパティは、現在実行中の関数自身を参照するためのプロパティ。
function factorial(n){
// 自然数nの階乗が「n*(n-1)」で求められる
if(n != 0){return n * arguments.callee(n-1);}
return 1;
}
document.writeln(factorial(5));
スコープチェーン
JavaScriptはスクリプト実行時に内部的にGlobalオブジェクトを(実装者はあまり意識していないが)生成し、グローバル関数やグローバル変数を管理する。そしてローカル変数もまた、Activationオブジェクト(通称Callオブジェクト)のプロパティになる。
スコープチェーンとは、グローバルオブジェクト、Callオブジェクトを生成順に連結したリストのことを指し、JavaScriptでは先頭(内部)から順にプロパティを検索して見つかった値を採用する。つまり、重複する変数名が存在する場合は(1)内部のCallオブジェクト、(2)外部のCallオブジェクト、(3)グローバルオブジェクトの順で解決することになる。
クロージャ ~その振る舞い、オブジェクトの如し~
一言で言うと「ローカル変数を参照している関数内関数」のこと。
function closure(init){
var counter = init;
// 戻り値に関数を定義(匿名関数)
return fucntion(){
return ++counter;
}
}
var myClosure = closure(1);
// 「2」が出力される
document.writeln(myClosure());
// 「3」が出力される
document.writeln(myClosure());
// 「4」が出力される
document.writeln(myClosure());
通常、関数内で使われたローカル変数は関数の処理が終了した時点で破棄されるが、上記の例ではclosure関数から返された匿名関数がローカル変数counterを参照し続けているため、closure関数終了後もcounterの値は保持される。ある種、メモリ上に一時データを保持したような状態になる。
大規模開発でも通用する書き方
JavaScriptにおけるオブジェクト指向の特徴
クラスではなくプロトタイプ
JavaScriptはれっきとしたオブジェクト指向だが、JavaやC#と異なる点もある。インスタンス化という概念はあるが、クラスという概念がないことだ。代わりにJavaScriptではプロトタイプ(雛形)という概念が存在する。
プロトタイプとは「あるオブジェクトの元になるオブジェクト」の事で、JavaScriptではこれを利用してオブジェクトを生成していく。
// JavaScriptでは関数(Functionオブジェクト)にクラスとしての役割を与えている
var Member = function(name, age){ // 一般的には名前は普通の関数と区別するために大文字で始める
this.name = name;
this.age = age;
this.getName = function(){
return this.name + ':' + this.age;
}
};
var mem = new Member('okano', 32);
// JavaScriptには厳密なメソッドという概念はなく、値が関数のプロパティがメソッドとしてみなされる
document.writeln(mem.getName()); // okano:32
// 動的なメソッドの追加も可能。ただし、別で生成したオブジェクトには反映されないので注意
mem.getAge = function(){
return this.age;
}
コンストラクタの問題点とプロトタイプ
前節でコンストラクタにメソッドを定義する方法を紹介したが、実はコンストラクタによるメソッドの追加には、メソッドの数に比例してメモリを消費するという大きな問題がある(インスタンスを生成する度に、それぞれのインスタンスメモリを確保してしまう)。
そこで、JavaScriptではオブジェクトにメンバを追加するためにprototypeというプロパティを用意している。このprototypeメンバに追加されたメンバは、そのコンストラクタを元に生成されたすべてのインスタンスから利用することができるようになる(各インスタンスは、プロトタイプへの参照を保持するようになる)。
prototypeを利用するメリットは主に2つ。
- メモリの使用量を削減できること
- メンバの追加や変更をインスタンスがリアルタイムにできること
前節で紹介した動的メソッドの追加は、他のインスタンスからはメソッドを参照することができない。それに対し、prototypeへのメンバ追加は、リアルタイムで他のインスタンスから参照することができるようになる。
プロトタイプオブジェクトの不思議
- プロトタイプが利用されるのは値の参照時のみ。あるインスタンスで値を変えても、他のインスタンスには反映されない。
静的プロパティ/静的メソッド
- 静的プロパティ/静的メソッドは、インスタンスを生成しなくてもオブジェクトから直接呼び出せるプロパティ/メソッド
- 基本的にグローバル変数/関数は名前が競合するため、関連する機能や情報は静的メンバにまとめることを心掛ける
- 静的プロパティ/静的メソッドを定義する場合は、プロトタイプオブジェクトに登録できないので注意
- 静的プロパティはインスタンスプロパティとは異なり、全てのインスタンスで共有される。基本的に読み取り専用項目でのみ使用する
- 静的メソッド内ではthisキーワードは解決できないので使用することができない
オブジェクト名.プロパティ名 = 値
オブジェクト名.メソッド名 = function(){ /* メソッドの定義 */}
オブジェクトの継承 ~プロトタイプチェーン~
var Animal = function(){};
Animal.prototype = {
walk: function(){document.writeln('piyopiyo');}
};
var Dog = function(){};
Dog.prototype = new Aminal(); // ここで継承を実施
Dog.prototype.bark = function(){document.writeln('hogehoge');} // メソッドの追加。もちろん、基底クラスのメソッドを派生クラスでオーバーライドすることも可能。
var d = new Dog();
d.walk(); // piyopiyo
d.bark(); // hogehoge
JavaやC++などと異なり、継承関係は動的に変更が可能。プロトタイプチェーンはインスタンスが生成された固定され、その後の変更に関わらず保存される。