JavaScriptプログラミング講座【関数について(Function)】

ECMAScript は、プロトタイプベース(インスタンスベース)の言語です。

プロトタイプは、実体化済みのオブジェクト(インスタンス)です。

実体化済み

であるオブジェクトをベースにして、新しいオブジェクトを生成できます。

クラスベース

とは、プロトタイプベースの対となる用語です。

静的な定義(未実体)

であるクラスをベースにして、新しいオブジェクトを作成できます。

ECMAScript 6 からは、クラスを使用した、クラスベースなコードを書く事もできます。

■「コンストラクタ関数」と「プロトタイプ」の関係について

new

演算子を使って、コンストラクタ関数を、

インスタンス化(実体化)

する事ができます。

実体化すると、新しいオブジェクトを作成する事ができます。

新しいオブジェクトは、インスタンス(実体)とも呼ばれます。

コンストラクタ関数については、

こちら

で解説しています。

コンストラクタ関数を実体化し、新しいオブジェクトを作成する


// MyFunc という名前の関数を宣言する

function MyFunc (){

}


// コンストラクタ関数を実体化し、新しいオブジェクトを作成する

var obj = new MyFunc();


console.log(obj);
関数は、プロトタイプと呼ばれるオブジェクトを、最初から持っています。
関数からプロトタイプを取得するには、prototype プロパティを使用します。
コンストラクタが所有する、プロトタイプオブジェクトを取得する


// MyFunc という名前の関数を宣言する

function MyFunc (){

}


// 関数が所有する、デフォルトのプロトタイプオブジェクトを取得する

var prototype_obj = MyFunc.prototype;


console.log(prototype_obj);

■「インスタンス」と「プロトタイプ」の関係について

関数が所有していたプロトタイプは、新しいインスタンスと関連付けられます。
新しいインスタンスにとって、プロトタイプは、原型(親)となります。
■インスタンスから、プロトタイプとなるオブジェクトを取得する


// Array コンストラクタが所有する、デフォルトのプロトタイプオブジェクトを取得する

var prototype_obj0 = Array.prototype;


// Array オブジェクトを作成する

var array_obj = new Array();


// プロトタイプ(原型)となるオブジェクトを取得する

var prototype_obj1 = Object.getPrototypeOf(array_obj);


// 同一であるか確認する

console.log(prototype_obj0 === prototype_obj1); // true

プロトタイプは、静的な定義(未実体)ではありません。

プロトタイプは、オブジェクトであり、実体化済みのインスタンスです。

1つのプロトタイプは、複数のインスタンスから参照されます。

プロトタイプは、すべての派生オブジェクト間で共有されます。

プロトタイプは、Live な資産です。

もしプロトタイプ内を変更した場合、すべての派生オブジェクトに影響があります。



// MyFunc という名前の関数を宣言する

function MyFunc (){

}


// デフォルトのプロトタイプオブジェクトを取得する

var prototype_obj = MyFunc.prototype;


// コンストラクタ関数を実体化し、新しいオブジェクトを作成する

var obj0 = new MyFunc();
var obj1 = new MyFunc();
var obj2 = new MyFunc();


// プロトタイプ(原型)となるオブジェクトを取得する

var prototype_obj0 = Object.getPrototypeOf(obj0);
var prototype_obj1 = Object.getPrototypeOf(obj1);
var prototype_obj2 = Object.getPrototypeOf(obj2);


// 同一であるか確認する

console.log(prototype_obj === prototype_obj0); // true
console.log(prototype_obj === prototype_obj1); 
console.log(prototype_obj === prototype_obj2); // true

■「新しく生成するオブジェクト」のプロトタイプを設定する

プロトタイプは、別のオブジェクトに変更する事ができます。
関数の prototype プロパティに、任意のオブジェクトをセットします。
prototype プロパティの設定は、実体化よりも以前に、済ませておく必要があります。

実体化後に変更しても、生成済みのインスタンスには、反映されません。

組み込み関数の prototype プロパティは、変更する事はできません。
新しいプロトタイプオブジェクトを作成し、コンストラクタ関数に登録する


// MyFunc という名前の関数を宣言する

