「何となくJavaScriptを書いていた人が一歩先に進むための本」の感想・備忘録2

スポンサーリンク
「何となくJavaScriptを書いていた人が一歩先に進むための本」の感想・忘備録1の続き

スコープチェーン

スコープチェーンとは「どんな順序で変数やプロパティを参照するかのルール」のこと。JavaScriptでは関数を呼び出す度に内部に、ローカル変数を管理するCallオブジェクトというものが生成される。スコープチェーンは、GlobalオブジェクトとCallオブジェクトを連結したリストのようなもの。

Globalオブジェクトを末端とし、Callオブジェクトが生成される度にその先端に紐付けているようなイメージになる。そして、JavaScriptの変数解決の仕組みは、このリストを先頭から順に辿って、一番最初に見つかった値を採用する仕組みとなっている。

var x = 'Global';
var y = 'Global';
function outer() {
    var x = 'outer';
    function inner() {
        var x = 'inner';
        console.log(x); // inner
        console.log(y); // Global
    }
    console.log(x); // outer
}
console.log(x); // Global

上記の例では以下のようなスコープチェーンが生成されます。
「innerFuncのCallオブジェクト」—-「outerFuncのCallオブジェクト」—-「Globalオブジェクト」

クロージャ

クロージャとは「ローカル変数の状態を保持できる関数」「関数内の関数で、ローカル変数を参照しているもの」のこと。

function myFunc(n) {
    var cnt = n;
    var innerFunc = function() { // この無名関数がクロージャ
        return ++cnt;   // スコープチェーンにより、外側のローカル変数 cnt を参照
    }
    return innerFunc;
}
var func = myFunc(0);
console.log(func()); // 1
console.log(func()); // 2

クロージャのスコープチェーン

上記の例ではグローバル変数funcにinnerFunc関数が格納される。その結果、funcがローカル変数cntを参照し続けることになり、cntはfuncが有効な限り破棄されない。

ちなみに、以下のようなスコープチェーンが生成される。
「innerFuncのCallオブジェクト」—-「myFuncのCallオブジェクト」—-「Globalオブジェクト」
このチェーンはinnerFunc関数が有効な限り保持されるが、innerFunc関数はfunc変数に格納されるためmyFunc関数が終了しても破棄されない。

クロージャの用途

  1. グローバル変数の代わりとして
    オブジェクト指向のような構文が実現できる。
  2. メンバに加える処理がシンプルなオブジェクトの代わりとして
    コードがスタイリッシュになる。
    例えば、ボタンを押した回数をインクリメントするメソッドはobjBtn.addCount()と書くよりも、クロージャを使ってclickBtn()と書いた方がカッコいい。クロージャを使わなければ処理が実現できないということは決してない。しかし、クロージャを理解すること=JavaScriptの奥深さを理解すること、だと筆者は考えている。

プロトタイプ

プロトタイプは「クラスの代わり」と考えるとよい。JavaScriptにはクラスの概念がないため、代わりにプロトタイプを駆使してオブジェクト指向ライクな実装をすることができる。
※ES6でクラスが用意されたが、普及状態が微妙なためここでは存在しないものとして考える

オブジェクトにはメンバを追加するためのprototypeプロパティというものが用意されている。意識して使わない限り空のオブジェクトを参照している。 prototypeプロパティに何らかの格納をした場合、インスタンス化した先のオブジェクトに引き継ぐことができる。 以下の例ではメソッドを追加している。

var Phone = function(name) {
this.name = name;
}
Phone.prototype.getName = function() {
return this.name;
}
var smartPhone = new Phone('smart phone');
console.log(smartPhone.getName());

プロトタイプの意味

コンストラクタに定義するのではなく、わざわざprototypeを使ってメソッドを定義するのは理由がある。 それは「メモリ使用量の削減のため」。

インスタンス化すると、コンストラクタで定義されているメンバ分のメモリ領域がその都度確保される。プロパティはインスタンス毎に違う値になるからそれで問題ないが、メソッドは個別にメモリを確保するのはただの無駄になる。ここでprototypeプロパティの出番である。インスタンス化されたオブジェクトは、prototypeプロパティへの参照を持る。prototype格納したメソッドも参照することができ、自前でメソッドを持つ必要はない。

今後、メソッドは必ずprototypeプロパティで管理するように徹底しましょう。

プロトタイプの意味(その2)

prototypeプロパティにはもう1つのメリットがある。それは「インスタンス化した後にprototypeにメソッドを追加しても、そのメソッドを追加する」ことができること。
「インスタンスはprototypeへの参照を持つ」ことを考えれば、特に不思議なことではない。

var Phone = function(name) {
    this.name = name;
};
var smartPhone = new Phone('smart phone');
Phone.prototype = {
    getName : function() {
        return this.name;
    };
};
console.log(smartPhone.getName());

なお、上記の例ではprotoypeの定義をドット演算子ではなくオブジェクトリテラル{}を使っている。実際の利用シーンでは複数のメソッドを定義すると思うので、こちらの方が好ましいでしょう。

プロトタイプチェーン

プロトタイプチェーンとは、JavaScriptで継承を行うための仕組みのこと。
以下の例のポイントは、何と言ってもSmartPhone.prototype = new Phone();の部分である。これがプロトタイプチェーンの正体で、継承したいオブジェクトのインスタンスを自身のprototypeに格納するのである。

var Phone = function() {};
Phone.prototype = {
    getName : function() {
        return this.name;
    }
};
var SmartPhone = function(name) {
    this.name = name;
}
SmartPhone.prototype = new Phone();
SmartPhone.prototype.tap = function() {
    console.log('tap!');
}:
MyPhone = new SmartPhone('tanaka');
console.log(smartPhone.getName()); // tanaka
console.log(smartPhone.tap()); // tap!

ECMAScriptとは

ECMAScriptはEcma Internationalという標準化団体によって標準化された言語仕様のこと。ブラウザ上で動作するJavaScriptはこの仕様をもとに実装されている。

ECMAScript6というのは2015年6月に採用されたバージョンのこと。現段階(2016年5月)では普及が進んでいないが、今のうちに概要ぐらいは頭に入れておきたいもの。

コメント