購読中です 読者をやめる 読者になる 読者になる

コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

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版から入るといいかと思います。

JavaScript 第5版

JavaScript 第5版


サイ本を読んだ後に、これを読むと目からウロコというか、JavaScriptの作法がよくわかります。絶対読むべき本だと思います。
JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法


jQueryのバージョンとしては古いのですが、jQuery全般について例を交えてよく書かれています。end()メソッドの使い方とか非常に有効です。
jQueryクックブック

jQueryクックブック