function MyFunc (){
}


// プロトタイプ用のオブジェクトを作成する

var prototype_obj = new Object();
prototype_obj.aaa = "a";
prototype_obj.bbb = "b";
prototype_obj.ccc = "c";

// ------------------------------------------------------------

// ------------------------------------------------------------
MyFunc.prototype = prototype_obj;


// コンストラクタ関数を実体化する

var obj = new MyFunc();


console.log(obj.aaa); // "a"
console.log(obj.bbb); 
console.log(obj.ccc); // "c"
最初から存在するプロトタイプに対して、機能を追加する事もできます。
組み込み関数のプロトタイプは、変更しない方がいいでしょう。


// MyFunc という名前の関数を宣言する

function MyFunc (){
}


// デフォルトのプロトタイプオブジェクトにデータを追加する

var prototype_obj = MyFunc.prototype;
prototype_obj.aaa = "a";
prototype_obj.bbb = "b";
prototype_obj.ccc = "c";


// MyFunc オブジェクトを作成する

var obj = new MyFunc();


console.log(obj.aaa); // "a"
console.log(obj.bbb); 
console.log(obj.ccc); // "c"
プロトタイプはオブジェクトなので、さらに別のプロトタイプを持つ事ができます。

プロトタイプを使って、オブジェクトを数珠つなぎのように連結する事ができます。

このような連結構造を、

プロトタイプチェーン

といいます。



// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){
}


// MyFunc_C という名前の関数を宣言する

function MyFunc_C (){
}


// MyFunc_A オブジェクトを作成する(Object → MyFunc_A)

var obj_a = new MyFunc_A();


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = obj_a;


// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj_b = new MyFunc_B();


// MyFunc_C 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_C.prototype = obj_b;


// MyFunc_C オブジェクトを作成する(Object → MyFunc_A → MyFunc_B → MyFunc_C)

var obj_c = new MyFunc_C();

■オブジェクト(プロトタイプチェーン)のアクセスについて

オブジェクトの任意のプロパティに、読み取りアクセスを試みたとします。

オブジェクトにプロパティが存在する場合、

オブジェクトからデータを取得します。

存在しなかった場合、

次に、プロトタイプにアクセスします。

プロトタイプにプロパティが存在する場合、

プロトタイプからデータを取得します。

存在しなかった場合、さらに次のプロトタイプにアクセスします。

最終的に、プロトタイプチェーン内に存在しなかった場合、未定義となります。
プロトタイプを拡張したオブジェクトのプロパティに読み取りアクセスする


// MyFunc という名前の関数を宣言する

function MyFunc (){
	this.bbb = "b";
}


// デフォルトのプロトタイプオブジェクトを取得する

var prototype_obj = MyFunc.prototype;
prototype_obj.aaa = "a";


// MyFunc オブジェクトを作成する

var obj = new MyFunc();


// 読み取りアクセステスト

console.log(obj.aaa); 
console.log(obj.bbb); 
console.log(obj.ccc); 
プロトタイプチェーン状態にある、オブジェクトのプロパティに読み取りアクセスする


// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){
	this.aaa = "a";
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){
	this.bbb = "b";
}


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = new MyFunc_A();


// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj_b = new MyFunc_B();


// 読み取りアクセステスト

console.log(obj_b.aaa); 
console.log(obj_b.bbb); 
console.log(obj_b.ccc); 
オブジェクトの任意のプロパティに、書き込みアクセスを試みたとします。

プロパティが存在しなかった場合、

自身

のオブジェクトにプロパティが追加されます。

存在する場合、

自身

のプロパティにデータがセットされます。

書き込みアクセスによって変化するのは、自身のオブジェクトのみです。

プロトタイプ内が、汚染する事はありません。
プロトタイプを拡張したオブジェクトのプロパティに書き込みアクセスする


// MyFunc という名前の関数を宣言する

function MyFunc (){
}


// デフォルトのプロトタイプオブジェクトを取得する

var prototype_obj = MyFunc.prototype;
prototype_obj.aaa = "a";
prototype_obj.bbb = "b";


