SlideShare a Scribd company logo
最強オブジェクト指向言語
JavaScript 再入門!
自己紹介
‣ ノジマユウジ @yuka2py
‣ 株式会社フォーエンキー
代表取締役
‣ システム開発、
グラフィックデザイン、
DTPや印刷なども
‣ PythonとJavaScriptが大好
き(Dartに興味深々)
‣ 様々なWebアプリケーション
設計・構築・運用。またWP
によるシステム開発、プラグ
イン開発などが主なお仕事
‣ おしゃれも大好き☆
リボンやお花が好き☆
‣ 参加コミュニティ
●
WordBench 神戸
●
HTML5-WEST.jp
●
日本Androidの会 神戸支部
絶賛
お仕事募
集中
去年のボク
Python
1%Design
15%
iOS
4%
Android
10%
Web(PHP/JS)
10%
Windows(C#)
20%
お嫁
40%
お 嫁
W i n d o w s ( C # )
W e b ( P H P / J S )
A n d r o i d
i O S
D e s i g n
P y t h o n
2012年11月2日
株式会社フォーエンキー調べ
最近のボク
Python
1%
Design
5%
Android
5%
Windows
15%
Web/WordPress
25%
お嫁
50%
お 嫁
W e b / W o r d P r e s s
W i n d o w s
A n d r o i d
D e s i g n
P y t h o n
2013年6月1日
株式会社フォーエンキー調べ
愛
恐
はじめに
本セッションでは、JavaScript でオブジェクト指向プログ
ラミングを行う際に備えておくことが望ましい、基礎知識
や概念について解説します。
対象者
・JavaScript でアプリケーションを構築できる方
・JavaScript におけるオブジェクト指向プログラミングの
 実現手法や原理への理解を深めたい方
・Java 的なクラスベースの言語との違いに違和感や混乱を
 感じてらっしゃる方
Simple and Powerfull
JavaScript はとてもシンプルな言語仕様でありながらも、
とても強力にオブジェクト指向プログラミングをサポートしています。
var obj = {}
JavaScript はオブジェクトが基本です。
オブジェクトの構造は、単純なキーと値の組み合わせによるハッシュテーブルのよ
うなものです。とても大切な概念です。難しく考え過ぎないでください。
var obj = {
key: value
}
関数は、JavaScript において「第一級オブジェクト」…簡単に言うと「値」です。
値ですから、変数に代入したり、取り出したり、受け渡しが制約なく行えます。
var f = function() {...}
もちろん、関数をオブジェクトのプロパティに格納する事もできます。オブジェクトが
単純なハッシュテーブルで、関数も値であるならば、それはとても自然なことですね。
var obj = {
key: function() {...}
}
言い換えると、オブジェクトのメソッドはそれをメソッドとして持つオブジェクト
に束縛されているのではなく、 オブジェクトによってただ参照されているだけです。
var obj = {
key: function() {...}
}
おしながき
‣ クラスはあるか?
‣ プロトタイプチェイン
●
JSでのオブジェクト指向プ
ログラミングの基礎概念
‣ オブジェクトの生成
●
newとコンストラクタ関数
●
プロトタイプチェインとの
関連
‣ スコープチェイン
●
var の意味
●
スコープチェインの正体
‣ クロージャ
●
定義された環境を束縛する
‣ this
●
thisの決定
●
thisのコントロール
クラスはあるか?
追記。本資料では、いわゆる「Java言語的なクラス」を「クラス」と定義してお話しています。それは、こ
のスライドを書きはじめた動機が、Web上でJava言語的OOPで解釈しようとする記事が多くあり、それが
JSを学ぼうとされる方の理解を混乱させているように思えたので、その混乱を解消したかったからです。
また、ES.next の class 構文も説明するには早そうなこと、かつさらに理解を難しくするものとして割愛。
なお、これらの省略や定義付けに違和感を覚えられる方は既に本資料の内容は理解済みとの認識です! (*'-'*)
QJavaScript で
クラスは作れますか?
(?_?)
A無理ポ。
Qでは、クラスっぽいものは
作れますか?
(?_?)
Aだから、無理ポ。
だって「オブジェクト」しか
無いんだもん。
「クラス」って何それ?
おいしいの?
(・ ・)
クラスっぽいものは出来るっていう方も
いらっしゃると思いますが…
それが理解を難しくする
と僕は思う
クラスが無いのに、クラスの概念を持ち込んで
理解しようとしたら、メンドクサクなりそうでないっすか?
そもそも…
オブジェクト指向に
クラスなんて要らない。
クラスや型は、オブジェクト指向を
扱い易くするためのテクニックのひとつに過ぎません。
必要なのはオブジェクト。
だって「オブジェクト指向」だしぃ∼
だから JavaScript は
代わりに、
もっと面白い方法を選んだ。
(↑あくまでも個人的主観)
それが…
プロトタイプチェイン
 「クラス」とは?の前に
似たオブジェクトを
量産するための仕組み
Class (型) クラス
クラスという「型」を元にして、
オブジェクトを生成するイメージ
クラスから生成されたオブジェクトは
クラスの特性(属性や機能)を受け継ぐ
Object(実体)
new
Object(実体)
new
Object(実体)
new
でも、
クラスを使わなくても、
同じ機能や属性(性質)を持った
オブジェクトを量産できれば
OKですよね。
では、JavaScript では?
Object (実体)
(prototype)
プロトタイプ
オブジェクトは、自分自身が備えて
いない特性(機能や属性)を、別の
オブジェクトにお願い(委譲)する
イメージ。この時、お願いする先を
「プロトタイプ」と言ったりします。
Object(実体)
dele
gate
Object(実体)
dele
gate
Object(実体)
dele
gate
…な、感じで実現します。
コードで見てみる。
3つのオブジェクトが登場し
ています。
ここで、3つ目の、objC は
__proto__ 属性以外何も持っ
ていませんが、I Love Nicole
と表示されます。
I Love Nicole
var objA = {
name : 'Yome',
say : function () {
alert('I Love ' + this.name);
}
};
var objB = { name : 'Nikole' };
objB.__proto__ = objA;
var objC = { };
objC.__proto__ = objB;
objC.say();
ここでポイントになるのは、
__proto__という属性。
この__proto__を介して、
objC が objB を経て objA ま
での参照を保持できているこ
とを確認してください。
所有
所有
var objA = {
name : 'Yome',
say : function () {
alert('I Love ' + this.name);
}
};
var objB = { name : 'Nikole' };
objB.__proto__ = objA;
var objC = { };
objC.__proto__ = objB;
objC.say();
I Love Nicole
が表示される仕組み
objC.say() メソッドのコール
(objB)
objC.say
無い
アッタ!
無い
objC.__proto__.say
(objA)(objB)
objC.__proto__.__proto__.say
objC に対して say() がコールされた時、JavaScript は、まず objC 自身が「say」というキーの
値を持っているか調べます。しかし、見つからないので、次にその__proto__ …つまり objB を検
索します。また見つからないので、objB の __proto__ = objA を検索し、say を見つけます。
objC.name 属性の参照
objC.name
アッタ!
無い
(objB)
objC.__proto__.name
(objA)(objB)
objC.__proto__.__proto__.say 使われ
ない
同様に、objC の name を参照した時、JavaScript は objC 自身が name を持っているか確認し
ます。しかし、見つからないので、次にその__proto__ …つまり objB を検索し、name を見つ
けました。必要な名前が見つかった時点で、検索は終了します。
このように、自分に無い機能や属性を、
__proto__ に入っている別のオブジェクトから
連鎖的に検索して探す仕組みが…
obj.__proto__.__proto__.property
obj.__proto__.__proto__.property
プロトタイプチェイン
では、同じ特性を持った
オブジェクトを量産するには?
同じプロト
タイプを所有
すればOK
var objA = {
name : 'Yome',
say : function () {
alert('I Love ' + this.name);
}
};
var objB = { name : 'Nikole' };
objB.__proto__ = objA;
var objC = { name : 'Gyu-Ri' };
objC.__proto__ = objA;
objB.say(); // I Love Nikole
objC.say(); // I Love Gyu-Ri
クラスベースと
較べてみる。
‣ クラスベース ‣ プロトタイプベース
class A
class B
class C
obj A
obj B
obj C
obj A of B
obj B of C
obj C of C
obj E
obj F
extends
extends
(delegete)
new
new
(delegete)
オブジェクト
だけの世界
クラスから
オブジェクト
を作成
obj D
(delegete)
new
(delegete)
(delegete)
クラスベースではクラスという型を拡張し、そこからインスタンスを作成するイメージです。
一方プロトタイプベースでは、必要な特性を持った他のオブジェクトを利用するイメージです。
プロトタイプチェインは、
以上です。
あれ?
prototype 忘れてない?
いえいえ、以上っすぅ。
だって prototype 無くても
プロトタイプチェインできたし (・ ・)
実は、prototype 属性は、
プロトタイプチェインではなく、
オブジェクトの生成で使われるもの。
ネット上の情報で、ここを一緒に解説している例を見掛けますが、
それが「ちょっと分かり難いなー」と個人的には思っています。
プロトタイプチェインはあくまでも__proto__の連鎖的検索であって、
prototype は検索対象では無いからです。
分けて
考えると
イイらしい
では…
オブジェクトの生成
とはいえ…
実は __proto__は標準仕様ではありません。
つまり、__proto__ が利用できない環境もあり、だから通常、このようなことはしません。
ここまでは、プロトタイプチェインの概念を分かり易くするために __proto__ を使って
説明していましたが、そういうことなので、実際のプログラミングではこんなことしな
いでくださいねー。 (・ ・)/
では、逆に、良く見かけるのは…
obj.__proto__ = otherObj
あまり見たコトない…
これは new 演算子とコンストラクタ関数によるオブジェクトの生成です。
JavaScript におけるオブジェクトの生成で、よく見られるコードです。
var o = new Person('Nicole')
このあたりの記法が、クラスベースの言語を学んだ方を惑わしますね♪
こっちのが、
よく見る感じ!
この手法による
オブジェクト生成について
見てみます。
準備
利用
準備して、利用しているのは、間違いありません。
よく見るコードの例
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
利用
クラス
定義?
よく見るコードの意味
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
クラスは存在しないので、もちろん「クラス定義」ではありません。
prototype
の拡張
コンスト
ラクタ関数
の定義
よく見るコードの意味
利用
とこんな感じで、ちゃんと分けて理解しておくのが良いと思います。
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
コンストラクタ関数とは?
オブジェクトの初期化に
使われる関数。
new 演算子と組み合わせて関数をコールすることで、関数はコンストラクタ関数
として実行され、オブジェクトの生成に利用されます。
new 演算子は?
オブジェクトの
生成・継承・初期化を行う
指示をしてます。
このようなコードを実行した時、
実際にはどんな意味合いになるか?
var o = new Person('Nicole')
こんなふうに new した時は…
だいたいこんな意味
var newObj = {};
newObj.__proto__ = Person.prototype;
Person.apply(newObj, arguments);
return newObj;
※分かり易さの為に詳細を割愛しています。
擬似
コード
だいたいこんな意味
var newObj = {};
newObj.__proto__ = Person.prototype;
Person.apply(newObj, arguments);
return newObj;
①新しい Object が
生成される
new する度に、新しい個別のオブジェクトが生成されます。
だいたいこんな意味
var newObj = {};
newObj.__proto__ = Person.prototype;
Person.apply(newObj, arguments);
return newObj;
②プロトタイプのオブ
ジェクトの参照を代入
コンストラクタ関数の Person.prototype を、新しいオブジェクトの __proto__ に
参照を代入し、プロトタイプチェインの仕組みで、その特性を継承します。
だいたいこんな意味
var newObj = {};
newObj.__proto__ = Person.prototype;
Person.apply(newObj, arguments);
return newObj;
③ newObj を
this に、コンストラクタ
関数を実行
これによって、コンストラクタ関数の中で、生成される新しいオブジェクトを this と
して参照し、その属性をセットしたりできるようになります。
だいたいこんな意味
var newObj = {};
newObj.__proto__ = Person.prototype;
Person.apply(newObj, arguments);
return newObj;
④完成した
オブジェクトを返す
Person.prototype の特性を継承し、コンストラクタ関数によって初期化済みの、
新しいオブジェクトが返され、プログラムで利用できるようになります。
ポイント
はココ
newObj.__proto__ = Person.prototype;
Person.prototype オブジェクトの機能が、
newObj で利用できるようになる!
(プロトタイプチェインの仕組み)
整理すると…
‣ __proto__
●
プロトタイプチェインで
検索されるプロトタイプ
オブジェクトが入ってる
‣ prototype
●
プロトタイプオブジェク
トの控え室
●
new とコンストラクタ
関数でオブジェクトを生
成する時に、__proto__
に代入される
プロトタイプチェインで
使われるもの
オブジェクト生成で
使われるもの
コードで見てみる。
余計分かり難くなったらごめんなさいですが、
__proto__ と prototype の動きをなるべく丁寧に書いてみました。
ゆっくりじっくり読んでみてください。
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype ); // true
	 p.say(); // I Love Nicole
	 p.name = 'Gyu-Ri';
	 p.say(); // I Love Gyu-Ri
	 Person.prototype.name = 'Ha-Ra';
	 p.say(); // I Love Gyu-Ri
	 delete p.name;
	 p.say(); // I Love Ha-Ra
})();
SAMPLE
CODE
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
この準備で、右側のようなオブジェ
クトが出来ます(コンストラクタ
関数の実行部は省略)。
ここで、Person.prototype に
入っているオブジェクトは、name
と say の2つの属性を持ちます。
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
new した時の擬似コード
var newObj = {};
newObj.__proto__ =
Person.apply(newObj, ['Nicole'])
return newObj;
Person を newすると、擬似コードにあるような初期化が行われ、新しい
オブジェクトの __proto__ に Person.prototype のオブジェクトが代入されます。
代入
p: {
name:'Nicole'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
参照
結果、新しいオブジェクト p の __proto__ は、
Person.prototype に入っているオブジェクトへの参照を持ちます。
p: {
name:'Nicole'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
True
同じ
Person.prototype が p.__proto__ に代入された直後なので、
当然ですが、同一性比較は true になります。
p: {
name:'Nicole'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
I Love Nicole
参照
p.say() がコールされると、p 上に say を探すが見つかりません。
__proto__を って、say を見つけます。name は p 上で見つかります。
p: {
name:'Gyu-Ri'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'nanashi'
say: functon () {…}
}
}
I Love Gyu-Ri
参照
先ほどと同じですが、p 自身が持つ name が変更されたため、
当然、表示される内容は変化します。
p: {
name:'Gyu-Ri'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'Ha-Ra'
say: functon () {…}
}
}
I Love Gyu-Ri
参照
Person.prototype.name を変更しましたが、
name は p 自身で見つかるので、結果に変化はありません。
p: {
name:'Gyu-Ri'
__proto__:
}
(function() {
	 var Person = function (name){
	 	 this.name = name;
	 };
	 Person.prototype.name = 'nanashi';
	 Person.prototype.say = function () {
	 	 alert('I Love ' + this.name);
	 };
	 var p = new Person('Nicole');
	 alert( p.__proto__ === Person.prototype );
	 p.say();
	 p.name = 'Gyu-Ri';
	 p.say();
	 Person.prototype.name = 'Ha-Ra';
	 p.say();
	 delete p.name;
	 p.say();
})();
Person: {
prototype: {
name:'Ha-Ra'
say: functon () {…}
}
}
I Love Ha-Ra
参照
p 自身の name を削除すると、p 自身は name を持たなくなるので、
say も name も __proto__ から検索されて、結果、表示内容が変わります。
削除されて無くなった
こんな感じです。
この解説で prototype 属性と __proto__ 属性それぞれの
役割や位置づけ、またその原理などがしっくりと理解でき
たら嬉しいです。
それでは、次。
JavaScriptの
もう一つの鎖などについて。
(チェイン)
スコープチェイン
スコープとは?
任意の変数に
アクセスできる範囲。
…で、いいか。 (;゚ ゚)
JavaScriptの2つのスコープ
‣ グローバルスコープ
●
文字通り「グローバル」
●
プログラム全体からアク
セスできる
‣ ローカルスコープ
●
ある特定の範囲でだけで
アクセスできる
●
JSでは基本的に、個々
の関数の中がローカルス
コープになる
●
ある関数の中(=ローカ
ルスコープ)で作成され
た変数は、その関数の
内側でのみ参照できる
●
関数の引数もその関数の
内側でのみで参照できる
例を見てみます。
外からは
見えないか
らエラー
ローカル
変数
function myfunc() {
var name = 'Gyu-Ri';
}
myfunc();
alert(name); //ReferenceError
でも逆に…
期待通り
計算される
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c); //738
	 }
	 func2();
}
func1();
外側の変数
は見える
JavaScriptのスコープは、
外から内は見えなくて、
内から外は見える。
ポイント
「自分より外側は見える」ぐらいの、とてもシンプルな概念です。
もう少し見てみます。
名前が同じでも
スコープが違えば
別の変数
var name = 'Nicole';
function myfunc() {
var name = 'Gyu-Ri';
}
myfunc();
alert(name); //Nicole
別の変数なので、関数内
での代入は外側の name
に影響しない。
じゃ、これは?
var name = 'Nicole';
function myfunc() {
name = 'Gyu-Ri';
}
myfunc();
alert(name); //???
var name = 'Nicole';
function myfunc() {
name = 'Gyu-Ri';
}
myfunc();
alert(name); //Gyu-Ri
同じ変数!!
同じ変数なので、関数内
で 代 入 し た ら 外 側 の
name も変わる。
なにが違った?
var name = 'Nicole';
function myfunc() {
var name = 'Gyu-Ri';
}
Before
var name = 'Nicole';
function myfunc() {
name = 'Gyu-Ri';
}
After
var name = 'Nicole';
function myfunc() {
var name = 'Gyu-Ri';
}
var
が無い!!
var は、変数宣言。
「新しく作るよ」宣言。
var a = 123
新しく変数を作る時は必ず必要です。
言い換えると、宣言してない時は、
「既に有るよ」的な意味になる。
外側の変数
は見える
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
さっきの例の
変数 a の場合
このローカルスコープで、宣言さ
れているのは c だけなので、
a と b は 既にあると解釈される
…でも、func2 のローカルスコー
プには、a と b は見つからない。
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
そこで JavaScript は、一つ外側
の func1 のスコープで a と b を
検索する。
そして、b を見つけるが、
まだ a は見つからない。
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
そこで JavaScript は、さらに
外側のスコープで a を検索し、
そして a を発見する。
こんな感じで、順繰りに
内側から外側へと探して行く。
なんとなく…
プロトタイプチェインに
似てなくない?
(func2)
変数 a への参照をちょっと
オブジェクトっぽく書いてみた
scope.a
無い
無い
(func2) (func1)
scope.__scope__.a
アッタ!
(func2) (global)(func1)
scope.__scope__.__scope__.a
やっぱり、
プロトタイプチェインに
そっくり。
このように、今のスコープに無い識別子を、
より外側のスコープから連鎖的に
検索して探す仕組みが…
scope.__scope__.__scope__.identifier
スコープチェイン
scope.__scope__.__scope__.identifier
この事から、「グローバルスコープ」も、何も特別なものではなくて、単に「最も外側にあるローカルス
コープ」という解釈もできます。実際のところ、グローバルスコープには幾つかの特別な役割も課せられてい
ますが、感覚的なその理解は概ね正しいです。
難しく考えないことがポイント。
原理的には内から外への連鎖的なスコープの検索ですが、利用上は単に、「関数が
定義された環境(ソースコード上の見かけ)において、その外側は見える」とい
うことに過ぎません。プログラマにとっては、とても直感的な仕様です。
余談ですが、
実はJavaScriptでは、変数のスコープでさえも、内部的にはオブジェクトで構成されています(=変数オブ
ジェクト)。つまり、つまるところ、「スコープチェイン」も「プロトタイプチェイン」も、オブジェクトの特
殊なプロパティによる連結リストによるデータ構造と、それを終端に向かって連鎖的に検索するという、よく
似たシンプルな仕組みに過ぎません。またこれは、JavaScriptにおけるスコープの実体が、グローバルスコー
プ(グローバル変数オブジェクト)をrootとする、変数オブジェクトが連なった巨大なオブジェクトのツ
リー構造だということでもあります。興味深いですね。
残念ながら、それらのスコープを構成するオブジェクトのツリーを、プログラマが直接参照することは出来ま
せん。しかし、プロトタイプチェインを構成する__proto__属性については、ECMA Script標準では無いも
のの、幾つかのブラウザ実装では実際に目にしてその仕組みを確認することが出来ます。これを良く観察す
ることで、「スコープチェイン」と「プロトタイプチェイン」両方の理解を深めることが出来るはずです。
さて、JavaScript のスコープには
もうひとつ大事な概念があります。
クロージャ
早速のクロージャの
サンプルコード。
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
(・∀・)?
サッキト オナジジャネ?
そうです。
さっきのもクロージャ。
クロージャという言葉には広義なものと狭義なものとあります。
そして「広義」にはこれもクロージャです。
引数以外の変数を実行時の環境ではなく、
自身が定義された環境(静的スコープ)において
解決することを特徴とする。
by Wikipedia(いつもありがとう)
クロージャ
の意味
ようするに、
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
この関数が
定義された
環境とは…
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
この関数を含
む全てのスコー
プのこと
スコープチェインによって、
関数定義の外側の変数まで
参照できましたよね!
スコープチェインによって、
関数定義の外側の変数まで
参照できましたよね!
さっきの
内側から外側は
見えるって
コト!
var a = 123;
function func1() {
	 var b = 3;
	 function func2() {
	 	 var c = 2;
	 	 alert(a * b * c);
	 }
	 func2();
}
func1(); //738と表示
スコープチェイン => クロージャ
つまり JavaScript では、スコープチェインの仕組みが、
そのままクロージャとして機能することになります。
JavaScript を使っていると、ごくごく当たり前かも知れませんが…、
これが出来る汎用プログラミング言語は少し前までそう多くはありませんでした。
PHPでは 5.3 から、Java でも Java 8 から利用できるようになります。
では、どんなふうに使えるか?
変数の隠 、別名の利用。
みんなもう普通に使ってるよ。 (*'-'*)
即時関数で
ラップしてローカル
スコープを作成
スコープの外から
は見えない。
=隠 されてる
引数を利用して
オブジェクトの
別名を利用
ここで、変数 privateValue や
$ は、即時関数の外からは見
えない変数になりますが、こ
のスコープの内側で定義され
る関数からは参照することが
できます。
即時関数は文字通り即時実行
されてその処理を抜けます
が、privateValue や $ と
いった変数が、引き続きその
中で定義された関数から参照
できることがポイントです。
var Person = (function($) {
	 var privateValue = 'Hogeta';
	 function Person(name) { ... }
	 Person.prototype.xxxx = ...
	 return Person;
})(jQuery);
変数の永続化。
とても重要でパワフルな概念。
返された
クロージャは
i をずっと
参照
※コードはWikipediaのクロージャのページからの引用です。
呼び出す毎
に i が1ずつ
増える
newCounter は、新しい関数
オブジェクトを生成して返却
します。newCounter が実行
されるたび、変数 i が初期化
され、その i を参照するク
ロージャが返却されます。
newCounter が返却するク
ロージャを変数 c1 に受け
取って実行すると、実行毎に
1ずつ増えた値が得られます。
これは、newCounter が返し
た関数オブジェクトが、変数
i を参照し続けていることで
実現されています。
このようにクロージャは、定
義された時点の環境を束縛し
ています。
function newCounter() {
var i = 0;
return function() {
i = i + 1;
return i;
}
}
var c1 = newCounter();
alert(c1()); // 1
alert(c1()); // 2
alert(c1()); // 3
クロージャを使いこなすには?
とりあえず、書く。
たくさんコードを書いて慣れるのが一番です。 (*'-'*)
とてもシンプルで直感的な仕様なのですが、上手く利用するには訓練が必要です。
クロージャの利用シーンはとても広範囲なので、たくさん書いて少しずつ用法を
身につけていくと良いと思います。
では、最後に、
例のややこしいやつですよ。
this
this も、ワリと簡単。
これこそ、クラスベース言語によくある考え方で理解しようとすると、
落とし穴にはまります。一旦、頭を真っ白してください。
this を使った例。
これは
OK
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
var btn = $('button#say-hello');
btn.on('click', function() {
	 person.sayHello();
});
よくあるパターンです。イベントにコールバック関数をバインドしています。
ここで、btn.on に渡している関数はクロージャで、クロージャ変数として
person を参照しています。
これは
NG
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
var btn = $('button');
btn.on('click', person.sayHello);
前の例では、呼び出したい関数をコールバックとして登録しているだけだったので、
sayHello 関数を直接バインドしたらどうだろう?とやってみると NG です…。
結果として、(多くの場合)「Hello undefined」と表示されます。
なぜか?
関数はオブジェクトに
束縛されない
ポイント常識!
繰り返しになりますが、オブジェクトのメソッドはそれを持つオブジェクトに束縛され
ているのではなく、その時そのオブジェクトによってただ参照されているだけです。
この意味に注意してください。
var obj = {
key: function() {...}
}
オブジェクトのメソッドは、
「オブジェクトによってただ参照されているだけ」
つまり…
btn.on('click', person.sayHello);
person の
sayHello メソッドを
渡してる
「person の sayHello」を渡しているのではない。
btn.on('click', person.sayHello);
「sayHello」に入っている、関数オブジェクトを渡している、が正解。
関数オブジェクトだけが渡されている、という理解が大切です。
sayHelloの中の関数オ
ブジェクトを渡してる
では、this ってなに?
ポイント
関数コール時にその関数が
所属していたオブジェクト。
= this
this
呼び出し時に、所属していたオブジェクトが、
関数内での this になる
person.sayHello();
所属
では、
ただの関数呼び出しの時は?
所属
sayHello();...nothing...???
ただの関数呼び出しで、関数がオブジェクトに所属していない時、
関数内の this は何になるのか?
関数のコール時にレシーバーが無い時、this はグローバルオブジェクトになります。
ブラウザ実装の場合、これは通常 window オブジェクトです。
global
object
= window
function sayHello() {
    alert('Hello ' + this.name);
}
sayHello();
つまり
ポイント
this は、
呼び出し時に決定される
ところで、
this が呼び出し元で
決定されるなら、
実は、呼び出し元で、
thisを操ることもできます。
this を操る
call apply bind
thisを操る
メソッド
関数オブジェクトには、this をコントロールできる3つのメソッドがあります。
(関数オブジェクトだけに存在する)
関数に渡す引数
call(object, arg1, arg2, ...)
apply(object, Array)
bind(object, arg1, arg2, ...)
これらのメソッドの第1引数に与えた object が、関数内での this になります。
また、その後の引数は、それぞれ関数呼び出し時に関数に与える引数になります。
this
this
this
3つのメソッドの利用法。
call と apply は
関数を実行する
person を this
として実行
call の第1引数が this に、
第2引数以降の引数が、
say関数の引数に。
function say(arg1, arg2) {
    alert(arg1 + this.name + arg2);
}
var person = new Person('Nicole');
say.call(person, 'Hello ', ' chan');
call の例
person を this
として実行
apply の例
call の第1引数が this に、第2引数の
配列の内容が、say関数の引数になる。
引数の渡し方以外は
call と同じ
function say(arg1, arg2) {
    alert(arg1 + this.name + arg2);
}
var person = new Person('Nicole');
say.apply(person, ['Hello ', ' chan']);
他のオブジェクト
のメンバだったら?
var p1 = {
name: 'Gyu-Ri',
say: function (arg1, arg2) {
     alert(arg1 + this.name + arg2);
}
};
var person = new Person('Nicole');
p1.say.call(person, 'Hello ', ' chan');
他のオブジェクト
のメンバでも関係ない
関数はオブジェクトに「束縛されていない」
ことを思い出してください。
ここで、関数 say が p1 に属していても、
指定した person が this になります。
var p1 = {
name: 'Gyu-Ri',
say: function (arg1, arg2) {
     alert(arg1 + this.name + arg2);
}
};
var person = new Person('Nicole');
p1.say.call(person, 'Hello ', ' chan');
bind は
関数に値を束縛する
personをthisに束縛した
新しい関数オブジェクトを返す
function say(arg1, arg2) {
    alert(arg1 + this.name + arg2);
}
var person = new Person('Nicole');
var say2 = say.bind(person);
say2('Hello ', ' chan');
結果:Hello Nicole chan と表示される
say2 関数の呼び出しでは、
常に person が this となる
bind の利用例
JavaScript OOP では非常に便利。
これは
NGでした
さきほど NG だった例ですが...
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
var btn = $('button');
btn.on('click', person.sayHello);
こうすれば
OK
bind で関数に this を束縛できるので、そのまま渡せるようになる。
person を2回使ってるように見えるけど、そういう意味でないことに注意。
var Person = function (name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    alert('Hello ' + this.name);
}
var person = new Person('Nicole');
var btn = $('button');
btn.on('click', person.sayHello.bind(person));
apply とクロージャの利用例
すこし長いし、元ネタは…だけど。
元のオブジェクト(self)と関
数(func)をクロージャで保持
し、必ず self に対して関数が
適用されるようにしている。
引 数 の 数 は ま ち ま ち な の
で、apply を使って実行する
コンストラクタ関数の中で、
__bind を実行し、返された
新しい関数をオブジェクト
のメンバーとして保持する
(関数の差し替え)
say には prefix も渡したい
ので、bind を使っているが、
第1引数に person でなく
null を渡しても、this は変
更されない
// 絶対 this を離さないユーティリティメソッド
function __bind (self, func) {
	 return function () {
	 	 return func.apply(self, arguments);
	 };
}
// コンストラクタ関数とプロトタイプを用意
function Person (name) {
	 this.name = name;
	 this.say = __bind(this, this.say);
}
Person.prototype.say = function (prefix) {
	 alert(prefix + this.name);
}
// オブジェクト生成
var person = new Person('Nicole');
// 普通に利用
nicole.say('I Love '); // I Love Nicole
// 他の値をバインドしてもOK
setTimeout(person.say.bind(null,
	 	 'I Just Realy Love to '), 2000);
