dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【JavaScript】他言語からJavaScriptへ移行した時のオブジェクト指向での勘所 3回目

どうも、雪が一晩で70~80cmくらい積もりました。

前日にスノーボードに行っていたので、次の日の雪かきで死にそうになりましたが、みなさんはいかがお過ごしでしょうか?

などという手紙の定型文みたいな書き方はやめて早速JavaScriptオブジェクト指向における勘所の3回目やっていきたいと思います。

おそらくこれが最終回になると思いますが、結果や如何に!?

前回 -> 【JavaScript】他言語からJavaScriptへ移行した時のオブジェクト指向での勘所 2回目 - datchの日記

前回までのおさらい


そしてJSOOPを実現しようとすると以下の仕組みを利用する必要があります。

  • オブジェクトのプロトタイプ
  • プロトタイプチェーン
  • 即時関数
  • クロージャ

上記のオブジェクトのプロトタイプ、プロトタイプチェーン、更に追加で継承についても触れていきました。
そして、今回が最後で残りの部分について触れていきたいと思います。
実は残りの部分というのは、JSOOPにおいてはそこまで重要という訳ではないので、軽く聞き流す程度でも問題ありません。

第一回でも触れたようにクロージャを使用すると関数の実体が余分に宣言され、メモリを余計に消費してしまう問題があるからです。
これについての詳細はクロージャの部分で触れていきます。

それでは残りの機能について説明します。

今回はそれぞれの機能について解説して行きたいと思います。

機能紹介のその前に



この記事を書く前に以下のスライドを閲覧し、非常に勉強になりました。
C++系統のオブジェクト指向に慣れ親しんでいる人がJSの仕様で
引っ掛かりそうな箇所をしっかり紐解いて説明してくれています。
著者の方には感謝感謝です。

即時関数



即時関数という言葉を耳にしたことはありますか?
実を言うと私もこの言葉を聞いたのはごく最近です←
今までJSでのクラスの宣言をどのように行うか、という過程で何となく調べて使っていた程度だったのですが、今回色々と調査をしていく上で名前や仕組みをある程度まで知ることが出来ました。

即時関数とは?

まずは以下のコードを見てみましょう。

(function()
{
    alert('Hello, 即時関数');
})();

これが即時関数です。
こいつをソースコードのグローバルな場所に埋め込むと、すぐに開始直後(正確にはJavaScriptがブラウザ上でロードされた瞬間)に実行され、「Hello, 即時関数」と表示されます。
JavaScriptを始めたばかりの方は、この関数が何故呼び出されていないのにも勝手に実行されるのか、何故このような書き方をしているのかがわからないと思います。
というより、自分の最初がそれでした。

では、こいつがどのような工程を経て実行されるかを順を追って解説して行きます。

即時関数実行までの流れ

では、まず上の即時関数を理解し易い形に直した物を下に明記してみる。

// hoge関数を定義
function hoge()
{
    alert('Hello, 即時関数');
}

// 読込終了と同時にinit()を実行
window.onload = init;

function init()
{
    hoge(); // Hello, 即時関数
}

このような形で呼び出されることで上記と同様の動作となる。
(正確にはHTMLファイルが完全に読込完了するまで実行するか、しないかの違いはあるが勘弁して頂きたい)

上記ではhoge()関数をグローバルに宣言し、その関数をinit()の内部で呼び出しているというものだ。

では、関数を以下のように表記できることを知っている人も多いだろう。

function()
{
    alert('Hello, 即時関数');
}

これは無名関数、匿名関数、ラムダ式、ラムダ関数などと呼ばれている。
JSではCのように関数に無理に名前を付けて宣言する必要はなく、上記のように名前なしでも宣言することが出来ます。
ではどのようにこの関数を呼び出せばいいのか?

まずひとつの方法として変数に関数を代入して呼び出す方法。

window.onload = init;

function init()
{
    var hoge = function()
    {
        alert('Hello, 即時関数');
    }
    hoge();
}

変数に代入することで"変数名(引数)"で代入した関数を呼び出す事が出来ます。

では、更に以下のように変更を加えてみるとどうでしょう?

var hoge = (function()
{
    alert('Hello, 即時関数');
});
hoge();

