HTML5テクニカルノート JavaScriptでオブジェクトに設定した関数のスコープ
JavaScriptでイベントハンドラやコールバックなど、オブジェクトに関数を設定することがあります。その関数の中で変数がどのように参照されるかをご説明します。 01 定義した関数をオブジェクトに設定する
つぎのコード001は、forループで複数のObjectインスタンスをつくり(第7行目)、別に定義してある関数(method())をそれぞれに設定します(第9行目)。関数は3つの変数を参照して、配列に入れて返します(第13〜15行目)。これらの変数値はどの値になるのかが問題です。 コード001■定義した関数を複数のオブジェクトに設定する
以下のようなスクリプトを加えれば、配列に納められた3つのObjectインスタンスを取出してその関数を呼出し、戻り値の配列の内容を確かめることができます。3つの配列のエレメント値は、つぎのように示されます。
3つのObjectインスタンスには同じ関数(method())を設定しました。関数にはローカル変数がありませんので、thisをつけない変数(iとn)についてはグローバルに定められた値(第1〜2行目)が参照されます。thisキーワードは、関数の設定されたオブジェクトの参照になります。したがって、thisを変数につければ、オブジェクトに設定された値(第8行目)が得られます。 02 名前のない関数をオブジェクトに設定する オブジェクト.関数名 = function() { つぎのコード002は、forループでつくったObjectインスタンスに、名前のない関数をそれぞれ設定しています(第9〜11行目)。関数の中身は、前掲コード001と同じです。3つの変数値をエレメントに納めた配列が返されます。 コード002■名前のない関数を複数のオブジェクトに設定する
3つのオブジェクトの関数が返す配列のエレメント値は、つぎのように変わります。名前のない関数は、オブジェクトの処理を行う関数(test())の中でつくられて設定されました。つまり、関数が入れ子になっています。すると、入れ子の関数はグローバルな変数より先に、親関数のローカル変数を参照します。そのため、thisのつかない変数(iとn)は、ローカル変数の値(コード002第5〜6行目)をとるのです[*1]。thisキーワードが関数の設定されたオブジェクトを参照することは変わりません。 3,0,2 前掲コード001との違いをもうひとつつけ加えます。コード001は3つのObjectインスタンスに同じ関数(method())を定めました。しかし、コード002では、3つのオブジェクトに同じ内容の名前のない関数をそれぞれつくって設定しているのです。つまり、名前のない関数はオブジェクトと同じ数だけでき上がっています[*2]。
03 入れ子関数でオブジェクトに関数を設定する (function(引数) { このやり方で複数のObjectインスタンスに関数を設定したのが以下のコード003です。これは先に結果を見てしまいましょう。3つのオブジェクトの関数は、つぎのような配列を返します。前掲コード002とひとつ大きく異なるのは、this参照なしの変数(i)の値が、オブジェクトごとに違うことです。 0,0,2コード003■入れ子関数の呼出しによりオブジェクトに関数を設定する
このコード003のObjectインスタンスに関数を設定する処理(第9〜13行目)は、つぎのように書替えてもほぼ同じ内容になります[*3]。つまり、オブジェクトに関数を設定する入れ子の関数(setCallback)が定められ、直ちに呼出されているということです。
関数が呼出されると、その引数やローカル変数を納めるための暫定的なオブジェクトができます[*4]。関数が処理を終えると、そのオブジェクトは通常メモリから消されます(ガベージコレクションが働きます)。けれど、入れ子関数を他のオブジェクトに設定して保持するなど、参照を保ったままにすると、その暫定的なオブジェクトは消えません。つまり、設定した関数を呼出せば、引数やローカル変数にアクセスできるのです。前掲コード002で親関数のローカル変数が参照されたのもこの仕組みによります。 コード003では、さらに入れ子の関数(書替えたコードではsetCallback)をつくり、それをObjectインスタンスの数だけ繰返し呼出しました(第10〜13行目)。すると、呼出された関数には毎回異なる暫定のオブジェクトがつくられることになります。そして、関数を呼出すときカウンタ変数(i)を引数に渡しました(第13行目)。そのため、オブジェクトごとに違った引数値が残ったのです。 ただし、親関数(test())は1度しか呼出されていません(コード003第17行目)。したがって、3つのObjectインスタンスの関数が共通してひとつの親関数の(暫定オブジェクトがもつ)ローカル変数(n)を参照したのです。 もっとも、関数の設定される各オブジェクトに直接異なる変数を設定すれば、thisキーワードでそれぞれの値を得ることはコード001から003まで共通してできています。また、コード002でも注意したように、入れ子の関数はオブジェクトの数だけでき上がり、メモリを費やします。したがって、入れ子関数を使うことが必要な場合はかぎられるでしょう[*5]。
04 クラスを入れ子関数として定義する ローカル変数は、関数の外からアクセスすることができません。コード001では、オブジェクトごとに異なる値は、オブジェクトの変数にしました。これで目的は果たせるものの、オブジェクトに設定した変数は外から書替えられます。小さなコンテンツでしたら、誤って上書きしないように気をつけさえすればよいことです。 しかし、たくさんのクラスを定める場合、変数が互いに重複しないようにするのは神経を使います。ましてや、公開されたライブラリは、中身を精査してから使うという人はあまりいません。重要な変数を知らずに上書きしてしまう恐れがあります。他の多くのプログラミング言語には、クラスの外からアクセスを許さないprivateという属性の指定があります。けれど、JavaScriptはそうした設定ができません。そこで、ローカル変数を使うのです。 JavaScriptでは、クラスは関数(function)として定めます。そのクラスの関数(コンストラクタ)を入れ子にして、親関数の呼出しによりクラスを定義するのです。すると、クラス定義に用いたvar宣言の変数には、親関数の外からアクセスできません。そこで、定義したクラスだけをグローバルなオブジェクト(windowオブジェクト)に設定します。 (function(window) { EaselJSでも、クラスはみなこのかたちで定義されています。たとえば、下図001はDisplayObjectクラスの一部です。クラス定義なら、ひとつのクラスにつき1度行うだけで済みますので、親関数を何度も呼出してメモリが無駄に費やされる恐れもありません。 図001■EaselJSのDisplayObjectクラス 作成者: 野中文雄 Copyright © 2001-2012 Fumio Nonaka. All rights reserved. |
||||||||||