7cc@はてなブログ

JavaScriptとかとか

event handler(2) onClick属性とonclickプロパティ

「イベントをonclickで付ける」という時次の2つがあり、紛らわしい。

1. HTML内に属性として記述するonClick

<button onclick="alert(1)">push<button>

2. jsファイル、script要素で動的に設定する obj.onclick

btn.onclick = function () {
  alert(1)
}

この2つは結構違いがあるので、詳しく説明する。

2つの違いではない、基本的なことについては前のエントリー参照

目次もどき

目次のような箇条書きのようなリスト。2回目以降は、これだけ見れば十分。

  • 呼び方
  • 重要なポイント
  • 属性
    • 大文字と小文字
    • 関数のラップ(wrap=つつむ)
      • イベントタイプの名前を持つ関数にラップされる
      • イベントオブジェクトは「event」という変数名でその関数に渡される
      • クロージャとthisの値
    • 複製(cloneNode) と イベント
  • 属性とプロパティ
    • 設定方法と読み
    • イベントハンドラーを設定できるオブジェクト
      • documentは属性不可
      • window
        • bodyが代わりに <body onload> = body.onload = window.onload
        • イベントとthisの値 <body onload><body onclick>
  • 互換性
    • イベントオブジェクト
    • イベントハンドラーを設定できるオブジェクト window.onclick と document.onclick
    • setAttrubute("onclick", "fn()") はIE8+

呼び方

そもそもこの2つは何と呼び分ければよいのか? HTML5になってできた仕様 によると

HTMLのほうはIDL属性 、 JavaScriptの方はcontent属性 と定義されている。詳しい説明はこの辺参照

しかし、一般的にはイベント属性/HTML属性イベントプロパティと呼ばれているので、この記事でもそう呼ぶことにする

重要なポイント

  • this, Eventを使うなら属性・プロパティはどちらか一つにしたほうが良い
  • 属性は使わないが吉
  • 基本的にaddEventListenerを使おう

属性

大文字と小文字

属性はよく、<button onClick>のようにcだけ大文字で書かれることがあるが、HTMLでは大文字と小文字は問われない

全部大文字でONCLICK、混ぜてoNcLiCk でもOK。
onclickで設定, getAttribute("oNcLicK")で読み、と一致していなくてもよい。
大文字・小文字は完全に無視される。

関数のラップ

同じイベントハンドラ(関数)を次のように属性とプロパティで設定したとき、function.toString()でそれぞれの関数を確認してみると

<button id="btn1" onclick="foo()">#btn1<button>
<button id="btn2">#btn2<button>

<script>
function foo() {}
btn2.onclick = foo
</script>

onClick属性はイベントタイプと同名の関数にラップされる

HTML onClick属性: btn1.onclick.toString()
イベントハンドラfunction onclickという関数にラップされる

function onclick(event) {
  foo()
}

JS onclick: btn2.onclick.toString()
こちらはそのまま

function foo() {
}

このため属性ではonclick="foo()"のように中の関数を実行する形式で書くということですね。

イベントオブジェクト

関数に渡されるイベントオブジェクトは上の通り、「event」という変数名に固定される。JSではよくイベントハンドラの引数にeevを使うが、これだと属性で動かない。

var logValue = function(ev) {
  console.log(ev.target.value)
}
document.body.setAttribute("onclick", "logValue(ev)") // ev is undefined
document.body.setAttribute("onclick", "logValue(event)") // ok

クロージャとthisの値

関数がクロージャになるので、イベント属性ではthisの値が window(=globalのthis) になる

<button onclick="alert(this)">1</button>
<button onclick="foo()">2</button>
<button id="b3">3</button>

<script>
  function alertThis() {
    alert(this)
  }
  b3.onclick = alertThis
</script>
方法 thisの値
属性 + インライン HTMLButtonElement
属性 + 関数 window(Strict modeでundefined)
プロパティ HTMLButtonElement

つまり、thisを使っていると単純な書き換えは出来ない。

  • 属性にthis.value, this.hrefとかがあったり
  • 関数内でevent.currentTargetの代わりにthisを使っている場合

要素の複製とイベント

cloneNode, cloneContentsなどで複製するとイベントハンドラもコピーされる。理由は単純に属性だから。idやtitleなどと同じ。

属性とプロパティ

設定方法と読み

設定法によってプロパティ・属性での読みに違いが出る。

body.getAttribute("onclick") は 属性で設定時だけ、
body.onclick は 属性・プロパティ両方で反映

document.body.setAttribute("onclick", "console.log(1)") // (A)とすると
document.body.getAttribute("onclick") // 反映(A)
document.body.onclick                 // 反映(A)
document.body.onclick = function(){}  // (B)
document.body.getAttribute("onclick") // 反映されない。(A)のまま
document.body.onclick                 // 反映される(B)

また、属性で設定した場合だけHTMLソースに反映される。

基本的にはon[event]プロパティで読むべき。

イベントハンドラを設定できるオブジェクトの違い

属性を使う方法では、windowとdocumentにイベントを設定できない

  • windowのイベントはbodyが代わりになる。詳しくは後述。
  • documentのイベントは設定できない。
    もっとも(on)readystatechangeしかないけれど。
    話が少し脱線するが、DOMContentLoadedなど、イベントハンドラでは設定不可能なイベントもあるので結局イベントハンドラは使わないほうが楽。イベントリスナを使おう。

window

bodyが代わりになる

(a)window.onloadwindow.onbeforeprint などのwindow[on]eventと
(b)body.on[event] が同じになる

window.onload = function(){} // windowだけに設定
document.body.onload === window.onload // true bodyも同じイベントが参照される

これはイベントハンドラに限った話なのでイベントリスナでは関係ない

var fn = function(){  alert(1) }
document.body.onload = fn                            // ok
document.body.setAttribute("onload", "fn()") // ok
document.body.addEventListener("load", fn) // 何も起きない
イベントとthisの値

bodyがwindowの代わりになるときだけ、thisの参照はwindowになる

<body onload="alert(this)" onclick="alert(this)">

<!--
この時の結果
onload - window
onclick - body
-->

互換性

IE8以下の話

イベントオブジェクト

attachEventの時と同じ。
window.event, event.srcElement などを使う。

IE8以下での関数のラップ: eventが渡されていないのが分かる。なのでwindow.eventで捕捉する。

<button onclick="foo()">
// btn.onclickの結果
function onclick() {
  foo()
}

イベントハンドラーを設定できるオブジェクト

windowにDOMイベントが設定不可
例)window.onclick, window.onkeydown

document以下に設定可

setAttributeでの設定

IE7以下で不可

setAttributeで設定できる属性の種類に制限がある。
setAttribute("class", "foo")が出来ないのと同じ。