質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

JavaScriptの関数のクロージャについて

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 943
退会済みユーザー

退会済みユーザー

thisとクロージャについて

JavaScript初級者から中級者になろう 九章第五回 クロージャ
上記のサイトでbindとthisについての説明があるのですが
分からない部分があるので教えて下さい。

分からない部分

サイト内で以下の2つのコードが出てきます。

// コード1  
function MyDiv(name){
    this.div = document.createElement("div");
    this.name=name;
    this.div.textContent="このdiv要素は"+name+"です";
    document.body.appendChild(this.div);

    this.div.addEventListener('click',function(e){
      console.log(this.name);
    },false);
  }

  var div1 = new MyDiv("div1");
// コード2
function MyDiv(name){
    this.div = document.createElement("div");
    this.name=name;
    this.div.textContent="このdiv要素は"+name+"です";
    document.body.appendChild(this.div);

    this.div.addEventListener('click',(function(i){
      return function(e){
        console.log(i.name);
      };
    })(this),false);
  }

var div1 = new MyDiv("div1");


この2つのコードを比較して「コード1を実行するとログには undefined が表示されるが、コード2を実行するとログに div1 が表示される」というものです。

質問1 

サイト内では

(コード1について)クリックすると表示された値はundefinedです。
① これは、thisのプロパティとしてnameなどというものは無かった、
② つまりthisは、MyDivのインスタンスを表していないということです。

③ これはひょっとすると、イベントとして登録された関数(イベントハンドラ)が
  インスタンスのメソッドでは無いからかもしれません。


と書かれています。
この文章中の②と③がよく分かりません。

②「thisはMyDivのインスタンスを表していない」とは具体的にどういうことですか?
コード1の最後の行でvar div1 = new MyDiv("div1");と書いているけど、関数の引数nameにdiv1が入っていないということですか?

③「イベントとして登録された関数が "インスタンスのメソッドではない" からかも」とはどういうことですか?

質問2

また、コード2の下から3行目、第2引数のthisについてサイトで
「引数iにthisを渡している」
「外側の無名関数function(i)で利用できる変数とは変数iです。外側の無名関数が呼ばれた時点で、その引数はthisです。
このthisはコンストラクタ内で使用されたものであるから、このthisは確かにインスタンスを表します。つまり、ここでは変数iはMyDivのインスタンスであるのです。」
とありますが、この部分も理解できません。

初歩的な質問ですみません。よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

大事なのは、それぞれのthisが何を参照しているのかということです。とにかくこのことに注目して見てください。

なぜなら、thisは実行の仕方やタイミングによって、それが示すものがコロコロ変わり得るからです。それがこの言語の大きな特徴であり、時として混乱を生むものでもあります。

回答1

コード1におけるthisは、ほとんどがMyDivコンストラクタを通じて生成されるインスタンス、つまりdiv1を指します。続けてdiv2、div3をnewで生成していけば、それぞれdiv2、div3を生成するようになるでしょう。

その唯一の例外が、

//↓こっちのthisはほかと同じでdiv1
this.div.addEventListener('click',function(e){
  console.log(this.name);  //←このthis!
},false);

イベントリスナの中で使用するthisです。イベントリスナの中で使用するthisは、そのイベントリスナの仕掛け先である、と決められていますので、このthisはdiv1ではなく、div1.divを指します。

その証拠に、ほんのちょっとだけコード1を改造すると、

this.div.addEventListener('click',function(e){
  console.log(this === div1.div);  //true
},false);

このような結果が得られました。

ですから、this.name、つまりdiv1.div.nameを参照しようとしても、そのようなプロパティは定義されていませんので、undefinedが返ってきます。


それからインスタンスメソッドについて。

function MyDiv(name){
  this.name=name;
}

MyDiv.prototype.getName = function() {
  return this.name;
}

var div1 = new MyDiv("div1です");
console.log(div1.getName());  //div1です

var div2 = new MyDiv("div2です");
console.log(div2.getName());  //div2です

インスタンスメソッドというのは、このようなものを言います。

注目すべきは関数の中のthisですが、それぞれdiv1やdiv2を指しています。インスタンスメソッドであれば、すべからくこのようにthisがインスタンスを指してくれるはずです。

もし、イベントリスナがインスタンスメソッドと同じものならば、イベントリスナのthisもインスタンスを指してくれるはずですが、先述のように、イベントリスナのthisはインスタンスを指しているわけではありませんでした。

そのため、

イベントとして登録された関数が "インスタンスのメソッドではない" からかも

このような一文が出てきたということです。

回答2

先の例のように、thisの参照先が変わることで嬉しいケースもあれば、そうでないケースもあるかもしれません。

イベントリスナの中のthisも、その外側で使っているのと同じように、生成されたインスタンスを参照していてほしい、ということも十分あり得ます。

そのような場合によく行われているのが、そもそもthisというキーワードを使うから、その参照先が変わって困る。だったらthisを使うのをやめよう、ということで、thisを変数に退避させるという方法です。

var that = this;
this.div.addEventListener('click',function(e){
  console.log(that === div1);  //true
  console.log(that.name);      //div1
},false);

この方法であれば、thisを使っていませんので、thisの参照先に惑わされることはありません。

なお、コード2でやっていることも、これと同じです。thisをvarで宣言する変数ではなく、関数の引数であるiに入れることで、thisをイベントリスナの中で使うことを避けています。


そして、もう1つの方法が、bindを使った方法です。bindを使うと、その関数の中のthisの参照先を、任意の対象に固定することができます。

this.div.addEventListener('click', function(e) {
  console.log(this.name);  //div1
}.bind(this), false);

bindの時点でのthisはdiv1ですので、bindによって関数内のthisはdiv1に固定されました。そのため、元のままの書き方でもthis.nameはdiv1.nameとなり、値を得ることができます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/09/21 13:34

    理解することができました。
    有難うございました!

    キャンセル

+1

質問1への関して

this.div = document.createElement("div");
this.div.addEventListener

ページに表示されるdiv要素はthis.divには紐付けられていますが、Mydivそのものでは無いからではないでしょうか?click時に呼び出された関数のthisはdiv1のdomオブジェクトの方であり、Mydivのインスタンスでは無いといった感じ?

addEventListener() を使って要素にハンドラ関数を設定したとき、ハンドラの中の this の値は要素への参照となります。これはハンドラに渡された event 引数の currentTarget プロパティの値と同じです。MDN

質問2に関して

(function(i){})(this)
こういう構造になっています。functionを括弧で包むとその関数はそこで即時実行されます。
後方の括弧内の値は関数への引数に対応します。
「javascript 即時実行 引数」あたりでGoogle検索するとそれらしい記事がhitすると思います。
ここで引き渡されたthisは(その括弧の中では)未定義なので、this.div.addEventListener('click',(function(i){})(this),false);これの外まで探索され、そこにあるthisが参照されます。
「該当範囲に変数が無くても、外側にあるならばそれが使用される」のがクロージャーの特徴という主旨の記事なんじゃないかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/09/21 13:34

    回答有難うございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる