« 【JavaScript】function定義方法による動作の差異(覚書) | トップページ | ひとまず今年の出張期間終了 »

2007/12/22

【JavaScript】これ(this)はなに?

JavaScriptでプログラムを組んだり人の作ったコードを読んでいると、よく出てくる“this”という表現。
これ、例えば同一の関数内のはずなのに、そのときどきで指しているものが異なったりして結構わかりにくい、と感じる方も多いのではないでしょうか。
■同一関数内で'this'が指すものが異なる例
function whatisthis(obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
}

var obj={
    obj_whatisthis : whatisthis
};
var ref_whatisthis = obj.obj_whatisthis;

var obj2={
    ref2_whatisthis : obj.obj_whatisthis
};

//(1)関数として呼び出し
/* 1-1 */   whatisthis(obj);                //  [A]true  [B]false
/* 1-2 */   obj.obj_whatisthis(obj);        //  [A]false [B]true
/* 1-3 */   ref_whatisthis(obj);            //  [A]true  [B]false
/* 1-4 */   obj2.ref2_whatisthis(obj);      //  [A]false [B]false

//(2)コンストラクタ(constructor)として呼び出し
/* 2-1 */   new whatisthis(obj);            //  [A]false [B]false
/* 2-2 */   new obj.obj_whatisthis(obj);    //  [A]false [B]false
/* 2-3 */   new ref_whatisthis(obj);        //  [A]false [B]false
/* 2-4 */   new obj2.ref2_whatisthis(obj);  //  [A]false [B]false
私的には、結局のところ(関数オブジェクトの実体がどこにあるかにはよらず)実際に呼び出されるときに、
  1. あるオブジェクトのプロパティとして関数が呼び出される場合、thisは当該オブジェクトを指す。
  2. 上記以外の場合には、thisはGlobalオブジェクト(ブラウザの実装では通常"window")を指す。
というルールに集約されるのかな、と理解しています。

また、補足事項としては、
  • 関数がコンストラクタ(constructor)として呼ばれた場合(var obj=new <関数名>()のようにnewを付けて呼びだした場合)、その関数は新規に作成されたオブジェクトのプロパティとして呼び出される。
    従って、ルール1から関数内のthisは新規に作成されたオブジェクトを指す。
  • 関数がcallもしくはapplyで呼びだされた場合(<関数名>.call(<オブジェクト>,<引数1>[,<引数2>...])、もしくは、<関数名>.apply(<オブジェクト>,<引数の配列>))、その関数はcallもしくはapplyの第1引数(<オブジェクト>)に指定されたオブジェクトのプロパティとして呼び出される。
    従って、ルール1から関数内のthisは第1引数で指定されたオブジェクトとなる(言い換えると、関数内で"this"として扱いたいオブジェクトをcallもしくはapplyの第1引数で明示出来る)。
  • Global領域で'function <関数名>(){...}'もしくは'var <関数名>=function(){...}'で定義された関数は、Globalオブジェクト("window")のプロパティとなる。
    従って(この関数が<関数名>を直接指定して呼び出される場合)ルール1から関数内のthisはGlobalオブジェクトとなる。
  • 関数内で'function <関数名>(){...}'もしくは'var <関数名>=function(){...}'で定義された関数は、オブジェクトのプロパティではない。
    従って(この関数が<関数名>を直接指定して呼び出される場合)ルール2から関数内のthisはGlobalオブジェクトとなる。
のようになるのかな、と。
例えば、上の方で挙げた例(関数whatisthis()はGlobal領域で定義された関数)では、
  • 1-1
     →"whatisthis"がGlobalオブジェクトのプロパティであるため、"this"はGlobalオブジェクト("window")。
  • 1-2
     →"obj_whatisthis"が"obj"オブジェクトのプロパティであるため、"this"は"obj"オブジェクト。
  • 1-3
     →"ref_whatisthis"がGlobalオブジェクトのプロパティであるため、"this"はGlobalオブジェクト("window")。
  • 1-4
     →"ref2_whatisthis"が"obj2"オブジェクトのプロパティであるため、"this"は"obj2"オブジェクト。
  • 2-x
     →関数がコンストラクタとして呼び出される場合、新規に作成されたオブジェクトのプロパティとしての呼び出しとなる。従って、"this"は新規に作成されたオブジェクトを指す。
