JavaScriptのthisをjQueryを含めて検証してみた
JavaScriptのthisは使用するタイミングによって、その内容が変わります。JavaScriptを記述する上でthisは基本的な要素であり、かつ理解が必須の要素でもあります。自分自身があまり整理できていなかったこともありましたので、確認の意味も込めて検証して見ました。
さて、thisそのものに注目するとthisはJavaScriptにおいてキーワードであって変数ではありません。そのため、thisの内容を参照することはできますが、その値を任意に変更することはできません。
今回はJavaScriptそのもののthisが参照する値がどうなるのか、またjQueryにおいても色々と変化するためそのパターンをひと通り検証し、整理します。
プレーンなJavaScriptの場合
最初にjQueryを考慮せずにプレーンなJavaScriptのthisを確認します。
単純にthisを照会するとwindowオブジェクトであることがわかります。以下のサンプルでは this === window はtrueになります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> console.log('this === window : ' + (this === window)); </script> </body> </html>
次に関数呼び出しの例を確認します。関数の中のthisもwindowオブジェクトを参照している事がわかります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> // 関数の例 function func() { console.log('# func() function'); console.log('this === window : ' + (this === window)); } console.log('### 関数'); func(); </script> </body> </html>
コンストラクタ関数
ここまでのthisはすべてwindowオブジェクトを参照してましたが、ここからが違ってきます。
以下の例ではコンストラクタ関数内でのthisの動作を検証しています。コンストラクタ関数をnew演算子を使って呼び出すと、空のオブジェクトが生成されそれがthisとして設定されます。ここで重要なのはコンストラクタ関数(と言ってもそういう用途として作成した関数)はnew演算子を使用しないとthisにオブジェクトが設定されないということです。以下の例では同じ関数をnew演算子を用いた呼び出しと、通常の関数呼び出しを検証しています。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> // コンストラクタ関数の例 function Point(x, y) { this.x = x; this.y = y; console.log('# Point() function'); console.log('this === window : ' + (this === window)); console.log('this instanceof Point : ' + (this instanceof Point)); } Point.prototype.print = function() { console.log('Point(this.x,this.y)=(' + this.x + ',' + this.y + ')'); } console.log('### コンストラクタ関数'); console.log('## newする'); var point = new Point(1, 2); point.print(); console.log("window.hasOwnProperty('x')=" + window.hasOwnProperty('x')); console.log('\n'); console.log('## newしない'); Point(3, 4); console.log("window.hasOwnProperty('x')=" + window.hasOwnProperty('x')); </script> </body> </html>
実行すると以下のようなログが出力されます。newした場合にはwindowオブジェクトではなくPointインスタンスであることがわかります。
### コンストラクタ関数 ## newする # Point() function this === window : false this instanceof Point : true Point(this.x,this.y)=(1,2) window.hasOwnProperty('x')=false ## newしない # Point() function this === window : true this instanceof Point : false window.hasOwnProperty('x')=true
具体的に確認してはいませんが、新しいブラウザではJavaScriptをstrictモードで実行することができます。strictモードの場合、newを付けないコンストラクタ関数の呼び出しではthisキーワードは未定義値になります。
そのためnewを付けないコンストラクタ関数の呼び出しを抑止できないことはありませんが、古い環境との共存を考慮した場合には完全な抑止は難しくなります。
strictモードについては、https://developer.mozilla.org/ja/JavaScript/Strict_modeを確認してください。
イベント
イベントハンドラで関数を指定すると、その関数内のthisではそのイベントの発生元となった要素がセットされます。また、eventオブジェクトのcurrentTargetプロパティにも呼び出し元の要素がセットされます。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <input type="button" id="button1" value="ボタン"> <script type="text/javascript"> // イベントの例 console.log('### イベント'); document.getElementById('button1').onclick = function(event) { console.log('## button1#click'); console.log('# this'); console.log('this=' + this.tagName); console.log('id=' + this.getAttribute('id')); console.log('this instanceof HTMLElement : ' + (this instanceof HTMLElement)); console.log('this.constructor=' + this.constructor); // event.currentTarget console.log('# event.currentTarget'); console.log('event.currentTarget=' + event.currentTarget.tagName); console.log('event.currentTarget.id=' + event.currentTarget.getAttribute('id')); }; </script> </body> </html>
実行すると以下のようなログが出力されます。thisもevent.currentTargetでも同じイベントの対象となる要素が取得できていることが確認できます。
### イベント ## button1#click # this this=INPUT id=button1 this instanceof HTMLElement : true this.constructor=function HTMLInputElement() { [native code] } # event.currentTarget event.currentTarget=INPUT event.currentTarget.id=button1
jQueryの場合
jQueryを使用した場合も基本的には、上記のように動作するがjQuery固有の注意すべき点がいくつかあります。
each
jQueryオブジェクトをeachメソッドでループさせた場合のthisは、対象の(jQueryオブジェクトではない)DOMオブジェクトとなります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="title">JavaScript this テスト</div> <div id="contents">jQueryのテスト</div> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> <script type="text/javascript"> // eachのthis console.log('### eachのthis'); $('div').each(function(i) { console.log('# div(' + i + ')=' + $(this).html()); console.log('this === window : ' + (this === window)); console.log('this.tagName=' + this.tagName); console.log('this.id=' + this.getAttribute('id')); console.log('this instanceof HTMLElement : ' + (this instanceof HTMLElement)); console.log('this.constructor=' + this.constructor); console.log("this.hasOwnProperty('length')=" + this.hasOwnProperty('length')); console.log("$(this).hasOwnProperty('length')=" + $(this).hasOwnProperty('length')); console.log('\n'); }); console.log('### jQueryオブジェクト'); console.log("$('div') instanceof jQuery.fn.init : " + ($('div') instanceof jQuery.fn.init)); console.log("$('div').constructor=" + $('div').constructor); </script> </body> </html>
実行後の出力は以下のようになります。
### eachのthis # div(0)=JavaScript this テスト this === window : false this.tagName=DIV this.id=title this instanceof HTMLElement : true this.constructor=function HTMLDivElement() { [native code] } this.hasOwnProperty('length')=false $(this).hasOwnProperty('length')=true # div(1)=jQueryのテスト this === window : false this.tagName=DIV this.id=contents this instanceof HTMLElement : true this.constructor=function HTMLDivElement() { [native code] } this.hasOwnProperty('length')=false $(this).hasOwnProperty('length')=true ### jQueryオブジェクト $('div') instanceof jQuery.fn.init : true $('div').constructor=function (a,b){return new e.fn.init(a,b,h)}
イベント
jQueryでも通常のJavaScriptと同様にイベント発生元のDOMオブジェクトがthisに設定されあます。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <input type="button" id="button1" value="ボタン"> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> <script type="text/javascript"> // イベントのthis $('#button1').click(function() { console.log('### イベントのthis'); console.log('# click') console.log('this === window : ' + (this === window)); console.log('this=' + this.tagName); console.log("this.hasOwnProperty('length')=" + this.hasOwnProperty('length')); console.log('id=' + this.getAttribute('id')); }); </script> </body> </html>
実行結果は以下のようになります。
### イベントのthis # click this === window : false this=INPUT this.hasOwnProperty('length')=false id=button1
each内部での関数呼び出し
each内部でそこで定義した関数を呼び出した場合には、その関数内部ではthisはwindowオブジェクトになります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="title">JavaScript this テスト</div> <div id="contents">jQueryのテスト</div> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> <script type="text/javascript"> // eachと関数の組み合わせ console.log('### 関数のテスト'); $('div').each(function(i) { console.log('div(' + i + ')=' + $(this).html()); (function() { console.log('inner function i=' + i); console.log('div(' + i + ')=' + $(this).html()); console.log('this === window : ' + (this === window)); console.log('\n'); })(); }); </script> </body> </html>
実行結果は以下のようになります。
### 関数のテスト div(0)=JavaScript this テスト inner function i=0 div(0)=null this === window : true div(1)=jQueryのテスト inner function i=1 div(1)=null this === window : true
このあたりはスコープチェーンを理解していればさほど難しくはありません。スコープチェーンについては、ハイパフォーマンスJavaScriptのp.16のスコープの管理がわかりやすいと思います。
まとめ
JavaScriptは定番といえる本がオライリーから出ているので、それを読めばこのあたりはパーフェクトです。具体的には以下の3冊がおすすめです。
通称サイ本。これが難しいのであれば、初めてのJavaScript 第2版から入るといいかと思います。
- 作者: David Flanagan,村上列
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/08/14
- メディア: 大型本
- 購入: 52人 クリック: 1,011回
- この商品を含むブログ (270件) を見る
サイ本を読んだ後に、これを読むと目からウロコというか、JavaScriptの作法がよくわかります。絶対読むべき本だと思います。
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (72件) を見る
jQueryのバージョンとしては古いのですが、jQuery全般について例を交えてよく書かれています。end()メソッドの使い方とか非常に有効です。
- 作者: jQuery Community Experts,株式会社クイープ
- 出版社/メーカー: オライリージャパン
- 発売日: 2010/08/18
- メディア: 大型本
- 購入: 10人 クリック: 333回
- この商品を含むブログ (36件) を見る