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

Post on 13-Dec-2014

164.311 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

この資料では、JavaScript でオブジェクト指向プログラミングを行う際に備えておくことが望ましい、基礎知識や概念について解説します。 【対象者】 ・JavaScript でアプリケーションを構築できる方 ・JavaScript におけるオブジェクト指向プログラミングの  実現手法や原理への理解を深めたい方 ・Java 的なクラスベースの言語との違いに違和感や混乱を  感じてらっしゃる方

TRANSCRIPT

自己紹介

‣ ノジマユウジ @yuka2py

‣ 株式会社フォーエンキー代表取締役

‣ システム開発、グラフィックデザイン、DTPや印刷なども

‣ PythonとJavaScriptが大好き(Dartに興味深々)

‣ 様々なWebアプリケーション設計・構築・運用。またWPによるシステム開発、プラグイン開発などが主なお仕事

‣ おしゃれも大好き☆リボンやお花が好き☆

‣ 参加コミュニティ● WordBench 神戸● HTML5-WEST.jp● 日本Androidの会 神戸支部

絶賛お仕事募集中

去年のボクPython

1%Design15%

iOS4%

Android10%

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 di O SD e s i g nP y t h o n

2012年11月2日株式会社フォーエンキー調べ

最近のボクPython

1%

Design5%

Android5%

Windows15%

Web/WordPress25%

お嫁50%

お嫁Web /Wo r d P r e s sW i n d o w sA n d r o i dD e s i g nP y t h o n

2013年6月1日株式会社フォーエンキー調べ

はじめに

本セッションでは、JavaScript でオブジェクト指向プログラミングを行う際に備えておくことが望ましい、基礎知識や概念について解説します。

対象者・JavaScript でアプリケーションを構築できる方・JavaScript におけるオブジェクト指向プログラミングの 実現手法や原理への理解を深めたい方・Java 的なクラスベースの言語との違いに違和感や混乱を 感じてらっしゃる方

Simple and PowerfullJavaScript はとてもシンプルな言語仕様でありながらも、とても強力にオブジェクト指向プログラミングをサポートしています。

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(実体)

delegate

Object(実体)

delegate

Object(実体)

delegate

…な、感じで実現します。

コードで見てみる。

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 NikoleobjC.say(); // I Love Gyu-Ri

クラスベースと較べてみる。

‣クラスベース ‣プロトタイプベース

class A

class B

class C

obj A

obj B

obj C

obj A of B

obj B of Cobj C of C

obj Eobj 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 })();

SAMPLECODE

(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と表示

(・∀・)?サッキト オナシ ジ ャ゙ネ?

そうです。さっきのもクロージャ。クロージャという言葉には広義なものと狭義なものとあります。そして「広義」にはこれもクロージャです。

ようするに、

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()); // 1alert(c1()); // 2alert(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 オブジェクトです。

globalobject = 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// 他の値をバインドしてもOKsetTimeout(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 日本 ライセンスの下に提供されています。

top related