パーフェクトJavaScript読書メモ 6章

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)


基本的に自分にとって大切な部分を抜粋させてもらってるメモです。言い方などが一部自分語になっている部分はあります。あくまで自身のための要約ですので、間違って解釈している部分等あるかもしれません。もちろんそのまま引用させていただいている文章に間違いはないはずですし、大体そうなのですが、間違ってたら申し訳ないので、書き添えさせていただきました。すばらしい書籍ですので、JavaScriptを使う初心者すべての方におすすめです。

6-2 関数呼び出しの整理

関数自体に分類があるわけではなく、呼び方の違いの分類であることに注意。つまり、ある関数を取り上げてそれをメソッドと呼ぶのは厳密には正しくない。正しくはその関数をメソッド呼び出ししたかどうかである。

関数呼び出しの分類
  1. メソッド呼び出し レシーバオブジェクト経由での関数呼び出し(applyとcallの呼び出しも含む)
  2. コンストラクタ呼び出し new式での関数呼び出し
  3. 関数呼び出し 上記2つ以外の関数呼び出し

6-5 関数はオブジェクト

関数はオブジェクトの一種です。内部的にはFunctionオブジェクトを継承します。これは以下のようにconstructorプロパティで確認できます。

js> function f(){}
js> f.constructor
function Function() {[native code]}

以下の大局的に見ると、実体(名なしのオブジェクト)の生成とそれを参照する名前を結びつけるコードという共通点があります。

var obj = {};
var obj = new MyClass();
var obj = function(){};
function obj(){}

6-7 入れ子の関数宣言とクロージャ

6-7-2 クロージャの仕組み

入れ子の関数宣言
function f(){
	function g(){
		alert('g is called');
	}
	g();
}
f(); // g is called

おさらいですが、JavaScriptの変数はプロパティです。それはグローバルとローカルという2つのスコープのどちらかにわかれます。グローバルはグローバルオブジェクトのプロパティです。一方関数内で宣言された変数がローカル変数であり、このローカル変数(関数のパラメータ変数も同様)とはその関数が呼ばれた時に暗黙的に生成されるCallオブジェクトのプロパティなんです。一般に変数の生存期間は関数が呼ばれてからぬけるまでだが、ここでクロージャを考慮にいれると抜けた後もアクセス可能なローカル変数が存在できるのです。

トップレベルコードでの関数fの宣言は、関数オブジェクトの生成と変数fによる関数オブジェクトの参照を意味します。変数fはグローバルオブジェクトのプロパティです。
JavaScriptでは関数を呼ぶたびにCallオブジェクトが暗黙に生成されます。関数f呼び出し時のCallオブジェクトを便宜上Call-fオブジェクトと呼びます。Callオブジェクトは関数呼び出しが終わると消滅します。
関数f内の関数gの宣言は関数gに対応する関数オブジェクトを生成する。名前gはCall-fオブジェクトのプロパティです。Callオブジェクトは関数呼び出しごとに独立しているので、関数gを呼ぶと別のCallオブジェクトが暗黙に生成されます。このCallオブジェクトを便宜上Call-gオブジェクトと呼ぶ。
関数gを抜けるとCall-gオブジェクトは自動で消滅します。同様に関数fを抜けるとCall-fオブジェクトが消滅します。この時、gが参照する関数オブジェクトは、プロパティgがCall-fオブジェクトとともに消滅するので、参照元がなくなる結果、消滅します(ガベージコレクションのため)

入れ子の関数とスコープ
var n = 'global';
function f(){
	var n = 'fff';
	function g(){
		var n = 'ggg';
                alert('n is ' + n);
		alert('g is called');
	}
	g()
}
f();

変数nの3箇所の宣言文の宣言場所によってnの値がどのようにアラートされるのか。関数内での変数の解決はCallオブジェクトのプロパティ、グローバルオブジェクトのプロパティの順で探す。上の例の場合は、g()が呼ばれた時にCall-gオブジェクトが生成されると考えると、関数内の変数はその関数のCallオブジェクトのプロパティなんだから、まずg()内部の変数nを探し、なければ外側の関数のCallオブジェクト(この場合だとCall-fオブジェクト)、さらになければ、この場合、グローバルオブジェクトのプロパティであるところの一番外側のnを探し当てるということになる。これをスコープチェーンと呼ぶ。

入れ子の関数を返す
クロージャ
function f(){
	var n = 123;
	function g(){
		alert('n is ' + n);
		alert('g is called');
	}
	return g;
}
var g2 = f();
g2();

