Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
JavaScript
応用編
- トップページ
- JavaScript応用編 - thisの概要と使い方
thisの概要と使い方
みなさまどうもおはこんばんにちは。
さて続きまして、呼びだされたタイミングのカレントオブジェクトを参照できる『this』(ディス)ってやつについて色々とやっていきたいと思います。
これ、めっちゃ便利なのでバッチリ使い方を覚えてしまいましょう。そんでもって『this』を使わないと構築できない処理も存在してたりしてなかったりするので結構必須項目のような感じになってます。
あと他の言語でも『this』のような仕組みは用意されていたりするのでございますが、JavaScriptの『this』の挙動はちょっと特殊かもしれません。なので他の言語、特にクラスベースな概念で設計がされている言語に精通している方は混乱してしまわないように要注意でございます。
というわけでやっていきましょう。とりあえずの『this』の使い方はこんな感じです。
this;
普通に『this』って記述するだけでOKです。オブジェクト(識別子)に渡す場合はこうです。
var self = this;
これでオブジェクト『self』に『this』が渡されました。単純に使用するだけならばこれだけです。
あと『this』は読み込み専用という仕組みになっているため、『this』を上書きしたりすることはできません。
this = 0; // これはエラーです。
ここまでは特にむつかしくないですね。さて、問題は『this』の中身、参照先です。
『this』は呼びだされたタイミングや状況によって参照先がコロコロと変わるので慣れていないとこんがらがってしまいがちですが、基本的なイメージとしてはメソッド内で『this』が呼び出された場合はそのメソッドが格納されているオブジェクトへの参照となり、格納されているオブジェクトが存在しない場合はwindowオブジェクトの参照となるという感じになります。
そして、『new演算子』と『call()』、『apply()』が使用されている場合は参照先がちょっと例外的な感じになります。
と、こんな感じで説明するよりも色々なところで『this』を出力させてみて挙動を確認した方が分かりやすいと思いますので色々と試してみましょう。
まずはトップレベルな場所(関数やメソッドの外側)で『this』を出力してみます。トップレベルな場所で『this』を呼び出した場合の参照先はwindowオブジェクトとなります。
console.log(this); // 『windowオブジェクト』が出力されます。
これは「格納されているオブジェクト的なものが存在していないからwindowオブジェクトが参照されている」というイメージでOKかと思います。
続いてグローバル関数(メソッド)の中で『this』を出力してみます。こちらの記事でも解説をさせてもらっていますが、JavaScriptのグローバル関数の実態はwindowオブジェクトのメソッドとなりますので以下の全ての『this』の参照先は自身のメソッドが格納されているwindowオブジェクトになります。
var f = function(){ console.log(this); // 『windowオブジェクト』が出力されます。 }; function _f(){ console.log(this); // 『windowオブジェクト』が出力されます。 }
では続いてローカル関数を定義してその中で『this』を出力させてみましょう。こちらも『windowオブジェクト』が出力されます。
var f = function(){ // グローバル関数を定義します。 var _f = function(){ // ローカル関数を定義します。 console.log(this); // 『windowオブジェクト』が出力されます。 }; _f(); // 実行します。 function _f0(){ // ローカル関数を定義します。 console.log(this); // 『windowオブジェクト』が出力されます。 } _f0(); // 実行します。 }; f();
これは即時関数でも同じです。
(function(){ // グローバルな即時関数です。 console.log(this); // 『windowオブジェクト』が出力されます。 (function(){ // ローカルな即時関数です。 console.log(this); // 『windowオブジェクト』が出力されます。 })(); })();
こちらも「格納されているオブジェクト的なものが存在していないからwindowオブジェクトが参照されている」ってイメージでOKです。
続いてメソッド(オブジェクトの中に関数を定義したもの)の中で『this』を呼び出してみましょう。以下のサンプルを実行させるとオブジェクト『o』が出力されます。
var o = { f: function(){ // メソッド『f』を定義します。 console.log(this); } }; o.f(); // オブジェクト『o』が出力されます。
上記のサンプルではオブジェクト『o』にメソッド『f』を定義してその中で『this』を出力させています。この場合は「メソッド内で『this』が呼び出された場合」に該当するのでそのメソッドが格納されているオブジェクト『o』への参照となった感じですね。
ちょっと注意点として、オブジェクトの中のオブジェクトといったような入れ子状態になった場合も自身のメソッドが格納されているオブジェクト(親オブジェクト)の参照となります。
var o = {}; // オブジェクト『o』を定義します。 o._o = {}; // オブジェクト『o』の中にさらにオブジェクト『_o』を定義します。 o._o.f = function(){ // オブジェクト『_o』の中にメソッド『f』を定義します。 console.log(this); // 『this』を出力します。 }; o._o.f(); // 『o._o』が出力されます。
そしてもういっちょ注意点なんですが、メソッドの中でローカル関数オブジェクトを生成し、その中で『this』を呼び出した場合の参照先は、そのメソッドが格納されているオブジェクトではなくwindowオブジェクトとなります。
var o = { f: function(){ // メソッド『f』を定義します。 var _f = function(){ // ローカル関数『_f』を定義します。 console.log(this); }; _f(); // 『windowオブジェクト』が出力されます。 } };
「メソッドの中で生成した関数の中の『this』の参照先はそのメソッドになるはず!」と勘違いしてしまいやすいところなんでめっちゃ注意です。JavaScriptの仕組みではローカル関数はどこぞのオブジェクトに格納されているわけではないので「格納されているオブジェクト的なものが存在していないからwindowオブジェクトが参照される」が該当します。
ローカルオブジェクトを生成して、その中でメソッドを定義した場合の『this』の出力先はそのローカルオブジェクトになりますね。
var o = { f: function(){ // メソッド『f』を定義します。 var _o = { // ローカルオブジェクト『_o』を定義します。 _f: function(){ // メソッド『_f』を定義します。 console.log(this); } }; _o._f(); // ローカルオブジェクト『_o』が出力されます。 } };
ここまでは大丈夫そうでしょうか。
続いて『this』を使用した相対的な処理の構築について考えてみましょう。以下のサンプルをみてみてください。
var o = { v: "初音ミク", f: function(){ // メソッド『f』を定義します。 console.log(o.v); // 文字列『初音ミク』が出力されます。 console.log(this.v); // 文字列『初音ミク』が出力されます。 } };
『console.log(o.v)』と『console.log(this.v)』の部分に注目です。
『this』を使用して、自身のメソッドが格納されているオブジェクト『o』のプロパティ『v』を出力、つまり『console.log(o.v)』と『console.log(this.v)』で同じ実行結果となっていますね。
これを利用すると下記のような処理が組めます。
var o = { v: "初音ミク", f: function(){ // メソッド『f』を定義します。 console.log(this.v); // このメソッドが格納されているオブジェクト『o』のプロパティ『v』(文字列『初音ミク』)が出力されます。 } }; var _o = { v: "IA", f: function(){ // メソッド『f』を定義します。 console.log(this.v); // このメソッドが格納されているオブジェクト『_o』のプロパティ『v』(文字列『IA』)が出力されます。 } }; o.f(); // 文字列『初音ミク』が出力されます。 _o.f(); // 文字列『IA』が出力されます。
『console.log(this.v)』のところに注目です。処理の記述は同じですが、格納されているオブジェクトのデータに合わせて出力内容が変わっていますね。
関数オブジェクトを渡してあげる形で定義すると相対的な処理がもっと実感しやすいかもしれません。以下がサンプルです。
var f = function(){ // 関数『f』を定義します。 if(this.v) console.log(this.v); // 『this.v』が存在すれば出力します。 }; var x = { v: "ワンパンマン" }; x.f = f; // 『x.f』に関数『f』を渡します。 var y = { v: "サイタマ" }; y.f = f; // 『y.f』に関数『f』を渡します。 x.f(); // 文字列『ワンパンマン』が出力されます。 y.f(); // 文字列『サイタマ』が出力されます。
同じ関数オブジェクトを渡しているにも関わらず、格納されたそれぞれのオブジェクトに合わせた処理結果が実現できていますね。
このパターンが一番効果的に使われるのは恐らく、『addEventListener()』でイベント登録をした場合になるかと思います。HTML要素に『addEventListener()』を使って関数やメソッドを渡した場合はそのHTML自身のイベントハンドラとしてその関数が登録されます。なのでその場合の『this』の参照先は自身が格納されているオブジェクト、つまりイベント追加されたHTML要素自身となります。
ちょっと以下のサンプルをみてみましょう。
<ul> <li>リスト1</li> <li>リスト1</li> <li>リスト1</li> </li> <script> var i, x = document.querySelectorAll("ul li"); // ul要素の中のli要素を全て取得します。 for(i = 0; i < x.length; ++i){ // オブジェクト『x』で取得したHTML要素の数だけループ。 x[i].addEventListener("click", function(){ // クリックイベントを登録します。 // ここに処理。 }, false); } </script>
上記のサンプルはul要素の中のli要素を全部取得して、それらにクリックイベントを登録するシンプルな処理ですね。
さて、上記のサンプルで「クリックされたli要素の文字色を赤色に変えたい」となった場合にどういう記述をすればスマートに構築できるでしょうか。
アルゴリズムとしては「クリックされたli要素を特定してその文字色を赤色に変える」って形になるのですが、「クリックされたli要素の特定」ってところがちょっとややこしい感じですよね。
しかし、これも『this』を使用することで簡単に解決できます。
HTML要素に『addEventListener()』で渡した関数オブジェクトの中の『this』はイベント登録されたHTML要素自身、つまりli要素となるので『this.style.color = "red";』と記述するだけで万事解決です。
<ul> <li>リスト1</li> <li>リスト1</li> <li>リスト1</li> </li> <script> var i, x = document.querySelectorAll("ul li"); // ul要素の中のli要素を全て取得します。 for(i = 0; i < x.length; ++i){ // オブジェクト『x』で取得したHTML要素の数だけループ。 x[i].addEventListener("click", function(){ // クリックイベントを登録します。 this.style.color = "red"; // この『this』はクリックされたli要素自身となるため、これだけで該当するli要素のみの文字色を赤色に変更できます。 }, false); } </script>
このパターンはめっちゃ使われるのでバッチリ覚えておいて下さい。
あと、とあるタイミングで呼び出した『this』の参照先を他のローカル関数などでも使用したい、といった場合にはローカルオブジェクトを定義してそいつに渡してしまいましょう。ローカル関数の中の『this』はwindowsオブジェクトになりますが、定義したローカルオブジェクトを使用することで操作することができます。
以下がサンプルになりますね。
<ul> <li class="test">リスト1</li> <li>リスト1</li> <li>リスト1</li> </li> <script> var i, x = document.querySelectorAll("ul li"); // ul要素の中のli要素を全て取得します。 for(i = 0; i < x.length; ++i){ // オブジェクト『x』で取得したHTML要素の数だけループ。 x[i].addEventListener("click", function(){ // クリックイベントを登録します。 var self = this; // ローカルオブジェクト『self』に『this』(この場合はli要素)を渡します。 var _f = function(){ if(self) self.style.color = "blue"; // ローカルオブジェクト『self』の文字色を青に変更します。この関数の中の『this』は『windowオブジェクト』になりますが、ローカルオブジェクト『self』を使用することで該当する要素の操作ができるようになります。 }; if(self.className === "test") _f(); // 該当する要素のクラス名が『test』に完全一致した場合のみローカル関数『_f』を実行します。 }, false); } </script>
『this』を渡してあげるオブジェクトとかのネーミングについてですが『self』という名前(識別子)が使用されることが多いです。他には『that』なんかも使用されますね。このネーミングのお作法はJavaScriptに限らず、他の言語でも使用されることが多いので覚えておきましょう。
そしてnew演算子を使用してオブジェクトを生成した場合の『this』の参照先は、その生成されたオブジェクトとなります。
var f = function(t){ // 関数『f』を定義します。 this.test = t; // 仮引数『t』に渡ってきたものを自身のプロパティ『test』に代入します。 }; var o = new f("ワンパンマン"); // 関数『f』をコンストラクタとしたオブジェクト『o』を生成します。 var _o = new f("サイタマ"); // 関数『f』をコンストラクタとしたオブジェクト『_o』を生成します。 console.log(o.test); // 文字列『ワンパンマン』が出力されます。 console.log(_o.test); // 文字列『サイタマ』が出力されます。
『this』を使用してアクセスしたオブジェクトのメソッドやプロパティに何か処理を加えるというパターンですね。この手の処理はよく構築されるパターンなんですが、ちょっと注意点があり、new演算子を書き忘れた場合は予想通りの処理となりません。
以下のサンプルを見てみましょう。
var f = function(s){ // 関数『f』を定義します。 this.test = s; // 仮引数『s』に渡ってきたものを自身のプロパティ『test』に代入します。 }; var o = f("ワンパンマン"); // 関数『f』をコンストラクタとしたオブジェクト『o』を生成するつもりだったんですが『new演算子』を忘れてみました (・ω<) テヘペロ console.log(o.test); // エラーになります。
最後の処理でエラーになっちゃってますね。そんでもって『console.log(test)』を実行してみましょう。するとあら不思議。
console.log(test); // 『ワンパンマン』が出力されます。
グローバル変数『test』が生成されており、その中に文字列『ワンパンマン』が入っちゃっていますね。これは、new演算子を書き忘れたために関数『f』の中の『this』がwindowオブジェクトになってしまったのが理由です。
繰り返しになりますがJavaScriptのグローバル変数の実体はwindowオブジェクトなので、windowオブジェクトにプロパティを追加する事とグローバル変数を定義する事は同義となります。
この手のnew演算子の書き忘れは良くやってしまいがちなところでもあり、しかもぱっと見じゃミスに気づきにくいところなんでめっちゃ気をつけてください。
というわけで『this』の概要は以上となります。特に『addEventListener()』の中の『this』の使用頻度はめちゃめちゃ高いのでバッチリ覚えてしまって下さい。
続いての記事では『this』の参照先を操作できる『call()』と『apply()』についてやっていきます。ではではこの辺で。またお会いしましょう。
この記事は桜舞が執筆致しました。
著者が愛する小型哺乳類 |
桜舞 春人 Sakurama HarutoISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。 |
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。