...

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

by user

on
Category: Documents
16

views

Report

Comments

Transcript

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