No Programming, No Life

プログラミング関連の話題や雑記

JavaScriptのthisではまった

JavaScriptのthisではまったのでメモしておきます。

はじめに

JavaなどのようなOOPに慣れ親しんでいると、JavaScriptのようなプロトタイプベースの言語を触ると痛い目を見ますね。あまりにも自由度が高すぎるためです。オブジェクトとそのメンバは密接に結びついているという概念を一度忘れ去る必要があるみたいです。

thisを使った例

<html>
<head>
<title>thisのテスト</title>
<script type="text/javascript">
var someObj = {
    name   : 'SomeObject',
    whoAmI : function() {
        alert('I am ' + this.name + '.')
    }
}
function init() {
    var button1 = document.getElementById('button1')
    var button2 = document.getElementById('button2')
    button1.onclick = someObj.whoAmI
    button2.onclick = function(){ someObj.whoAmI() }
}
</script>
</head>
<body onLoad="init()">
<button id="button1" name="ぼたん1">押してね1</button><br>
<button id="button2" name="ぼたん2">押してね2</button><br>
</body>
</html>

実行結果

「押してね1」ボタンをクリックすると「I am ぼたん1.」というアラートが、
「押してね2」ボタンをクリックすると「I am SomeObj.」というアラートが出ます。

イベントハンドラに関数オブジェクトを代入する

    button1.onclick = someObj.whoAmI

この代入はsomeObjというオブジェクトのwhoAmIというフィールドに設定されていた関数オブジェクトをbutton1のonclickイベントハンドラに設定することを意味します。
つまり、ボタンがクリックされた際のオーナー(呼び出し主)はボタン1オブジェクト自身となるため、nameで参照できるのは「ぼたん1」となるわけです。

イベントハンドラにクロージャを代入する

    button2.onclick = function(){ someObj.whoAmI() }

そしてこちらは、function(){ ... } という形で無名関数(≒クロージャ)を代入している。
この場合、クリックされた際はオーナーであるボタン2オブジェクトがクロージャを呼び出し、クロージャ内部の処理で、someObjをオーナーとしてのwhoAmIというフィールドに設定されている関数オブジェクトが呼び出されます。オーナーがsomeObjなので、「SomeObj」が出力されるという仕組み。

キーポイント

大事なのは、

function() {
    alert('I am ' + this.name + '.')
}

という関数オブジェクトは、「その時のオーナー(this)のnameというフィールドを使ってアラートする」という処理が行われるということです。
関数オブジェクトは誰のものでもなく、今回たまたま宣言された時にsomeObjの上に乗っかっていたと考えると分かりやすいかもしれません*1。誰のものでもないので、動的に付けたり外したりできるわけですね。*2

まとめ

ある関数の中でthisはオーナー(呼び出し主)を指している。
Javaなどのようにインスタンスを固定で指すわけではないことに注意する。

2009-05-02追記

トラックバックid:SiroKuroさんよりご指摘いただいた、部分を修正しました。
Javaでは…thisが参照するインスタンス「の持つメソッド」は固定的にできるわけですね。

*1:本当はsomeObjじゃなくても、誰のところでも生きていける(笑)

*2:Javaでは動的に付けたり外したりできず、class宣言時にすべてが決まってしまうから、thisが参照するインスタンスの持つメソッドは固定的にできるわけですね。