// MyFunc オブジェクトを作成する(Object → MyFunc)

var obj0 = new MyFunc();
var obj1 = new MyFunc();


// 書き込みアクセステスト

obj0.aaa = "書き込みA";
obj1.bbb = "書き込みB";


// 出力テスト(プロトタイプオブジェクト内は変化しない)

console.log(prototype_obj.aaa); 
console.log(prototype_obj.bbb); 

// ------------------------------------------------------------

// ------------------------------------------------------------
console.log(obj0.aaa); 
console.log(obj1.aaa); 
console.log(obj0.bbb); 
console.log(obj1.bbb); 
プロトタイプチェーン状態にある、オブジェクトのプロパティに書き込みアクセスする


// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){
	this.aaa = "a";
	this.bbb = "b";
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){
}


// MyFunc_A オブジェクトを作成する(Object → MyFunc_A)

var obj_a = new MyFunc_A();


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = obj_a;


// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj_b0 = new MyFunc_B();
var obj_b1 = new MyFunc_B();


// 書き込みアクセステスト

obj_b0.aaa = "書き込みA";
obj_b1.bbb = "書き込みB";


// 出力テスト(プロトタイプオブジェクト内は変化しない)

console.log(obj_a.aaa); 
console.log(obj_a.bbb); 

// ------------------------------------------------------------

// ------------------------------------------------------------
console.log(obj_b0.aaa); 
console.log(obj_b1.aaa); 
console.log(obj_b0.bbb); 
console.log(obj_b1.bbb); 

読み取りアクセス順は、プロパティと同じです。

プロトタイプが保有している関数オブジェクトにも、アクセスできます。

プロトタイプから供給しているメソッドを実行した場合、プロトタイプ自身のプロパティや、プロトタイプ内のローカル環境に、書き込みアクセスできます。
この場合、プロトタイプ内は汚染してしまうので、回避する必要があります。
プロトタイプチェーン状態にある、オブジェクトのメソッドを実行する


// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){

	
	// プライベートな変数
	
	var _local_a = "a";

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	this.getA = function (){
		return _local_a;
	};

	
	// Aにデータをセットするメソッド
	
	this.setA = function (v){
		_local_a = v;
	};
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){

	
	// プライベートな変数
	
	var _local_b = "b";

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	this.getB = function (){
		return _local_b;
	};

	
	// Bにデータをセットするメソッド
	
	this.setB = function (v){
		_local_b = v;
	};
}


// MyFunc_A オブジェクトを作成する(Object → MyFunc_A)

var obj_a = new MyFunc_A();


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = obj_a;


// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj_b0 = new MyFunc_B();
var obj_b1 = new MyFunc_B();


// 出力テスト

console.log(obj_a.getA());  
console.log(obj_b0.getA()); 
console.log(obj_b1.getA()); 
console.log(obj_b0.getB()); 
console.log(obj_b1.getB()); 

// ------------------------------------------------------------

// ------------------------------------------------------------
obj_b0.setA("書き込みA");
obj_b1.setB("書き込みB");


// 出力テスト(プロトタイプから供給しているメソッドを実行すれば、プロトタイプ内を変化させる事ができる)

console.log(obj_a.getA());  
console.log(obj_b0.getA()); 
console.log(obj_b1.getA()); 


// ------------------------------------------------------------

// ------------------------------------------------------------
console.log(obj_b0.getB()); 
console.log(obj_b1.getB()); 
パブリックなメソッドは、できるだけプロトタイプ側に実装します。

プロトタイプに1つの機能を追加すれば、すべての派生オブジェクトから利用できます。

しかし、

パブリックなメソッドは、プロトタイプ内が汚染する可能性があります。

プロトタイプ内が汚染しないように、注意して実装する必要があります。

クロージャに書き込みアクセスしないようにします。

プロトタイプ内で必要な変数は、すべてプロパティとして公開します

外部からアクセスを禁止したいプロパティは、命名を工夫します。

先頭や後尾にアンダーバーを付けて、怪しげな名前にするなどの方法があります。

プロトタイプのメソッド内から、プロパティにアクセスする場合、this 演算子を使用します。