関数fを呼んだ時のCallオブジェクト(Call-fオブジェクト)のプロパティgが参照していた関数オブジェクトをg2が参照します。参照元がある限りガーベージコレクションの対象にならないので、名前g2が有効な限り、関数オブジェクトも生きている。この関数オブジェクトはCall-fオブジェクトへの参照を持ちます(スコープチェーンのために使う)結果、名前g2から参照されるこの関数オブジェクトが残る限り、Call-fオブジェクトも残る。これが関数fを抜けてもローカル変数nが生きている理由です。

6-7-4 名前空間の汚染を防ぐ

//関数リテラル(無名)をその場で呼び出す
	//関数リテラルの返り値は関数なので変数sumは関数
	var sum = (function() {
		//関数の外部からこの名前にアクセスできない
		//事実上プライベートな変数
		//通常、関数の呼び出しが終わればアクセス出来ない名前だが
		//返り値の無名関数のなかから使える
		var position = {
			x : 2,
			y : 3
		};

		//同じく関数の外部からアクセスできなプライベート関数
		//名前をsumにしても問題ないが、余計な混乱を避けるためここでは別名にしている
		function sum_internal(a, b) {
			return Number(a) + Number(b);
		}

		//上記2つの名前を強引に使うだけの恣意的な返り値
		return function(a, b) {
			alert('x = ' + position.x);
			return sum_internal(a, b);
		}
		
	})();
	
        //呼び出し
	alert(sum(3,4)); // 7
	alert(sum_internal(3,4)); //sum_internal is not defined
	alert(position.x); //position is not defined


抽象化すると以下の様な感じ

(function() { 関数本体 })();

関数スコープによる名前の閉じ込めと、クロージャで関数を抜けた後も生きている名前、という2つの特性を活用した情報隠蔽です。


関数リテラルでなく、以下のようにオブジェクトリテラルを返しても情報隠蔽の目的は達成できる。

	var obj = (function() {
		//関数の外部からこの名前にアクセスできない
		//事実上プライベートな変数
		var position = {
			x : 800,
			y : 777
		};

		//同じく関数の外部からアクセスできないプライベート関数
		function sum_internal(a, b) {
			return Number(a) + Number(b);
		}

		//上記2つの名前を強引に使うだけの恣意的な返り値
		return {
			sum : function(a, b) {
				return sum_internal(a, b);
			},
			x : position.x,
			y : position.y
		};
	})();

	//呼び出し
	alert(obj.sum(10, 10)); //20
	alert(obj.x); // 800
	alert(obj.y); // 777

6-7-5 クロージャとクラス

//クラス定義もどき
function MyClass(x,y){
	//フィールド相当
	this.x = x;
	this.y = y;
	//メソッド相当
	this.show = function(){
		alert(this.x + " " + this.y);
	}
}

var obj = new MyClass(3,2);
obj.show();
alert(obj.x); // 3 アクセス可能
alert(obj.y); // 2 アクセス可能

プロパティに外からアクセスできてしまう。それなので、こうしてあげよう

function myclass(x, y){
	return { show : function(){alert(x + " " + y);}}
}
var obj = myclass(3,7);
obj.show();
alert(obj.x); //undefined 情報隠蔽
alert(obj.y); //undefined 情報隠蔽


もう一つの例を見て、クロージャを使うクラスの形をつかもう

function counter_class(init){
	var cnt = init || 0;
	
	//必要であればここにプライベートな変数や関数を宣言
	
	return {
		//公開メソッド
		show: function(){alert(cnt);},
		up: function(){cnt++; return this;},
		down: function(){cnt--; return this;}
	};
}

var counter1 = counter_class();
counter1.show(); // 0と表示
counter1.up(); 
counter1.show(); // 1と表示

var counter2 = counter_class(10);
counter2.up().up().up().show(); // 13と表示 メソッドチェーン

6-8-2 JavaScriptとコールバック

クロージャとコールバック
var emitter = {
		// 複数のコールバック関数を登録できるように配列で管理
		callbacks:[],
		//コールバック関数登録メソッド
		register: function(fn){
			this.callbacks.push(fn);
		},
		//イベント発火処理
		onOpen:function(){
			for each ( var f in this.callbacks){
				f();
			}
		}
};

//クロージャをコールバック関数に登録
//ここで登録されるのはfunction(){alert(msg + 'is called');}という本文を持った関数オブジェクトですね
emitter.register((function(){ var msg = 'closure1'; return function(){alert(msg + 'is called');}})());
emitter.register((function(){ var msg = 'closure2'; return function(){alert(msg + 'is called');}})());

//イベント発火のエミュレーション(コールバック関数の呼び出し)
emitter.onOpen();