用語の整理でもしてみる

はいどーもニートです。
人生詰んでるので、死ぬ前に備忘録を兼ねてJavaScriptにまつわる用語の整理(および得た知識の整理)でもしてみたいと思います。
「おめぇそれちげぇよ!」というツッコミは歓迎ですが、ツッコンでるころには死んでるので意味ないです。

注*冗談なので真に受けて通報しないでください。

値型と参照型

JavaScriptで参照型と言うと、オブジェクトのことになるでしょう(Object型のオブジェクトに限定した話ではなく、もっと大きな意味でのオブジェクト)。
値型は数値リテラルとかですね。
これらの数値リテラルをオブジェクトのように扱うこともできますが、内部でボックス化がおこなわれているだけで、別にオブジェクトじゃないです。
シンプルな値です。
ついでに言うと文字列は不変型(このへんC#と同じですよね)なので、あんまり無意味に新しい文字列を作りまくると重くなりそうな気がします(内部で新しいオブジェクトを生成するため)。

データ型

これはMDNのそのまま引用したらいいかなと思います。

6 つのデータ型はプリミティブ型です:
真偽値 (Boolean)。 true または false.
null。null 値を意味する特殊なキーワードです。JavaScript は大文字・小文字を区別するため、null は Null や NULL などとは異なります。
undefined。値が未定義のトップレベルプロパティ。
数値 (Number)。 42 や 3.14159 など。
文字列 (String)。 “Howdy” など。
シンボル (Symbol)(ECMAScript 6 の新機能)。 インスタンスが固有で不変となるデータ型。
そして オブジェクト (Object)。

これだけしか型がないんですね。
あんまり悩むところはなさそうですが、nullとundefinedが若干わかりにくいです。
個人的な使いわけですが、undefinedは意図しないものにアクセスしたとき自動的に発生するものなので、nullは明示的な空を意味させることにしています
つまり自分でundefinedを代入させることはないです。
あ、Symbol使ったことないです。
僕はES5までの機能専門です。

クラス

JavaScriptにクラスはないらしいです(ES6でクラス構文ができたらしいですが、ES6使わないので僕にとってはやっぱりないです)。
実際、見た目としてはただの関数をコンストラクタとして用い、new演算子を用いてインスタンスを作ります
僕もこれが初めの頃は全く意味がわからなかったのですが(だってお前関数じゃん、と思っていた)、そういうものとして覚えれば違和感はなくなりました。
そんなわけで、見た目がただの関数のものをコンストラクタと呼びます
実際、JavaScirptではどんな関数でもコンストラクタになれますが、普通はコンストラクタ用の関数と、それ以外の関数をわけて作ります。
そのことをわかりやすくするため、通常の関数はパスカルケース以外(僕はキャメルケース)で書き、コンストラクタ名は普通パスカルケースで書きます
これにて、パスカルケースで書かれた関数を「XXXクラス」と呼ぶんですね(実際はただの関数であっても)。
が、何かしらを説明するときに便利なので、パスカルケースで書かれた関数のことをクラスって呼んでます(少なくとも僕は)。

関数とメソッド

僕がプログラミングをしていて悩んだものの一つに、「関数とメソッドって同じものじゃねえの?」というものがありました。
実際、C#では関数という用語はほとんど使われておらず、ほぼメソッドと呼ばれています。
ですがどうもJavaScriptでは関数とメソッドが区別されているようなのです(言語仕様としてはメソッドなんてものはないらしいですが)。
というわけで、それもまとめておきます。

メソッドとは、オブジェクトのプロパティに関数をセットしたもののことです。
関数とは、それ以外の関数のことです。
つまり
var fnc = function(){};
var obj = {objFnc : function(){}};
obj.objFnc();

この場合fncが関数と呼ばれ、オブジェクトobjのプロパティobjFncがメソッドなのです。
thisを使って関数を呼ぶ場合もメソッドと言うことが多いようです。

this

thisはC#のthisとJavaScriptのthisで全然違うので、かなり混乱しました。
JavaScriptのthisは、クラスのインスタンスを指すとは限りません
thisがどこで使われたかによってthisの指す内容が変わるのです!
一番わかりやすいのが、イベントリスナー内のthisじゃないでしょうか。
イベントリスナー内では、thisはイベントの発生元の参照を返します
関数内や関数の外で使うと、グローバルオブジェクトを指すことになります。
そしてメソッド内の場合(ここで関数とメソッドの違いへの理解が大切になるのです!!)、呼び出し元のオブジェクトを意味します。
つまり先の例(obj.objFnc();)でいえば、オブジェクトobjを指します。
慣れたら「なるほど」と思わないでもない仕様ですが、call()などと組み合わさると非常にわかりにくいです。

ちなみにですが、この呼び出し元のオブジェクトというのが意外と曲者です。
クラスのインスタンスを指しているつもりで、別の関数を指していてエラー……なんてことがわりとあるんですねぇ……。
まあこのへんは僕だけかもしれないので(本読んでもあんまり触れらてないし)、thisについてはこのへんで。

スコープ

JavaScriptのスコープは三つです。

・グローバルスコープ
・関数スコープ
・evalスコープ

ツクール勢にとって、関数スコープで有名なのは即時関数ではないでしょうか。
いろんなサイトで「即時関数を使いましょうねー」と書かれていると思いますが(ついでに言うとヘルプでも即時関数の仕様が推奨されています)、これは変数のスコープを限定するためです。
が、「名前空間を利用すれば問題ない」と考える人もいるようで、僕も実際のところ「確かに問題ないよなあ」と思っています。
とはいえ公式で推奨されている方法を使わない理由も特にないので、僕は使っています。

ちなみにですが、ES6以降の環境では、let文などを使えばブロックスコープを作ることもできます
ただツクールMVのコアスクリプトにlet文が出てこない(多分)ので、僕はvar文しか使ってません。
さらについでの話ですが、ツクールのスクリプトコマンドはevalスコープなので、いくつ変数を宣言してもグローバルスコープを汚染することはありません
じゃんじゃか変数を宣言しましょう。

プロトタイプチェーン

JavaScriptにおいては、Function()のインスタンスにprototypeプロパティが勝手に付け加えられます。
コンストラクタから生成されたインスタンスには、当然のことながらコンストラクタ関数のprototypeプロパティへアクセスする機能が自動的に付与されます。
まずこれがわかりにくいですねえ。

継承の仕方にもいくつか方法がありますが、ツクールのコアスクリプトに習うのが一番いいんじゃないかなと思います。
つまりObject.create()を使う方法です。
Object.create()については
http://uhyohyo.net/javascript/11_7.html
このサイトがめちゃくちゃわかりやすいです。読みましょう!

注*例によってES6は知りません。

で、こうして作ったプロトタイプチェーンなんですが、「最後にはObject.prototypeまでさかのぼる」と書けば、なんとなく動作が予想できるんじゃないかなあと思います。
つまりプロパティにアクセスしたとき、プロトタイプチェーンに従い、最初にみつけたプロパティを返します。
そんなわけで、継承元Aと継承先Bにhogeプロパティが両方定義されていたとしたとき、継承先Bでhogeプロパティにアクセスすると継承先Bで定義されているhogeプロパティが返されます。
ちなみに変数のスコープ解決方法もこれとひじょーに似てますので(Callオブジェクトのスコープチェーン)、どっちかが理解できればどっちも理解できるんじゃないかなあと思いました。

ただ、正直なところ、ぶっちゃけ僕はクラスベースの継承のほうがわかりやすいと思いました
特にC#と比べると、どうも使いにくい感が否めません。
好みと言ってしまえばそれだけな気もしますが……。

プロトタイプを使う理由

prototypeにメソッド等を追加していくと、新しいインスタンスを作成したときに、prototypeに追加されたものはインスタンスにコピーされません
ではどうやってインスタンスはプロトタイプに追加されたメソッドにアクセスするのかと言うと、プロトタイプチェーンをたどってアクセスします。
これに何の意味があるかと言えば、重複する機能のコピーを避け、メモリの節約をしてるんですね。

実際にインスタンスがプロパティにアクセスするとき、こんな感じで動いてます。

1.インスタンスのプロパティを探す
2.見つからない場合、インスタンスの元になるオブジェクトに属するprototypeプロパティを探す(__proto__を使ってプロトタイプチェーンをたどる)
3.それでも見つからない場合、プロトタイプチェーンに従って探し続ける
4.Object.prototypeでも見つからない場合、undefinedを返す

大事なのは1と2の間ですね。
例えばクラスにメソッドを追加するとき、prototypeを使わなければ、1のインスタンスそのものにメソッドを追加します。
でも普通、メソッドって各インスタンスで共通の動きをさせますよね。
つまりメモリの無駄です。
そこでprototypeプロパティにメソッドを追加し、インスタンス作成時に同じメソッドを作るなんて無駄なことをしないようにするのです。

ちなみに、prototypeプロパティには値も追加できますが、値はインスタンスごとに異なることが普通です。
つまり、それなりの理由がない限り、prototypeプロパティにはメソッドしか追加しません。

もう一つついでに書くと、静的メソッドはインスタンスにはコピーされません。
これ自体は普通の動きだと思うのですが、「JavaScriptにおける静的メソッドってなんぞ?」というのがややこしく感じたため、わりと混乱しました。

インスタンスを作成しない場合

インスタンスを作成しないなら、prototypeプロパティに値を追加する必要はありません。
静的クラスとして使いましょう。

prototypeはぶった切ったり、別のものをつなげたりと、かなりアクロバティックなことができるので、これもややこしいなあと思いました(ぶった切るのって、どういうときにするのかいまいちわかりませんが)。

ES6の機能

個人的に使いたいのが

・let
・イテレーター・ジェネレーター
・アロー関数

あたりでしょうか。
特にlinq.jsを使うなら、イテレーターやアロー関数はぜひとも使いたいところですが……。
ツクールMVって対応してないんですよねえ。
ラノゲツクールは対応してるのかしらん。

長々と書いてみて

自分でまとめてみると、どこの理解が曖昧だったのかがわかってよいですね。
改めて『開眼JavaScript』や『JavaScript本格入門』を紐解き、理解が深まったと思います。
しかしそろそろフレームワークの使い方も覚えないとなあと思っていまして、しかしC#でも遊んでみたいしで、仕事はねえしでどうすりゃええねんっちゅー感じです。
ほなそんな感じで、また。
続きも書きました。

フォローする