スポンサーリンク

JavaScriptで,オブジェクトやクラスの初歩を理解しているか,実力を確かめるための7つの質問 (サンプルコード付き)


JavaScriptはオブジェクト指向のスクリプト言語。

オブジェクトやクラスの扱い方の基礎を理解していない場合,開発の戦力にならない。

JavaScriptの業務スキルレベル 判別表 (5段階)
http://language-and-engineering.hatenablog.jp/entry/20100111/p1

初級者を脱した段階(ノーマルレベル)に求められる項目:

  • 「JavaScriptで,プロトタイプベースのOOPができる。」

つまり,オブジェクトの扱いがわからなければ,まだ入門書を読み進めていく段階である。



せめて,以下の7つの質問に答えられるようになろう。

  • (1)関数呼び出し時にnewを付けるのと付けないのでは,
    • 関数内のthisにどのような違いが生じるか。
    • 関数の呼び方・とらえ方にどのような違いが生じるか。
  • (2)クラスのインスタンスを生成する際に,生成される新規オブジェクトのメンバを定義・初期化する方法は2種類ある。
    • それぞれ何を使って初期化するのか。
    • それぞれの初期化タイミングにはどのような違いがあるか。
    • それぞれのメモリ効率にはどのような違いがあるか。なぜそうなるのか。
  • (3)オブジェクトのプロパティが呼び出される際の,プロパティの探索の順序を2段階で述べること。(※クラスの継承は無いものとする。)
  • (4)プロトタイプチェーンによってコンストラクタのprototypeにリンクする状況を,自分なりの何かのアナロジーで例えてみること。
  • (5)prototypeの中身を定義したり更新したりするためには,以下の2つの方法がある。それぞれのメリットとデメリット(危険性)は何か。
    • A.prototype.x = 〜; のように,個別に定義する。
    • A.prototype = { x : 〜, y : 〜 }; のように,まとめてハッシュで定義する。
  • (6)new付きで関数が呼び出されたときに,それによって生じる処理を4ステップで説明すること。
  • (7)JavaScriptでOOPを学ぶと,function(){〜} という記述には2通りの解釈が生じる。
    • 何の宣言と何の宣言か。
    • どのように見分けることができるか。


なお,クラスの「継承」に関わる事項は,ここには含まれていない。基礎事項だけである。


これら7つの点に回答できるようになるためのサンプルコードを下記に示す。


目次:


(1)・(7):関数宣言はクラス宣言である

// この行のコメントアウトを外せば,jsファイルを単独で実行できます。
//function alert(s){ WScript.Echo(s) };



// ただの関数
var f1 = function()
{
	alert( "f1" );
};

// 関数を呼んだだけ
f1(); // "f1"



// 関数オブジェクトにプロパティを持たせた例
var f2 = function()
{
	alert( this.x );
};
f2.x = "f2という関数オブジェクトが持つプロパティです。";


f2(); 
// 何も表示されない。
// thisは関数オブジェクトではなくグローバルをさすから。

alert( f2.x ); 
// 同じくxを呼び出しているわけだが,この場合は
// "f2という関数オブジェクトが持つプロパティです。" が表示される。

基礎の要点をまとめると

  • JavaScriptのちゃんとしたプログラミングで役に立つものは,ほとんどすべてオブジェクトである。
  • 特に,関数や,無名関数(クロージャ)はオブジェクトである。
  • そして,オブジェクトのプロパティは全て,連想配列(ハッシュ)の元であり,ドット表記はハッシュ表記のシンタックスシュガーである。
  • オブジェクトはプロパティを持てるから,関数オブジェクトもプロパティを持てて,ドット表記とかでアクセスできる。
  • functionを実行したとき,その内部のthisは,関数オブジェクトを指すわけではない。
// 関数をnew付きで呼び出す例
var F3 = function()
{
	this.x = "コンストラクタがセットしたプロパティです。";
};

var f3_1 = new F3();
var f3_2 = new F3();

alert( f3_1.x );
alert( f3_2.x );
// 両方とも "コンストラクタがセットしたプロパティです。" が表示される。

f2のサンプルとf3のサンプルを比較すること。違うのはnewの有無だけだが,thisの指すものが変わってくる。


つまり,関数実行時にnewをつけると,関数内でのthisの意味が変わり,
「新規オブジェクト」を指すようになる。


言い換えると,newをつけて実行される関数は,
新規オブジェクトの生成メソッド(コンストラクタ)として機能する。


コンストラクタ内に新規オブジェクトの初期化処理を書くことにより,
新規オブジェクトを生成のたびに毎回動的に初期化できる。


つまり,関数宣言はクラス宣言でもある。


