プログラムな日常>日常>
javascript における視野
(scope in javascript programs)

 javascript で色々なプログラムを作っているが、スコープやオブジェクトのことがいまだに何となくのまま進んでしまっている。 一度きちんと確認しようと思った。その検討備忘録。 自分としての目標は、変わった機能を使いこなしてドヤ顔をすることでなく、あくまで最小限の便利なやり方を確立することなので、 まずは分析し、併せてどういう使い方がベストなのか検討しようと思う。

【地の文の変数】

 このような(function の中ではない)地の文に出てくる aa は、this.aa, window.aa などとしても同じだ。 多分、this は常に何かしら定義されている普通名詞的なものであり、それがこの文面では(固有名詞的な)window なのだろう。 window.aa が最も本来のものなのだと思う。
 ただし、上の4行目は var aa と aa=0 を併せた効果があり、前者はページの実行以前になされる変数の定義のようだ。 従ってこの行全体を window.aa=0, this.aa=0 などと置き換えることはできない。 var を指定しない次のプログラムでは、一行目からたちまちエラーになる。

 しかし、一行目だけでも this. を付けるとちゃんと通る(window. でも良い)。

 あるいは let を最初から使うかである。

 このことから、var はプログラム全体に、変数が正当なものであることを知らしめる効果があるようだ。 これは、var 独自の効果で、let や const にはない。

【地の文の関数】
 var と同じ効果は、function についてもある。ただし、どんな function かに依る。
function f() { } と、window.f=function() { } や this.f=function() { } は同じようなものだが、 function f() { } だけが、それがどこにあっても呼び出せすことができ、他の2つは、定義した箇所より後でないと呼び出せない。 と、言いつつ、今言ったことは絶対ではない。要は実行順序の問題なのだ。 次のプログラム内で fa を参照しようとすれば、文脈上では後にある関数 prepfuncs() を実行する中で fa を定義した後であれば可能である。

 説明が前後したが、prepfuncs() の中の環境で this はやはり window である。 しかし、これをするなら普通に(外側の環境で) function fa() { } をすれば良いように思う。また、逆に次のようなアクロバットをしようとするとエラーになる。

 callfunc() の中で、this.fa(x) を呼び出そうとするときに、関数が既に準備されていないためだ。

 振り返ってもしやと思い、関数内で変数を定義してみたが駄目だった。この場合、関数内において let で作った aa はローカル変数になるようだ。 これは let を var にしても同じだった。グローバルな変数として定義するには this をつけなければいけない。 それなら初めから関数を使うメリットはない。

 総じて地の文脈における aaa, window.aaa, this.aaa さらにここから呼び出された関数内における window.aaa, this.aaa は全て同じものである。 関数内における aaa だけは別箇の変数である。定義されていない変数を参照した場合は、 window.aaa, this.aaa では undefined になるが、aaa ではなぜかエラーになる。 それはグローバル環境の話で、ローカル環境で参照した場合、もしグローバル環境で定義されていれば、それが参照される。設定も同様である。 グローバル環境の定義もなければ、エラーになるのは同じである。


【function の入れ子】
 function の外から中を見ることはできない。これはあくまで基本で、new をしてオブジェクトを作った場合は違うのだけれど、それは後述。
 function の中では、内側の変数とか関数が優先される。参照する名前がもしなければ、外側の名前が、内側に近いものから参照される。 これは別に新しいことではなく、確か Pascal などの言語では既にある static binding というやつだ。 しかし、最近の C# などでは、クラスを入れ子にしても、外側のオブジェクトを内側に渡さないと実体を参照できないというとてつもない不便があった。 これでは入れ子の意味がないとずっと思っていたので、解消されたのは好ましい。
 function の中では、定義される var や function foo() { } はどこからでも参照できる。 しかし、foo=function() { } が定義以後でないと参照できないのは、地の文と同じだ。
 外から中が見えないと言ったが、中から外の環境にアクセスして関数を設定することはできる。次のような感じだ。


【イベントの場合】
 bta, btb, btc という3つのボタンがあって、順にクリック、最後の btc については2度クリックしたとすると、次のような結果になる。

 ラムダ式で書いた部分の this は window だが、独立のリスナーを作った場合の this はボタンなどのオブジェクトになる。 こうしたものに、変数や関数を後付けすることができる。

【new をした場合】
 new をした関数の中で、this は作られつつあるオブジェクトを定義する。
 インスタンスに対する変数および関数はいちいち this をつけなければいけない。これはかなり面倒くさい。 this をつけていない、下のプログラムで aa のような変数は、オブジェクト言語の local 変数と同じだ(6/12 改定。もともと「static 変数と」としていましたが、オブジェクトに依らない static 変数のような振る舞いはありません)。

 面白いことに、インスタンスの中から呼び出された this の無い関数の中で、this は window に戻っている。 まあ、それはそうだ。それを避けるために this が必要だったのだから。

 ただ、this のない関数にはメリットがある。this のある関数は使用以前に定義されなければならない。 だからコンストラクタ(というか、関数内の地の文章)から呼び出そうとすると、それ以前に書かれなければならないが、これはプログラムを見づらくするので、私の好みとしては避けたい。

 避ける方法として、後の方に置いた this のない関数をまず呼び出すという、先に紹介した方法はここでは使えない。 なぜなら、this のない関数内の this は window に戻ってしまうわけだから。 しかし、呼び出しがコンストラクタからのみの場合には解決策がある。それは this を変数として引き渡してしまう方法だ。例えば次のように。

 難点は外部から呼び出せないことだが、その必要がない関数の需要は多い。