更に更に以下のようにしてみると...

(function()
{
    alert('Hello, 即時関数');
})();

あれ?最初の即時関数になった。
実は最後のカッコというのはこの関数自身を呼び出しに使っています。
(関数オブジェクト)()のように関数オブジェクトを()で囲むことで、その後に()を付けることでこの関数自身を呼び出します。

即時関数のメリット

では何故わざわざこのように即時関数を利用するのか?
それは、即時関数の中身というのはグルーバルから見えない、つまりは秘匿な値になります。

クラスの宣言でわざわざ以下のようにreturnしていたのも、関数をreturnすることでコンストラクタ関数(つまりは擬似クラス)を変数に格納することで、他の言語と同様の感覚で使えるJSOOPを実現していたのです。

var Hoge = (funciton()
{
    function Hoge(){};
    return Hoge;
})();

上記のように継承を行わない時はわざわざ即時関数にする意味はない。
しかし、宣言の統一性を高めるためにも敢えて即時関数を利用して、継承なしのクラスも宣言するのが一般的な気がする。

次のように継承を行う場合には、関数呼び出しの()の中に基底クラスを引数として与えることで、自在に継承を行える。

var Huga = (function(super)
{
    functino Huga(){super.apply(this);};
    return Huga;
})(Hoge);

すごいよ、即時関数!
他にもわざわざonload使いたくない時に実行するときに使ったりとか、色々と便利な使い方が出来ます。

クロージャ



クロージャについて説明しようと思ったのですが、第一回目の説明以上にクロージャについて説明をする必要があまりない、と考えたのでthis参照について解説。

this参照



this参照とはJavaやCのオブジェクト指向言語では、そのオブジェクトのインスタンス自身(メモリ上でデータとして実体化されたクラス)を指すポインタです。
このインスタンスオブジェクトの先頭アドレスからどういった型で何番目に宣言してあるのか、という情報を元にプロパティの実体にアクセスします。

というのが、JavaやCでは世界の真理として扱われていますが、JSの世界ではこれが崩壊します。

結論から言うとJSでのthis参照はインスタンスオブジェクトを必ずしも指しません。

これがJavaやCのOOPを触れている人には圧倒的に詰まりやすいポイントなのです。

thisの指し示す先

非常に良い記事があったのでこちらを参考にどうぞ。

JavaScriptの「this」は「4種類」?? - Qiita

上記の記事で実際にはthis参照というものは4種類あるということを述べていました。
JSOOPではこれの「コンストラクタ呼び出し」に当たります。

実はこの点はJSOOPでは深く考える必要はありません。
何も考えずに黙って宣言したクラスをnewして下さい。

こうすることでJavaやCと同じ感覚でthisが使え、非常に幸せな気持ちになれます。

しかし、JSのthis参照で苦戦した人には何も考えずに信じて使え、と言われてもキツイでしょう。

では、newをすることで何が起きたかについて述べてこのJSOOPについての解説を終わりたいと思います。

newの処理の流れ

ちなみにこのnewの仕組みについての概要は最初に紹介してあるスライドにも明記されています。
が、理解のためにもこちらで書いておきます。

しっかりと理解したい方はスライドも確認してください。

では、以下の文がどのような処理になっているのか?

var h = new Hoge();

これは事実、このように変換することが出来、これによりthis参照がhを指し、hのプロパティにアクセスするようになります。

var h = (function(v, c)
{
    var obj = {};
    c.apply(obj);
    return obj;
})(h, Hoge);

このように自動的コンストラクタの呼び出しをapplyによってthisの中身が変化して返され、混乱がないようにプロパティを弄ることが出来るようになる仕組みです。
実際、ECMASCRIPTのソースを見ているわけではないので、動作は違うと思いますが、このような動作イメージです。

最後に



JSは高度な言語仕様を駆使してOOPを実現、というよりは実装しているということがわかった。
言語的には非常に面白いが、クロージャでの関数のメモリ問題、グローバル汚染、this参照の危険性などで大規模な開発が難しいというのも感じた。
それを置き換える意味でもTypeScriptなどのJS変換言語の発達がこれからも重要になるということを、改めて再認識した。