Changes between Initial Version and Version 1 of HowTo/JavaScriptLanguageIntroduction/Introduction


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

--

Legend:

Unmodified
Added
Removed
Modified
  • HowTo/JavaScriptLanguageIntroduction/Introduction

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