// MyFunc という名前の関数を宣言する

function MyFunc (){

}


// MyFunc のデフォルトのプロトタイプオブジェクトを拡張する

(function(){

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	var self = MyFunc.prototype;

	
	// ローカルで使用する変数(プロパティとして公開)
	
	self._data = "初期値";

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	self.getData = function (){
		return this._data;
	};

	
	// データをセットするメソッド
	
	self.setData = function (v){

		
		this._data = v;

		
		//self._data = v;
	};

})();




// MyFunc オブジェクトを作成する(Object → MyFunc)

var obj0 = new MyFunc();
var obj1 = new MyFunc();


// 出力テスト

console.log(obj0.getData()); 
console.log(obj1.getData()); 

// ------------------------------------------------------------

// ------------------------------------------------------------
obj0.setData("書き込み");


// 出力テスト

console.log(obj0.getData()); 
console.log(obj1.getData()); 


// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){

	
	// 自身のオブジェクト
	
	var self = this;

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	self._local_a = "a";

	
	// Aからデータを取得するメソッド
	
	self.getA = function (){
		return this._local_a;
	};

	
	// Aにデータをセットするメソッド
	
	self.setA = function (v){
		this._local_a = v;
	};
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){

	
	// 自身のオブジェクト
	
	var self = this;

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	self._local_b = "b";

	
	// Bからデータを取得するメソッド
	
	self.getB = function (){
		return this._local_b;
	};

	
	// Bにデータをセットするメソッド
	
	self.setB = function (v){
		this._local_b = v;
	};
}


// MyFunc という名前の関数を宣言する

function MyFunc (){

}


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = new MyFunc_A();


// MyFunc 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc.prototype = new MyFunc_B();




// MyFunc オブジェクトを作成する(Object → MyFunc_A → MyFunc_B → MyFunc)

var obj0 = new MyFunc();
var obj1 = new MyFunc();


// 出力テスト

console.log(obj0.getA()); 
console.log(obj1.getA()); 

console.log(obj0.getB()); 
console.log(obj1.getB()); 

// ------------------------------------------------------------

// ------------------------------------------------------------
obj0.setA("書き込みA");
obj1.setB("書き込みB");


// 出力テスト

console.log(obj0.getA()); 
console.log(obj1.getA()); 

console.log(obj0.getB()); 
console.log(obj1.getB()); 
プロトタイプを共有せずに、クラスのような静的な継承を実現します。

この方法は、インスタンスごとにメソッドが用意されるため、合理的ではありません。

そのかわり、カプセル化が実現できます。

1.プロトタイプを指定して、新しいオブジェクトを生成する

プロトタイプオブジェクトを指定して、新しくオブジェクトを作成する関数を用意します。

Object.create()

関数を使って実現する事もできます。

プロトタイプを指定して新しいオブジェクトを生成する関数


// プロトタイプを指定して新しいオブジェクトを生成する関数

function ObjectCreateByPrototype(prototype){
	var f = function (){};
	f.prototype = prototype;
	return new f();
}
2.コンストラクタ関数を実行せずに、新しいオブジェクトを作成する

引数に、関数の

prototype

プロパティにあるプロトタイプオブジェクトを指定します。

すると、

コンストラクタ関数を実行すること無く、オブジェクトを作成する事ができます

これを利用すると、先にプロトタイプ用オブジェクトを用意し、後で初期化関数を実行する事ができます。
コンストラクタ関数を実行せずに、インスタンスを生成する


// プロトタイプを指定して新しいオブジェクトを生成する関数

function ObjectCreateByPrototype(prototype){
	var f = function (){};
	f.prototype = prototype;
	return new f();
}


// MyFunc という名前の関数を宣言する

function MyFunc (){
	console.log("コンストラクタ関数が実行された");
}


// コンストラクタ関数を実行せずに、MyFunc オブジェクトを作成する

var obj = ObjectCreateByPrototype(MyFunc.prototype);


// 出力テスト

console.log(obj instanceof MyFunc); 
console.log(obj instanceof Object);  // true
3.コンストラクタ関数内で、派生元コンストラクタ関数を実行し初期化する

