[[PageOutline]]
= !JavaScript 言語概論 =
== このテキストは何? ==
ここでは、 !JavaScript のプログラミング言語としての使用、特徴を学びます。
* !JavaScript ってそもそも何? どんなことができるの? という人は[wiki:HowTo/JavaScriptTutorial JavaScript チュートリアル]を、
* !JavaScript に最初から用意されているクラスとかを使いこなしたい! という人は[wiki:HowTo/CoreJavaScriptIntroduction Core JavaScript イントロダクション]を、
* !JavaScript でリッチなグリグリ動く GUI を作りたい! という人は[wiki:HowTo/DomIntroduction DOM 概論]を、
* !JavaScript でもフリーのライブラリを利用して楽をしたい! という人は[wiki:HowTo/JavaScriptLibraryIntroduction JavaScript ライブラリ概論]を、
それぞれ参照してください。
== どんな言語? ==
!JavaScript 言語の大まかな特徴を、ざっと列挙してみましょう。
=== 比較的小さな実装のスクリプト言語 ===
Web ブラウザの機能の一つとして実装される !JavaScript は、その為に非常に軽量に実装されています。つまり、言語自体が元々持つ機能が少ない、ということです。例えば、ローカルファイルへの入出力を行う命令などは用意されていません。
また、スクリプト言語なので、処理系はプログラムをステップ毎に逐次読み込みながら実行します。最近は JIT コンパイラ (内部でコンパイルを行ってから実行するタイプ) の処理系が多いので、基本的な構文チェックだけを先に通してから実行される、という動作も多くなってきていますが、基本的にはエラーが発生する前の部分までは実行される、といった動作になります。
=== 文法は C や Java にやや似ている ===
!JavaScript という名前の通り、言語の文法は Java に似ていなくもありません。が、値の型が曖昧な点や、インタプリタとして動作する点などを理由に、初期の頃は C に似た書き方の BASIC みたいな言語、といった見られ方をしていました。
近年、 !JavaScript の持つ言語としての特徴が再評価され、むしろ C に似た書き方の LISP みたいな言語、といった見られ方に移りつつあるようです。
文法が C や Java に似ていると言われる所以は何か。まず、処理の単位となるブロックをブレース "{" ~ "}" で括る書き方。
{{{
function hoge() {
// 処理...
}
if (a == b) {
// 処理...
}
}}}
それから、処理の単位をセミコロン ";" で区切る書き方 (但し、行末のセミコロンは必須ではない)。
{{{
var a = 10; var b = a + 20;
// 行末のセミコロンは省略可
var a = 10
var b = a + 20
}}}
コメントの書き方も Java と同じです。 "/*" ~ "*/" で括るか、 "//" の後ろに書きます。
{{{
/*
* 複数行
* コメント
*/
alert("コメントサンプル"); // 一行コメント
}}}
その他、 if, for, while, switch といった処理の流れを記述するおなじみの構文や、 C言語風の演算子 (==, ++, -- など)、さらには関数呼び出し時には関数名の後ろに引数をかっこで括る書き方なども、まさに C や Java そっくりの書き方です。
=== 数値と文字列の型は曖昧 ===
多くのスクリプト言語と同様、 !JavaScript もまた、数値と文字列を自由に行き来する言語です。例えば、数値と文字列の足し算は、文字列の連結として解釈されます。
{{{
var a = 10;
var b = 20;
alert("a + b = " + (a + b)); // "a + b = 30" と表示
}}}
また、数字だけの文字列を数値演算に含めることもできます。その場合、足し算においては文字列の連結として誤解されないよう注意が必要です。
{{{
var a = document.getElementById("input_a").value - 0; // 入力欄の文字列を取得。
var b = document.getElementById("input_b").value - 0; // 0 で減算することで、変数には確実に数値として代入される。
document.getElementById("result").value = a + b; // 足し算した結果を別のフォーム部品に表示。
}}}
=== 複雑なデータ構造を自由に書ける === #introduction-hashmap
C や Java がデータ構造の構成を構造体やクラスとしてあらかじめ定義しなければならないのに対し、 !JavaScript のオブジェクトはいつでも好きなだけメンバを追加できます。
{{{
// オブジェクトを作成
var murachi = {
real_name = "村山 俊之",
real_name_kana = "ムラヤマ トシユキ",
birth = new Date(1978, 1, 7),
sex = "Female"
};
// 本名を表示
alert("名前: " + murachi.real_name + " (読み: " + murachi.real_name_kana + ")");
// オブジェクトに後からメンバを追加できる
murachi.disp_name = "T.MURACHI";
murachi.tall = 166;
murachi.weight = 70;
alert(murachi.disp_name + "の身長は" + murachi.tall "cm、体重は" + murachi.weight + "kg");
}}}
もちろん、メンバにオブジェクトや配列を持たせることもできますし、配列にオブジェクトを持たせることもできます。
{{{
var harapeko = {
name = "株式会社はらぺこ",
name_e = "Harapeko Inc.",
birth = new Date(2008, 4, 1),
// オブジェクトの入れ子
address = {
post_no = 2750015,
county = "千葉県",
city = "習志野市",
town = "鷺沼台1-8-27",
building = "マウント・ヴィレッヂⅠ 101"
},
// メンバに配列を追加
address_history = [
// オブジェクトの配列
{
post_no = 2760017,
county = "千葉県",
city = "八千代市",
town = "八千代台北1-10-11",
building = "マメール八千代台 502"
},
{
post_no = 2760017,
county = "千葉県",
city = "八千代市",
town = "八千代台北1-10-11",
building = "ファイン八千代台 502"
}
]
};
// オブジェクトの内容を一通り表示
document.write("
会社名: " + harapeko.name + "
");
document.write("創立: " + harapeko.birth.toLocaleString() + "
");
document.write("現住所: 〒" + harapeko.address.post_no + " " +
harapeko.address.county + harapeko.address.city + harapeko.address.town + " " +
harapeko.building + "
");
document.write("過去の住所 (古い順に):
");
for (var i = 0; i < harapeko.address_history.length; i++) {
var old_address = harapeko.address_history[i];
document.write("- 〒" + old_address.post_no + " " +
old_address.county + old_address.city + old_address.town + " " +
old_address.building + "
");
}
document.write("
");
}}}
=== 割と本格的な関数型言語 ===
!JavaScript では、関数の定義内容を変数に代入することができます。
{{{
// 変数 hoge に関数を代入
var hoge = function(text) {
alert("hoge: " + text);
}
// hoge に代入した関数を呼び出す
hoge("fuga");
}}}
かっこでうまく括ってやれば、わざわざ変数に代入せずとも、その場で呼び出してしまうことも可能です。
{{{
// 無名の関数を即座に呼び出す例
(function(text) {
alert("hoge: " + text);
})("fuga");
}}}
こうして作られた無名関数は、'''クロージャ'''として機能します。つまり、無名関数が生成されたときの変数の値を覚えています[[FootNote(正確には、変数のスコープが失われたときの値を覚えています。この性質は若干ややこしいので、クロージャを用いる際には関数の生成の仕方に注意する必要があります。詳細は後述します。)]]。
{{{
// クロージャを生成する関数
function generateAlertClosure(text) {
// 無名関数を返す
return function() {
alert(text);
};
}
var hoge = generateAlertClosure("ほげっ!");
var fuga = generateAlertClosure("ふんがーヽ(゚口゚ミ)ノ");
alert("まだ何も起こっていないはず…"); // ここまではまだ alert が呼ばれていないことを確認
hoge(); // ここで "ほげっ!" と表示
fuga(); // "ふんがー(ry" と表示
}}}
[[FootNote]]
=== プロトタイプ型オブジェクト指向 ===
!JavaScript でオブジェクトと言えば、複雑なデータ構造を表す'''連想配列'''を指します。「[#introduction-hashmap 複雑なデータ構造を自由に書ける]」の節で示したアレですね。
{{{
// オブジェクト
var obj = {
"foo": "hoge",
"bar": "fuga",
"baz": "oyoyo"
};
}}}
ところで、オブジェクト指向プログラミングといえば、オブジェクトの生成・破棄を行う処理を定義するコンストラクタ・デストラクタがあり、メンバメソッドがあり、カプセル化のためのアクセス制約があり、クラスの継承がある、といったいわゆる Java 的な世界観が連想されます。 !JavaScript の場合はクラスではなく'''プロトタイプ'''というアプローチではありますが、似たようなことを行うための記法が用意されています。
まず、関数の定義はそのままコンストラクタとしても機能します。
{{{
// クラス定義のようなもの
function MyObject() {
// メンバ変数の定義
this.foo = "hoge";
this.bar = "fuga";
this.baz = "oyoyo";
}
// 上記を用いてオブジェクトインスタンスを生成
var obj = new MyObject();
alert("foo: " + obj.foo); // "foo: hoge" と表示される
}}}
デストラクタは残念ながら存在しません。オブジェクトが必要なくなるタイミングで特定の処理を行いたい場合は、自分でメソッドを用意して、自分で確実に呼び出すよう記述する必要があります。
メンバメソッドはメンバ変数に関数を代入でも良いのですが、通常はプロトタイプのメンバに代入します。
{{{
// クラス定義のようなもの
function MyObject() {
// メンバ変数の定義
this.foo = "hoge";
this.bar = "fuga";
this.baz = "oyoyo";
}
// メンバメソッド定義
MyObject.prototype = {
"show" = function(member) {
if (this[member] == null)
alert(member + "なんてメンバはねーよ!!");
else
alert(member + ": " + this[member]);
},
"showAll" = function() {
var dump = "";
for (var member in this) {
if (dump != "")
dump += "\n";
if (typeof this[member] != "string")
continue;
dump += member + ": " + this[member];
}
alert(dump);
}
}
// 上記を用いてオブジェクトインスタンスを生成
var obj = new MyObject();
obj.show("bar"); // "bar: fuga" と表示
obj.showAll(); // foo, bar, baz すべて表示
}}}
以上が、 Java や C++ などで言うところのクラス定義に相当する、といって良いでしょう。継承を行いたい場合は、新たに定義するコンストラクタの関数名に、プロトタイプの複製をコピーしてあげれば ok です。以下のような記述になります。
{{{
// ダイアログオブジェクトの基本クラス
function Dialog() {}
Dialog.prototype = {
"doModal": function(callback) {
this.modal_callback = callback;
this.createDialog();
},
"createDialog": function() {
this.window = document.createElement("div");
this.window.className = "dialog";
var dialog_title = document.createElement("h2");
dialog_title.appendChild(document.createTextNode(this.title || "名称未定義"));
document.body.appendChild(this.window);
this.drawForm(); // 継承クラスにて実装する
this.show();
},
"show": function(is_show) {
is_show = typeof is_show == "boolean" ? is_show : true;
this.window.style.display = is_show ? "block" : "none";
},
"destroyDialog": function() {
if (!this.window) return;
if (this.modal_callback && this.preEndModal) {
var result = this.preEndModal();
if (result == null)
return;
if (result)
this.modal_callback();
}
if (this.preDestroyDialog) {
if (!this.preDestroyDialog())
return;
}
document.body.removeChild(this.window);
this.window = undefined;
}
};
function AhoDialog() {
this.title = "あほダイアログ";
}
AhoDialog.prototype = new Dialog(); // メンバメソッドを継承 (プロトタイプの複製をコピー)
// 継承クラスの独自メソッドを記述
AhoDialog.prototype.drawForm = function() {
this.dom_elems = {};
var p = document.createElement("p");
p.appendChild(document.createTextNode("あほあほ"));
this.window.appendChild(p);
p = document.createElement("p");
p.appendChild(document.createTextNode("どう思いますか? → "));
var input = document.createElement("input");
input.type = "text";
input.style.width = "20em";
p.appendChild(input);
this.dom_elems.answer_input = input;
this.window.appendChild(p);
p = document.createElement("p");
p.style.textAlign = "center";
input = document.createElement("input");
input.type = "button";
input.value = "OK";
var that = this;
input.onclick = function() {
that.is_ok = true;
that.destroyDialog();
};
p.appendChild(input);
p.appendChild(document.createTextNode(" "));
input = document.createElement("input");
input.type = "button";
input.value = "キャンセル";
input.onclick = function() {
that.is_ok = false;
that.destroyDialog();
};
p.appendChild(input);
this.window.appendChild(p);
};
AhoDialog.prototype.preEndModal = function() {
if (!this.is_ok) // キャンセルボタン
return false;
var answer = this.dom_elems.answer_input.value;
if (answer == "") {
alert("何か入力して下さい…。");
this.dom_elems.answer_input.focus();
return;
}
this.data = {
"answer": answer
};
return true;
};
// 実際にダイアログを使用するコード例
var dlg = new AhoDialog();
dlg.doModal(function() {
var div = document.getElementById("result_container");
while (div.childNodes.length > 0)
div.removeChild(div.lastChild);
var text = "";
for (int i = 0; i < 1000; i++)
text += this.data.answer + " ";
div.appendChild(document.createTextNode(text));
});
}}}