ということで…
JavaScript でオブジェクト指向プログラミ
ングを行う際に備えておくことが望ましい、
基礎知識や概念について解説してきました。
ちょっと欲張り過ぎましたが、
どれも一度理解したらとても簡単な概念です。
JavaScript にはクラスが無いこと、
オブジェクトが
単純なハッシュテーブルであること。
この2つの理解を深めると、
たとえば this の挙動も納得できます。
JavaScript では、このように単純なデータ構
造を幾つかのルールに沿って組み合わせるこ
とで、とても柔軟で強力なオブジェクト指向
プログラミングを実現しています。
JavaScript が強力なのは、
このシンプルさの所以です。
しかしある場面では、
そのシンプルさが冗長なソースコードの記述
を要求するかも知れません。
それらの問題は、例えば CoffeeScript や
TypeScript といった、JavaScript を基本と
する別の技術で解決できるかも知れません。
ただ、そういった新しい技術を利用するにあ
たっても、そのベースとなる JavaScript そ
のものの基礎理解は、あなたの開発を大いに
助けてくれるはずだと、僕は信じています。
var obj = {}
JavaScript はオブジェクトが全てです。
ご清聴、ありがとうございました!
@yuka2py
書籍を執筆しました。
JavaScript についても基礎から
応用・発展まで詳しく書かれています。
良かったら書店で手に取ってください。
http://www.amazon.co.jp/dp/4798129682
この文書は クリエイティブ・コモンズ 表示 - 継承 2.1 日本 ライセンスの下に提供されています。

More Related Content

最強オブジェクト指向言語 JavaScript 再入門!