継承したいコンストラクタ関数内で、派生元コンストラクタ関数を実行し、初期化します。

コントラクタ関数の実行には、

Function.apply()

メソッドを使用するといいでしょう。

これで、new 演算子を使用するたびに、すべてのコンストラクタ関数を実行する事ができます。

プロトタイプオブジェクトは、ダミーです。変更される事はありません。

すべての初期化処理は、新しく生成されるオブジェクトに施されます。
継承したいコンストラクタ関数内で、派生元コンストラクタ関数を実行し初期化する


// プロトタイプを指定して新しいオブジェクトを生成する関数

function ObjectCreateByPrototype(prototype){
	var f = function (){};
	f.prototype = prototype;
	return new f();
}


// MyFunc_A コンストラクタ関数

function MyFunc_A (){
}


// MyFunc_B コンストラクタ関数

function MyFunc_B (){

	
	MyFunc_A.apply(this,arguments);
}


// MyFunc_C コンストラクタ関数

function MyFunc_C (){

	
	MyFunc_B.apply(this,arguments);

}


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = ObjectCreateByPrototype(MyFunc_A.prototype);


// MyFunc_C 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_C.prototype = ObjectCreateByPrototype(MyFunc_B.prototype);




// MyFunc_A オブジェクトを作成する(Object → MyFunc_A)

var obj_a = new MyFunc_A();


// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj_b = new MyFunc_B();


// MyFunc_C オブジェクトを作成する(Object → MyFunc_A → MyFunc_B → MyFunc_C)

var obj_c = new MyFunc_C();


// 出力テスト

console.log(obj_a instanceof Object); // true
console.log(obj_a instanceof MyFunc_A); 
console.log(obj_a instanceof MyFunc_B); 
console.log(obj_a instanceof MyFunc_C); 

console.log(obj_b instanceof Object); // true
console.log(obj_b instanceof MyFunc_A); 
console.log(obj_b instanceof MyFunc_B); 
console.log(obj_b instanceof MyFunc_C); 

console.log(obj_c instanceof Object); // true
console.log(obj_c instanceof MyFunc_A); 
console.log(obj_c instanceof MyFunc_B); 
console.log(obj_c instanceof MyFunc_C); 
プロトタイプを共有せずに、クラスのような静的な継承を実現する


// プロトタイプを指定して新しいオブジェクトを生成する関数

function ObjectCreateByPrototype(prototype){
	var f = function (){};
	f.prototype = prototype;
	return new f();
}


// MyFunc_A という名前の関数を宣言する

function MyFunc_A (){

	
	// プライベートな変数
	
	var _local_a = "a";

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	this.getA = function (){
		return _local_a;
	};

	
	// Aにデータをセットするメソッド
	
	this.setA = function (v){
		_local_a = v;
	};
}


// MyFunc_B という名前の関数を宣言する

function MyFunc_B (){

	
	// 派生元コンストラクタ関数を実行する
	
	MyFunc_A.apply(this,arguments);

	
	// プライベートな変数
	
	var _local_b = "b";

	// ------------------------------------------------------------
	
	// ------------------------------------------------------------
	this.getB = function (){
		return _local_b;
	};

	
	// Bにデータをセットするメソッド
	
	this.setB = function (v){
		_local_b = v;
	};
}


// MyFunc_B 関数を実体化した時に、インスタンスのプロトタイプとなるオブジェクトを指定する

MyFunc_B.prototype = ObjectCreateByPrototype(MyFunc_A.prototype);




// MyFunc_B オブジェクトを作成する(Object → MyFunc_A → MyFunc_B)

var obj0 = new MyFunc_B();
var obj1 = new MyFunc_B();


// 出力テスト

console.log(obj0.getA()); 
console.log(obj1.getA()); 
console.log(obj0.getB()); 
console.log(obj1.getB()); 

// ------------------------------------------------------------

// ------------------------------------------------------------
obj0.setA("書き込みA");
obj1.setB("書き込みB");


// 出力テスト

console.log(obj0.getA()); 
console.log(obj1.getA()); 
console.log(obj0.getB()); 
console.log(obj1.getB());