もう少し複雑な例をあげると、
function    test_whatisthis() {
    function    whatisthis(obj) {
        alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
    }
    this.whatisthis=whatisthis;
    
    whatisthis(this);           // #1
    this.whatisthis(this);      // #2
    whatisthis.call(this,this); // #3
    
    var obj={whatisthis:whatisthis};
    whatisthis(obj);            // #4
    obj.whatisthis(obj);        // #5
    whatisthis.call(obj,obj);   // #6
}

//(1)関数として呼び出し
test_whatisthis();
    //  #1:[A]true  [B]true
    //  #2:[A]true  [B]true  ※IE6では [A]false [B]false ??
    //  #3:[A]true  [B]true
    //  #4:[A]true  [B]false
    //  #5:[A]false [B]true
    //  #6:[A]false [B]true

//(2)コンストラクタ(constructor)として呼び出し
var temp=new test_whatisthis();
    //  #1:[A]true  [B]false
    //  #2:[A]false [B]true
    //  #3:[A]false [B]true
    //  #4:[A]true  [B]false
    //  #5:[A]false [B]true
    //  #6:[A]false [B]true
関数test_whatisthis()はGlobal領域で定義されたものです。

まず、「(1)関数として呼び出し」た場合には、"test_whatisthis"内でのthisはGlobalオブジェクト("window")になります。
この前提で、
  • #1  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #2  →"test_whatisthis"内での"this"オブジェクト(即ちGlobalオブジェクト)のプロパティとして呼び出されているため、"whatisthis"内のthisはGlobalオブジェクト("window")。
  • #3  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=this=window)のプロパティとして<引数>(=this=window)を指定して呼び出す』という意味になるため、"whatisthis"内のthisはGlobalオブジェクト("window")。
  • #4  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #5  →"obj"オブジェクトのプロパティとして呼び出されているため、"whatisthis"内のthisは"obj"オブジェクト。
  • #6  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=obj)のプロパティとして<引数>(=obj)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"obj"オブジェクト。
次に「(2)コンストラクタ(constructor)として呼び出し」た場合には、"test_whatisthis"内でのthisは新規に作成されたオブジェクト(仮に"temp"とします)になります。
この前提で、
  • #1  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #2  →"test_whatisthis"内での"this"オブジェクト(即ち"temp"オブジェクト)のプロパティとして呼び出されているため、"whatisthis"内のthisは"temp"オブジェクト。
  • #3  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=this=temp)のプロパティとして<引数>(=this=temp)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"temp"オブジェクト。
  • #4  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #5  →"obj"オブジェクトのプロパティとして呼び出されているため、"whatisthis"内のthisは"obj"オブジェクト。
  • #6  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=obj)のプロパティとして<引数>(=obj)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"obj"オブジェクト。
……と、大体説明出来るかなぁ?と思っていましたが、test_whatisthis()の(1)-#2の結果が、IE6では"[A]false [B]false"となってしまい、混乱してしまいました。
上記説明の通り、whatisthisはGlobalオブジェクト("window")のプロパティとして呼び出されており、また引数も"window"になることから、"[A]true [B]true "でないとおかしいはずです(実際に、FirefoxやOperaではこうなります)。
ところがIE6でも、(1)-#2のときのwhatisthis関数内における"this"の持つプロパティを調べてみると、"window"オブジェクトが持っているプロパティと一致するんですね。
とすると……window.whatisthis()が呼び出された瞬間、"window"のコピーが作成され、これが"this"として扱われている???
このIE6での現象をもっと単純に再現すると、
window.whatisthis=function (obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
};
window.whatisthis(window); // IE6:[A]false [B]false vs others:[A]true [B]true
となります。
これが、window.whatisthisをvar whatisthisに置換えたら、想定通りの動きとなる不思議。
var whatisthis=function (obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
};
window.whatisthis(window); // [A]true [B]true
要はwindow.<関数名>=function(){...};という定義方法だと変な動きになる?これまた、ややこしいバグの温床となりそうな現象だ……。

« 【JavaScript】function定義方法による動作の差異(覚書) | トップページ | ひとまず今年の出張期間終了 »

パソコン・インターネット」カテゴリの記事

覚書」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: 【JavaScript】これ(this)はなに?:

« 【JavaScript】function定義方法による動作の差異(覚書) | トップページ | ひとまず今年の出張期間終了 »

戻るリンク追加

カレンダー

2022年4月
          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

ココログカレンダーPlus(旧2)

検索


    • Web全体 サイト内
    • 蔵書のISBNを入力して下さい
    • はじめる前
      初級者向け
      上級者向け
      ブログ紹介
      結果を表示

コメントリストツリー化

無料ブログはココログ