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

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

ただいまの
回答率

90.48%

  • JavaScript

    16971questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

JavaScriptにて、配列の要素一つ一つにイベントを実装したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 2,751
退会済みユーザー

退会済みユーザー

いつもお世話になっています
Javascriptでのイベント処理にてわからないことがありましたので、質問させて頂きました
サムネイルの様に、複数枚並んだ画像全てにイベント処理を実装したく、下記のようなコードを記述しましたが、上手くいきませんでした(show関数が実行されなかったり、iが配列の最大値の時のみ実行されたりします)

この記述がなぜ上手くいかないのか、どうすれば上手くいくのかを、教えて頂ければと思います
お手数ですが、よろしくお願い申し上げます



// 関数定義
function show(x){
    console.log(x);
}

// イベント処理
var img = document.getElementsByTagName('img');

for(var i = 0; i < img.length; i++){
    img[i].onclick = function(){
        show(i);
    };
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+5

click が呼び出された時点で呼び出される変数 i が for 文を通過した後に参照される為ですね。
変数 i を束縛する方法はいくつかありますが、handleEvent を使用する方法はどうでしょうか。
(JSFiddle にもサンプルをUPしました。)

<img src="sample-1.jpg" alt="sample1">
<img src="sample-2.jpg" alt="sample2">

<script>
(function () {i
  function handleClick (event) {
    console.log(this.x);
  }
  
  var imgs = document.getElementsByTagName('img');
  
  for (var i = 0, l = imgs.length; i < l; ++i) {
    imgs[i].addEventListener('click', {x: i, handleEvent: handleClick}, false);
  }
}());
</script>

handleEvent は addEventListener に備わった機能なので第一引数の event オブジェクトと併用できます。


(2015/09/08 19:14追記)
もし、IE8- にも対応させるならクロージャで変数束縛する方法があります。
(JSFiddleにサンプルUPしました。)

(function () {
  function createHandleClick (i) {
    return function handleClick (event) {
      console.log(i, event.type);
    };
  }

  function addEvent (element, type, listener) {
    if (element.addEventListener) {
      element.addEventListener(type, listener, false);
    } else if (element.attachEvent) {
      element.attachEvent('on' + type, listener);
    } 
  }

  function init () {
    var imgs = document.getElementsByTagName('img');
  
    for (var i = 0, l = imgs.length; i < l; ++i) {
      addEvent(imgs[i], 'click', createHandleClick(i));
    }
  }

  init();
}());


(2015/09/08 23:00追記)
data-* 属性で index を割り振っておくのも一つの手段だと思います。
(JSFiddleにサンプルUPしました)

<img src="sample-1.jpg" alt="sample1" data-i="1">
<img src="sample-2.jpg" alt="sample2" data-i="2">

<script>
(function () {
  function handleClick (event) {
    var img = event.target || event.srcElement; // target要素を得る(Standard || IE8-)
    console.log(img.getAttribute('data-i'), event.type); // data-*属性を参照
  }

  function addEvent (element, type, listener) {
    if (element.addEventListener) {   // Standard
      element.addEventListener(type, listener, false);
    } else if (element.attachEvent) { // IE8-
      element.attachEvent('on' + type, listener);
    } 
  }

  function init () {
    var imgs = document.getElementsByTagName('img');
  
    for (var i = 0, l = imgs.length; i < l; ++i) {
      addEvent(imgs[i], 'click', handleClick);
    }
  }

  init();
}());
</script>

img要素の親要素ノードでバブリングを利用してキャプチャすると更にスマートになります。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2015/09/08 17:30

    なるほど、for文を抜けた後でイベントが呼び出されるのが原因みたいですね
    自分のJavaScriptに関する知識が浅いので、上記のコードをきちんと理解できているか不安なのですが
    handleEventを持つオブジェクトにiの値を保持しておくという認識で正しいでしょうか?

    キャンセル

  • 2015/09/08 17:40

    addEventListener の第二引数にオブジェクトが指定された場合、ハンドラ関数は handleEvent プロパティを参照し、this 値は第二引数のオブジェクト {x: i, handleEvent: handleClick} に束縛されます。
    つまり、下記コードとほぼ等価です。
    imgs[i].addEventListener('click', handleClick.bind({x: i, handleEvent: handleClick}), false);
    listener 関数内で this 値で event.currentTarget を参照しなければこの方法に伴う問題は発生しないと思われます。

    キャンセル

  • 2015/09/08 22:10

    自分の知識が浅く、丁寧な回答をしていただいたにもかかわらず、あまり理解することができませんでした。申し訳ありません
    ただ
    1.メソッドをオブジェクトで束縛するためには、bindする必要がある
    2.handleEventプロパティを使うことで、addEvantListenerにオブジェクトを指定することができる
    3.上記1・2のどちらでもオブジェクト固有のイベント処理を行うことができる
    かなと思いました
    もう少し理解を深めた上で、回答を見直したく思います

    キャンセル

  • 2015/09/08 23:08

    大雑把に説明すれば、「listener関数に引数を渡すのは現実的ではないので this 値に束縛して渡しましょう」という事です。
    this 値の束縛として Function#call, Function#apply, Function#bind の挙動を確かめておくと理解できると思います。
    (function () {
    console.log(this.x); // 1
    }.call({x: 1}));

    親コメントでクロージャ版、data-* 属性版コードも追記しましたのでよろしければ参考にして下さい。
    私としては data-* 属性を使う手法がお勧めです。
    index による割り振りは動的にimg要素が増えただけで変化する不安定なものなのでしっかりとした固定値を割り当てた方がぶれなくて良いと思います。
    場合によっては、alt属性値やsrc属性値(画像ファイル名)にindex値に類する情報があるので、それらの属性値から正規表現でindex値を抽出するのも有です。

    キャンセル

+1

どういう処理を行うかわかりませんが、
私なら次のように書きます。

ポイントは2つあるんですが、
1つ目は基本ですがimgタグの後にscriptタグを書く
2つ目はiの評価がクリック時に行われる書き方なのでbindでその時の値を渡しておく
って感じです。
それぞれshow関数が実行されない件と、iが配列の最大値で実行される件の対策です。
参考になると良いのですが。

<img src="image.jpg" width="20px">
<img src="image2.jpg" width="20px">
<script>
// 関数定義
function show(x){
    console.log(x);
}

// イベント処理
var img = document.getElementsByTagName('img');

for(var i = 0; i < img.length; i++){
    img[i].onclick = show.bind(img[i], i)
}
</script>

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2015/09/08 17:04

    すみません、説明が不足していました
    スクリプトは別ファイルとして記述しており、htmlファイル内に
    <img src="image.jpg" width="20px">
    <img src="image2.jpg" width="20px">
    のような記述がされているという設計です

    bindメソッドを使うのが肝みたいですね
    あまりbindメソッドを理解しきれていないのですが
    bindメソッドを使用した場合はimg要素一つ一つにshowメソッドが作られ、イベントとして登録されるのに対し
    使用しない場合は同一のshowメソッドが参照されてしまうということでしょうか?

    キャンセル

  • 2015/09/08 17:25

    > スクリプトは別ファイルとして記述しており、htmlファイル内に
    > <img src="image.jpg" width="20px">
    > <img src="image2.jpg" width="20px">
    > のような記述がされているという設計です

    念のためこちらにも返答するのですが、
    別ファイルでも問題ないです。
    ただし対象となるimgタグはスクリプトを読み込む前に記述する必要があります。
    <img src="image.jpg" width="20px">
    <img src="image2.jpg" width="20px">
    <script src="hoge.js"></script>
    のような感じですね。


    > bindメソッドを使用した場合はimg要素一つ一つにshowメソッドが作られ、イベントとして登録されるのに対し
    > 使用しない場合は同一のshowメソッドが参照されてしまうということでしょうか?

    はい。私の理解ではそうです。
    さらに、使用しない場合はiが評価されるのが実行時(今回の場合はクリック時)なので、
    show()のconsole.log(x)は毎回「2」が表示される動きになっていました。
    bindを使うことによって引数を渡した状態にしておくことができるので、
    今回はbindを使いました。

    キャンセル

  • 2015/09/08 17:45

    質問文のサンプルコードでは問題になりませんが、Function#bind で第二引数を指定すると、listener 関数の第一引数に存在した event オブジェクトを受け取れなくなる点に注意が必要です。
    this 値の束縛に留めておけば、event オブジェクトも受け取れます。

    キャンセル

  • 2015/09/08 18:30

    think49さん
    いつも質問の内容のみに絞って考えているので、
    こういったご指摘は助かります。
    eventが必要な場合は単純に bind(this, i); に
    すれば取れる気がしますが、何か問題ありますか…?
    質問になってしまってすみません。
    もしご覧になられていたら回答いただけると助かります。

    rin_kataさん
    そういえば私の書いたbindを使う方法はIE8以前では使えません。
    対応すべきブラウザがIE9以降でない場合は別の実装にする必要があります。
    その場合は色々大変なので rk7fd3s さんの仰るようにjQueryを使うのが手っ取り早いですね…

    キャンセル

  • 2015/09/08 18:58

    To: notableさん
    > eventが必要な場合は単純に bind(this, i); にすれば取れる気がしますが、何か問題ありますか…?
    Function#bind の第二引数で event が渡るべき第一引数を束縛しているので event を参照できません。
    http://jsfiddle.net/m627kwag/2/
    bind({i: i}) なら event を参照可能です。
    http://jsfiddle.net/m627kwag/4/
    IE8- を視野に入れるならクロージャで変数束縛が使えますが、そもそも論として index 束縛がどうかという気もするので data-* 属性に埋め込んでおくスタイルもありだと思いました。

    キャンセル

  • 2015/09/08 22:39

    notableさん
    >使用しない場合はiが評価されるのが実行時(今回の場合はクリック時)なので、
    show()のconsole.log(x)は毎回「2」が表示される動きになっていました。
    なるほど、bindを使わないと同じshow()が参照されるから全部「2」になっちゃうみたいですね
    丁寧な回答、ありがとうございます

    キャンセル

  • 2015/09/09 09:01

    think49さん
    JSFiddleまで用意いただいてありがとうございます。
    ちょっと伝わってなかったかもしれないのですが、
    handleClick.bind(imgs[i], i)

    handleClick.bind(this, i)
    にして、
    console.log(i, i.type);

    console.log(i, this.event.type);
    にすると取れるなと思った次第です。
    これ以上は元の質問の内容と外れるので回答いただかなくても大丈夫です。
    丁寧に対応いただいてありがとうございました。

    rin_kataさん
    同じshow()が参照されるのは問題ないのですが、
    show()が実行されたときのiの値が引数として渡ってくるのが
    問題という意味でした。
    for文が終わるタイミングとクリックされるタイミングでは
    後者のほうが遅いので必ず「2」が出力される状態ということです。

    キャンセル

0

jQueryを使ったサンプルをあげます。

<img class="thumbnail" src="image.jpg" width="20px">
<img class="thumbnail" src="image2.jpg" width="20px">

<script>
$(document).ready(function(){
  // thumbnailクラスが付いているものすべてをjQueryオブジェクで取得
  var $thumbnails = $('.thumbnail');
  
  // クリックイベントを定義
  $thumbnails.on('click', function(e) {
    // 実処理関数にjQueryオブジェクトで引き渡す
    clickEvent($(this));
  });
  
  /**
   * サムネイルクリックイベント
   * param $n サムネイル画像タグのjQueryオブジェクト
   *
   **/
  var clickEvent = function($n) {
    console.log($n.attr('src'));
  };
});
</script>

コメントを充実させたので、少し長く見えますが、実コードはすごくシンプルになります。
jQueryの導入や賛否は置いておいて。。。。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2015/09/08 22:50

    rk7fd3sさん
    すばらしいですね、for文で回す必要もないっていうw
    他の方も指摘されていますが、各ブラウザへの対応等を考えると、jQueryの導入は有効な解決策だと思います
    回答ありがとうございます

    キャンセル

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16971questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。