Changes between Version 12 and Version 13 of HowTo/JavaScriptLanguageIntroduction


Ignore:
Timestamp:
Aug 2, 2010, 10:01:49 AM (14 years ago)
Author:
村山 俊之
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • HowTo/JavaScriptLanguageIntroduction

    v12 v13  
    1 [[PageOutline]]
    2 
    31= !JavaScript 言語概論 =
    42
     
    86
    97 * !JavaScript ってそもそも何? どんなことができるの? という人は[wiki:HowTo/JavaScriptTutorial JavaScript チュートリアル]を、
    10  * !JavaScript に最初から用意されているクラスとかを使いこなしたい! という人は[wiki:HowTo/CoreJavaScriptIntroduction Core JavaScript イントロダクション]を、
     8 * !JavaScript に最初から用意されているクラスとかを使いこなしたい! という人は[wiki:HowTo/JavaScriptCoreObject JavaScript コアオブジェクト]を、
    119 * !JavaScript でリッチなグリグリ動く GUI を作りたい! という人は[wiki:HowTo/DomIntroduction DOM 概論]を、
    1210 * !JavaScript でもフリーのライブラリを利用して楽をしたい! という人は[wiki:HowTo/JavaScriptLibraryIntroduction JavaScript ライブラリ概論]を、
     
    1412それぞれ参照してください。
    1513
    16 == どんな言語? ==
     14== コンテンツ ==
    1715
    18 !JavaScript 言語の大まかな特徴を、ざっと列挙してみましょう。
    19 
    20 === 比較的小さな実装のスクリプト言語 ===
    21 
    22 Web ブラウザの機能の一つとして実装される !JavaScript は、その為に非常に軽量に実装されています。つまり、言語自体が元々持つ機能が少ない、ということです。例えば、ローカルファイルへの入出力を行う命令などは用意されていません。
    23 
    24 また、スクリプト言語なので、処理系はプログラムをステップ毎に逐次読み込みながら実行します。最近は JIT コンパイラ (内部でコンパイルを行ってから実行するタイプ) の処理系が多いので、基本的な構文チェックだけを先に通してから実行される、という動作も多くなってきていますが、基本的にはエラーが発生する前の部分までは実行される、といった動作になります。
    25 
    26 === 文法は C や Java にやや似ている ===
    27 
    28 !JavaScript という名前の通り、言語の文法は Java に似ていなくもありません。が、値の型が曖昧な点や、インタプリタとして動作する点などを理由に、初期の頃は C に似た書き方の BASIC みたいな言語、といった見られ方をしていました。
    29 
    30 近年、 !JavaScript の持つ言語としての特徴が再評価され、むしろ C に似た書き方の LISP みたいな言語、といった見られ方に移りつつあるようです。
    31 
    32 文法が C や Java に似ていると言われる所以は何か。まず、処理の単位となるブロックをブレース "{" ~ "}" で括る書き方。
    33 
    34 {{{
    35 function hoge() {
    36     // 処理...
    37 }
    38 
    39 if (a == b) {
    40     // 処理...
    41 }
    42 }}}
    43 
    44 それから、処理の単位をセミコロン ";" で区切る書き方 (但し、行末のセミコロンは必須ではない)。
    45 
    46 {{{
    47 var a = 10;  var b = a + 20;
    48 
    49 // 行末のセミコロンは省略可
    50 var a = 10
    51 var b = a + 20
    52 }}}
    53 
    54 コメントの書き方も Java と同じです。 "/*" ~ "*/" で括るか、 "//" の後ろに書きます。
    55 
    56 {{{
    57 /*
    58  * 複数行
    59  * コメント
    60  */
    61 
    62 alert("コメントサンプル");  // 一行コメント
    63 }}}
    64 
    65 その他、 if, for, while, switch といった処理の流れを記述するおなじみの構文や、 C言語風の演算子 (==, ++, -- など)、さらには関数呼び出し時には関数名の後ろに引数をかっこで括る書き方なども、まさに C や Java そっくりの書き方です。
    66 
    67 === 数値と文字列の型は曖昧 ===
    68 
    69 多くのスクリプト言語と同様、 !JavaScript もまた、数値と文字列を自由に行き来する言語です。例えば、数値と文字列の足し算は、文字列の連結として解釈されます。
    70 
    71 {{{
    72 var a = 10;
    73 var b = 20;
    74 
    75 alert("a + b = " + (a + b));  // "a + b = 30" と表示
    76 }}}
    77 
    78 また、数字だけの文字列を数値演算に含めることもできます。その場合、足し算においては文字列の連結として誤解されないよう注意が必要です。
    79 
    80 {{{
    81 var a = document.getElementById("input_a").value - 0;  // 入力欄の文字列を取得。
    82 var b = document.getElementById("input_b").value - 0;  // 0 で減算することで、変数には確実に数値として代入される。
    83 
    84 document.getElementById("result").value = a + b;       // 足し算した結果を別のフォーム部品に表示。
    85 }}}
    86 
    87 === 複雑なデータ構造を自由に書ける === #introduction-hashmap
    88 
    89 C や Java がデータ構造の構成を構造体やクラスとしてあらかじめ定義しなければならないのに対し、 !JavaScript のオブジェクトはいつでも好きなだけメンバを追加できます。
    90 
    91 {{{
    92 // オブジェクトを作成
    93 var murachi = {
    94     real_name = "村山 俊之",
    95     real_name_kana = "ムラヤマ トシユキ",
    96     birth = new Date(1978, 1, 7),
    97     sex = "Female"
    98 };
    99 
    100 // 本名を表示
    101 alert("名前: " + murachi.real_name + " (読み: " + murachi.real_name_kana + ")");
    102 
    103 // オブジェクトに後からメンバを追加できる
    104 murachi.disp_name = "T.MURACHI";
    105 murachi.tall = 166;
    106 murachi.weight = 70;
    107 
    108 alert(murachi.disp_name + "の身長は" + murachi.tall "cm、体重は" + murachi.weight + "kg");
    109 }}}
    110 
    111 もちろん、メンバにオブジェクトや配列を持たせることもできますし、配列にオブジェクトを持たせることもできます。
    112 
    113 {{{
    114 var harapeko = {
    115     name = "株式会社はらぺこ",
    116     name_e = "Harapeko Inc.",
    117     birth = new Date(2008, 4, 1),
    118     // オブジェクトの入れ子
    119     address = {
    120         post_no = 2750015,
    121         county = "千葉県",
    122         city = "習志野市",
    123         town = "鷺沼台1-8-27",
    124         building = "マウント・ヴィレッヂⅠ 101"
    125     },
    126     // メンバに配列を追加
    127     address_history = [
    128         // オブジェクトの配列
    129         {
    130             post_no = 2760017,
    131             county = "千葉県",
    132             city = "八千代市",
    133             town = "八千代台北1-10-11",
    134             building = "マメール八千代台 502"
    135         },
    136         {
    137             post_no = 2760017,
    138             county = "千葉県",
    139             city = "八千代市",
    140             town = "八千代台北1-10-11",
    141             building = "ファイン八千代台 502"
    142         }
    143     ]
    144 };
    145 
    146 // オブジェクトの内容を一通り表示
    147 document.write("<p>会社名: " + harapeko.name + "</p>");
    148 document.write("<p>創立: " + harapeko.birth.toLocaleString() + "</p>");
    149 document.write("<p>現住所: 〒" + harapeko.address.post_no + " " +
    150     harapeko.address.county + harapeko.address.city + harapeko.address.town + " " +
    151     harapeko.building + "</p>");
    152 
    153 document.write("<p>過去の住所 (古い順に):</p><ul>");
    154 
    155 for (var i = 0; i < harapeko.address_history.length; i++) {
    156     var old_address = harapeko.address_history[i];
    157     document.write("<li>〒" + old_address.post_no + " " +
    158         old_address.county + old_address.city + old_address.town + " " +
    159         old_address.building + "</li>");
    160 }
    161 
    162 document.write("</ul>");
    163 }}}
    164 
    165 === 割と本格的な関数型言語 ===
    166 
    167 !JavaScript では、関数の定義内容を変数に代入することができます。
    168 
    169 {{{
    170 // 変数 hoge に関数を代入
    171 var hoge = function(text) {
    172     alert("hoge: " + text);
    173 }
    174 
    175 // hoge に代入した関数を呼び出す
    176 hoge("fuga");
    177 }}}
    178 
    179 かっこでうまく括ってやれば、わざわざ変数に代入せずとも、その場で呼び出してしまうことも可能です。
    180 
    181 {{{
    182 // 無名の関数を即座に呼び出す例
    183 (function(text) {
    184     alert("hoge: " + text);
    185 })("fuga");
    186 }}}
    187 
    188 こうして作られた無名関数は、'''クロージャ'''として機能します。つまり、無名関数が生成されたときの変数の値を覚えています[[FootNote(正確には、変数のスコープが失われたときの値を覚えています。この性質は若干ややこしいので、クロージャを用いる際には関数の生成の仕方に注意する必要があります。詳細は後述します。)]]。
    189 
    190 {{{
    191 // クロージャを生成する関数
    192 function generateAlertClosure(text) {
    193     // 無名関数を返す
    194     return function() {
    195         alert(text);
    196     };
    197 }
    198 
    199 var hoge = generateAlertClosure("ほげっ!");
    200 var fuga = generateAlertClosure("ふんがーヽ(゚口゚ミ)ノ");
    201 
    202 alert("まだ何も起こっていないはず…");  // ここまではまだ alert が呼ばれていないことを確認
    203 
    204 hoge(); // ここで "ほげっ!" と表示
    205 fuga(); // "ふんがー(ry" と表示
    206 }}}
    207 
    208 [[FootNote]]
    209 
    210 === プロトタイプ型オブジェクト指向 ===
    211 
    212 !JavaScript でオブジェクトと言えば、複雑なデータ構造を表す'''連想配列'''を指します。「[#introduction-hashmap 複雑なデータ構造を自由に書ける]」の節で示したアレですね。
    213 
    214 {{{
    215 // オブジェクト
    216 var obj = {
    217     "foo": "hoge",
    218     "bar": "fuga",
    219     "baz": "oyoyo"
    220 };
    221 }}}
    222 
    223 ところで、オブジェクト指向プログラミングといえば、オブジェクトの生成・破棄を行う処理を定義するコンストラクタ・デストラクタがあり、メンバメソッドがあり、カプセル化のためのアクセス制約があり、クラスの継承がある、といったいわゆる Java 的な世界観が連想されます。 !JavaScript の場合はクラスではなく'''プロトタイプ'''というアプローチではありますが、似たようなことを行うための記法が用意されています。
    224 
    225 まず、関数の定義はそのままコンストラクタとしても機能します。
    226 
    227 {{{
    228 // クラス定義のようなもの
    229 function MyObject() {
    230     // メンバ変数の定義
    231     this.foo = "hoge";
    232     this.bar = "fuga";
    233     this.baz = "oyoyo";
    234 }
    235 
    236 // 上記を用いてオブジェクトインスタンスを生成
    237 var obj = new MyObject();
    238 
    239 alert("foo: " + obj.foo);  // "foo: hoge" と表示される
    240 }}}
    241 
    242 デストラクタは残念ながら存在しません。オブジェクトが必要なくなるタイミングで特定の処理を行いたい場合は、自分でメソッドを用意して、自分で確実に呼び出すよう記述する必要があります。
    243 
    244 メンバメソッドはメンバ変数に関数を代入でも良いのですが、通常はプロトタイプのメンバに代入します。
    245 
    246 {{{
    247 // クラス定義のようなもの
    248 function MyObject() {
    249     // メンバ変数の定義
    250     this.foo = "hoge";
    251     this.bar = "fuga";
    252     this.baz = "oyoyo";
    253 }
    254 
    255 // メンバメソッド定義
    256 MyObject.prototype = {
    257     "show" = function(member) {
    258         if (this[member] == null)
    259             alert(member + "なんてメンバはねーよ!!");
    260         else
    261             alert(member + ": " + this[member]);
    262     },
    263     "showAll" = function() {
    264         var dump = "";
    265         for (var member in this) {
    266             if (dump != "")
    267                 dump += "\n";
    268             if (typeof this[member] != "string")
    269                 continue;
    270             dump += member + ": " + this[member];
    271         }
    272         alert(dump);
    273     }
    274 }
    275 
    276 // 上記を用いてオブジェクトインスタンスを生成
    277 var obj = new MyObject();
    278 
    279 obj.show("bar");    // "bar: fuga" と表示
    280 obj.showAll();      // foo, bar, baz すべて表示
    281 }}}
    282 
    283 以上が、 Java や C++ などで言うところのクラス定義に相当する、といって良いでしょう。継承を行いたい場合は、新たに定義するコンストラクタの関数名に、プロトタイプの複製をコピーしてあげれば ok です。以下は、人を表す基本クラス Human からコンストラクタとメソッドを継承し、働く人を表す派生クラス Worker を実装するサンプルです。
    284 
    285 {{{
    286 function Human(name, birth, sex) {
    287     if (name == null)  // プロトタイプの複製をコピーする際には、このコンストラクタは何もしない
    288         return;
    289     this.name = name;
    290     this.birth = birth instanceof Date ? birth : new Date(birth);
    291     this.sex = /^m/i.test(sex) ? "male" : "female";
    292 }
    293 
    294 Human.prototype = {
    295     "introduce": function() {
    296         var message = "私の名前は" + this.name + "。" +
    297             this.birth.getFullYear() + "年" + (this.birth.getMonth() + 1) + "月" +
    298             this.birth.getDay() + "日生まれの" + (this.sex == "male" ? "男性" : "女性") + "です。";
    299         var message += this.makeAdditionalIntroduction();
    300         alert(message);
    301     },
    302     "makeAdditionalIntroduction": function() {
    303         // 基本クラスでは何もせず、空文字列を返す
    304         return "";
    305     }
    306 };
    307 
    308 function Worker(name, birth, sex, company, roll, post) {
    309     Human.call(this, name, birth, sex);  // 親クラスのコンストラクタを呼び出す
    310     this.company = company;
    311     this.roll = roll;
    312     this.post = post;
    313 }
    314 
    315 Worker.prototype = new Human();  // クラスメソッドの継承 (プロトタイプの複製をコピー)
    316 
    317 // Worker 独自のメソッドを実装
    318 Worker.prototype.makeAdditionalIntroduction = function() {
    319     // 自己紹介に追加する内容を返す
    320     return "\n" + this.company + (typeof this.post == "string" ? "の" + this.post : "") +
    321         "にて、" + this.roll + "をやっています。";
    322 };
    323 
    324 var murachi = new Human("村山 俊之", new Date(1978, 1, 7), "male");
    325 murachi.introduce();
    326 
    327 murachi = new Worker("村山 俊之", new Date(1978, 1, 7), "male", "株式会社はらぺこ", "代表取締役社長");
    328 murachi.introduce();
    329 }}}
    330 
    331 最後に、メンバの private 宣言に相当するアクセス制約についてですが、 !JavaScript にその為の簡潔な記法が用意されているわけではないものの、それに近いものをクロージャを用いて実現することは可能である、という例を以下に示します。ここまでやるとかなり大きなグループ開発にも耐えうる運用となり得ますが、 !JavaScript 界隈ではあまり見かけない習慣かも知れません。
    332 
    333 {{{
    334 var IS_DEBUG = true;
    335 
    336 function Human(name, birth, sex) {
    337     // プライベートフィールド
    338     var fields = {
    339         "name": name,
    340         "birth": birth instanceof Date ? birth : new Date(birth),
    341         "sex": /^m/i.test(sex) ? "male" : "female"
    342     };
    343    
    344     // メソッドがプライベートフィールドを参照するためのメソッド
    345     this.getFields = function() {
    346         if (IS_DEBUG && !this.permitPrivate())
    347             throw new Error("private method access denied.");
    348         return fields;
    349     };
    350 }
    351 
    352 Human.prototype = {
    353     // プライベートメソッドの呼び出し元をチェックしてアクセス許可を判定する
    354     "permitPrivate": function() {
    355         var is_permitted = false;
    356         for (var method in Human.prototype) {
    357             if (Human.prototype[method] == arguments.callee.caller.caller) {
    358                 is_permitted = true;
    359                 break;
    360             }
    361         }
    362         return is_permitted;
    363     },
    364     // private:
    365     "getAge": function() {
    366         if (IS_DEBUG && !this.permitPrivate())
    367             throw new Error("private method access denied.");
    368         var now = new Date();
    369         var today = now.getFullYear() + ("0" + (now.getMonth() + 1)).slice(-2) +
    370             ("0" + now.getDay()).slice(-2);
    371         var birth = this.getFields().birth;
    372         var birth_day = birth.getFullYear() + ("0" + (birth.getMonth() + 1)).slice(-2) +
    373             ("0" + now.getDay()).slice(-2);
    374         return (today - birth_day) / 10000 | 0;
    375     },
    376     // public:
    377     "introduce": function() {
    378         var fields = this.getFields();
    379         alert("私の名前は" + fields.name + "。" +
    380             fields.birth.getFullYear() + "年" + (fields.birth.getMonth() + 1) + "月" +
    381             fields.birth.getDay() + "日生まれの" + this.getAge() + "歳・" +
    382             (fields.sex == "male" ? "男性" : "女性") + "です。");
    383     }
    384 };
    385 
    386 var murachi = new Human("村山 俊之", new Date(1978, 1, 7), "male");
    387 
    388 murachi.introduce();            // 紹介文を表示
    389 alert(murachi.getAge() + "歳"); // エラー: プライベートメソッドにアクセスできない
    390 }}}
     16 * [wiki:HowTo/JavaScriptLanguageIntroduction/Introduction どんな言語?]
     17 * [wiki:HowTo/JavaScriptLanguageIntroduction/ValueAndVariable 値と変数]
     18 * [wiki:HowTo/JavaScriptLanguageIntroduction/Calculate さまざまな演算]
     19 * [wiki:HowTo/JavaScriptLanguageIntroduction/RegularExpression 正規表現]
     20 * [wiki:HowTo/JavaScriptLanguageIntroduction/ProcessFlow 処理の流れ]
     21 * [wiki:HowTo/JavaScriptLanguageIntroduction/Function 関数]
     22 * [wiki:HowTo/JavaScriptLanguageIntroduction/ObjectAndArray オブジェクトと配列]
     23 * [wiki:HowTo/JavaScriptLanguageIntroductoin/PrototypeAndOOP プロトタイプとオブジェクト指向]
     24 * [wiki:HowTo/JavaScriptLanguageIntroduction/Exception 例外処理]