No.2 スコープ
それでは前回の続きをやっていきます!:)
前回、関数の呼び出し方を3種類ほど紹介しましたが、実際に何が良いのかは説明しませんでした。
今回は、「何が便利なのか」についてやっていこうと思います!
ただ、その前にJavaScriptのスコープというものを学んで頂きます。
スコープ?
JavaScriptには、現在のスコープの参照を持つthisという変数があり、今回はこのthisについて学んで行きたいと思います。(この他にも変数のスコープというものが存在しますが、それについては別の機会にご紹介します)
例えば、下記のようなコードをhtmlのscriptタグにべた書きした場合
1 | console.log(this); |
WebインスペクタのConsoleに下記のようにwindowオブジェクトが出力されると思います。
windowオブジェクトは、JavaScriptにおけるグローバルスコープです。よって、この場合のスコープはwindowオブジェクトということが分かると思います。
次は、scriptタグ内に関数定義を行い、その中のスコープを確認してみましょう。
1 2 3 4 | function checkScope() { console.log(this); } checkScope(); |
上記のようなコードを実装し再度ブラウザを再読込すると、またまたwindowオブジェクトが表示されると思います。これは何故かというと、先ほどのコードはscriptタグに直接関数定義を行いましたが、実はベタ書きでこのように処理を書くと、windowオブジェクトのメソッドのような形で関数定義がされます。
JavaScriptでは、windowオブジェクトは記述時には省略できるため忘れがちになりますが、checkScopeというどこからでも実行できる関数を定義したというよりは、JavaScriptのグローバルオブジェクトであるwindowオブジェクトにメソッドを追加したイメージです。
WebインスペクタのConsoleなどから、window.checkScopeと入力すると、先ほど実装した関数にアクセス出来ることが分かると思います。
以上のことから、checkScope内でのthisは呼び出し元のスコープを指すためwindowオブジェクトになります。
コロコロ変わるスコープ
前項での説明でスコープはwindowオブジェクトを指していましたが、どのような時にこのスコープが変化するのでしょうか?
簡単な例を見ていきましょう
1 2 3 4 5 6 | var sampleObj = { log: function() { console.log(this); } }; sampleObj.log(); |
この例では、sampleObjオブジェクトにlogメソッドを実装してあります。このlogメソッドを実行した際に出力されるthisはどうなるか。
実行すると、下記のような値がConsoleに出力されます。
変わっちゃいましたね・・・:(
このように関数内のthis(スコープ)は、実行元が変わるとthisの内容も変化します。
this(スコープ)の説明をしましたので、1つ前の記事でご紹介した特殊な2種類の関数呼び出しについて再度説明したいと思います。
関数呼び出しその1:Function.call
1 | 関数名.call(スコープ対象, 引数...); |
スコープの説明をしたことで、引数の意味がちょっと分かってきましたかね?:)
先ほどスコープが参照する対象は、場合によって変化すると説明しましたが call
(apply
もほぼ同等の動き)は、このスコープを設定することが出来るメソッドになります。
記述例
1 2 3 4 5 6 7 8 9 10 11 12 | var o = {x:10}; var func = function(v) { if (v === undefined) { console.log(this.x); } else { console.log(this.x * v); } } func(); // 結果:undefined func.call(o); // 結果:10 // 引数有り func.call(o, 10); // 結果:100 |
単純にfunc
を実行しても、参照がwindowオブジェクトのためundefinedになると思いますが、call
を利用してスコープをオブジェクトoに設定しています。(オブジェクトoにはプロパティxを設定している)
すると、関数内でのスコープがオブジェクトoになり、プロパティxも設定されているため「10」という値が出力されるというわけです。引数を付ける場合はスコープ対象の後に、カンマ区切りで通常の関数呼び出しのよう中たちで付けてあげれば大丈夫です。
関数呼び出しその2:Function.apply
1 | 関数名.apply(スコープ対象, [引数...]); |
引数の渡し方以外については、call
と同等です。
1 2 3 4 5 6 7 8 9 10 11 12 | var o = {x:10}; var func = function(v) { if (v === undefined) { console.log(this.x); } else { console.log(this.x * v); } } func(); // 結果:undefined func.apply(o); // 結果:10 // 引数有り func.apply(o, [10]); // 結果:100 |
apply
は非常にcall
と似ています。(実際に動き的には同等なので・・・)
ただ、引数の渡し方が配列のため、関数内で利用できるarguments(関数に渡された引数すべてを配列で受け取る特別な変数)と合わせた使われ方をよくします。
apply × arguments
JavaScriptでは引数すべてを設定しないもしくは、設定されている数より多い引数を渡すことが可能になっており、実行時に渡された引数をそのままパスすることも可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | var app = { name: 'app', x: 2, y: 200, init: function(a, b) { arg.init(arguments); }, multi: function() { return this.x * this.y; }, str: function() { return arguments.join('') + '-' + this.name; } }; var arg = { name: 'arg', x: 5, y: 100, init: function(a, b, c, d) { console.log(a, b, c, d); }, multi: function() { console.log(app.multi.apply(this)); }, str: function() { console.log(app.str.apply(this)); } }; app.init(10, 20, 5, 'abc'); // 結果:10, 20, 5, 'abc' arg.multi(); // 結果:500 app.str('A', 'B', 'C', 'D'); // 結果:ABCD-app arg.str('A', 'B', 'C', 'D'); // 結果:ABCD-arg |
この機構はSenchaのアーキテクチャでも利用されており、子のメソッドが実行された後に親メソッドにそのまま引数ごとパスする際などにもよく利用されます。
1 | this.callParent(arguments); |
スコープおよび、スコープを利用した関数呼び出しの説明は以上です!
それでは、又次回をお楽しみに:)