読者です 読者をやめる 読者になる 読者になる

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回目以降は、これだけ見れば十分。

  • 呼び方
  • 重要なポイント
  • 属性
    • 大文字と小文字
    • ラップ(包む)
      • イベントタイプの名前を持つ関数にラップされる
      • イベントオブジェクトは「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になってできた仕様 によると

  • 属性: event handler content attribute
    (コンテンツ属性, コンテント属性)
  • JS: event handler IDL attribute
    (IDL属性)

と定義されている。IDL属性 と content属性 の説明はこの辺参照

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

重要なポイント

  • 属性でもプロパティでも、互換性がある[*1]のはIE9以上
    しかし、IE9からはaddEventListenerというメソッドが使えるので互換性目的でonイベントを使用する意味は無い
  • this, Eventを使うなら属性・プロパティはどちらか一つにしたほうが良い
  • 属性は使わないが吉

[*1]互換性がある = すべてのブラウザで有効

属性

大文字と小文字

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

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

ラップ

同じイベントハンドラ(関数)を次のように属性とプロパティで設定したとき、

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

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

function.toString()でそれぞれの関数を確認してみると

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

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

function onclick(event) {
  foo()
}

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

function foo() {
}

ラップされるので、属性ではonclick="foo()"のように中の関数を実行する形式で書くということですね。

イベントオブジェクト

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

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

補足

関数に他の引数を与えずに、arguments[0]でeventを受け取る方法もあるが、出来るというだけでする利点は皆無。他の引数を渡せなくなる上に、分かりにくいだけ。

var logValue = function() {
  console.log(arguments[0].value) // object MouseEvent
}

クロージャとthisの値

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

<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.onload や window.onbeforeprint などのwindow[on]eventと
(b)body.on[event] が同じになる

window.onload = function(){} // windowだけに設定
document.body.onload === window.onload // true bodyにも反映された

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

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")が出来ないのと同じ。