(2)〜(6):prototypeとは何か,どう使いわけるのか

以下は時間差を生むと説明しやすいので,WSHのSleep機能を使っています。

hoge.jsとかで保存してダブルクリックすれば実行できます。

function alert(s){ WScript.Echo(s) };



// prototypeを使った例
var F4 = function()
{
	this.x = "動的に初期化したプロパティです。" 
	 + ( new Date() ).getSeconds();
};
F4.prototype.y = "静的に初期化したプロパティです。" 
	 + ( new Date() ).getSeconds();

	// この時点での時刻を「0秒目」と考えることにする。

// 1秒目に生成されたオブジェクト
	WScript.Sleep( 1000 );
var f4_1 = new F4();

// 2秒目に生成されたオブジェクト
	WScript.Sleep( 1000 );
var f4_2 = new F4();


alert( f4_1.x ); // 動的に初期化,1秒目
alert( f4_2.x ); // 動的に初期化,2秒目

alert( f4_1.y ); // 静的に初期化,0秒目
alert( f4_2.y ); // 静的に初期化,0秒目


オブジェクトの初期化方法には2種類ある。


コンストラクタから行なう新規オブジェクトの初期化は動的なのに対し,
prototypeから行なう新規オブジェクトの初期化は静的である。



prototypeを使った初期化は,あらかじめ参照用のオブジェクトをprototypeに用意しておいて,
インスタンス側で必要になった時にそのprototypeを参照している。

prototypeの参照は全インスタンスで共有。

JavaScriptの動かないコード (中級編) オブジェクトのprototypeを変更した時のエラー
http://language-and-engineering.hatenablog.jp/entry/20080922/1222010471


prototype経由のオブジェクト初期化処理は,静的な「事前の」初期化なので,
prototype内のプロパティ定義ではthisを利用できない。

(thisが指すべき新規オブジェクトがまだ生成されていないので)

JavaScriptの動かないコード (中級編) オブジェクトのプロパティ定義にthisを使って失敗するエラー
http://language-and-engineering.hatenablog.jp/entry/20090221/p1

newの挙動を覚えよう。

JavaScript の new 演算子の意味
http://nanto.asablo.jp/blog/2005/10/2...

newにより

  • (1)新規オブジェクトが生成され
  • (2)そのオブジェクトはコンストラクタのprototypeを参照するようになり
  • (3)そのオブジェクトをthisとした状態でコンストラクタが実行され
  • (4)そのオブジェクトが返される


prototypeのイメージをつかもう。

prototype(内部的には__proto__という名前で実装されている)は,rubyで言えば,あるオブジェクトの「method_missing」メソッドのようなもの。

デフォルトではコンストラクタ関数のprototypeプロパティが代入される。


あるオブジェクトがプロトタイプチェーンを持つということは,オブジェクトにプロパティが見つからないときの「助け舟」,「代打」, 「駆け込み寺」のようなもの。

プロトタイプ(prototype)によるJavaScriptのオブジェクト指向
http://codezine.jp/article/detail/222

「読み取り評価の時は、暗黙の参照をたどるのですが、代入やdelete演算子は、たどらない」



http://www.tokumaru.org/JavaScript/pr...

  • prototypeに書いておいたほうがメモリ効率が良い。
  • JavaScript言語では、オブジェクトのプロパティを探索する際に、まずオブジェクトが直接持っているプロパティから、該当のプロパティを探索します。
  • そこになかった場合、オブジェクトのクラス(JavaScriptの場合、厳密にはコンストラクタ)のprototypeプロパティの中から、該当する名前のプロパティを探します。

オブジェクトを使った処理の現実的な例

var Talker = function( name )
{
	// 名前
	this.name = name;
	
	// 誕生秒
	this.birth_second = (new Date()).getSeconds();
};
Talker.prototype.introduce = function()
{
	alert( "私の名前は" + this.name + "です。誕生秒は"
	   + this.birth_second + "です。" );
};

// インスタンスを生成してみる
var t1 = new Talker( "太郎" );
	WScript.Sleep( 1000 );
var t2 = new Talker( "次郎" );

t1.introduce();
t2.introduce();

補足

基礎事項なので,ここに挙げた内容が分からなかった場合は,入門書などを読み返すこと。

わからなかったキーワードをネットで調査するのも○。


クラスの継承など,次のステップの話題は,以下のエントリを参照。

JavaScriptで,クラスを継承する方法 (複数のサブクラスから共通クラスのプロトタイプを参照する)
http://language-and-engineering.hatenablog.jp/entry/20100924/p1


なお,「プロトタイプベースの言語なのでクラスはないだろ。」と正確に気づいて突っ込んだ人は上級者。