なぜchecked操作はattr()ではなくprop()でないと壊れるのか

チェックボックスが「戻る」現象の正体

jQueryでチェックボックスを操作していると、こんな経験が起きます。

  • チェックを外したはずなのに送信するとONになっている
  • 画面上はOFFなのにJavaScriptではtrueになる
  • 再描画やバリデーション後に元に戻る

この原因の多くはバグではありません。checkedは属性ではなく状態だからです。そしてこの違いを無視してattr()を使うと壊れます。

結論として、checkedはHTMLの情報ではなく、ブラウザが持つ実行時の状態です。そのため操作にはprop()が必要になります。

checked属性が意味しているもの

次のHTMLを見ます。

<input type="checkbox" id="mail" checked>

このcheckedは「今チェックされている」という意味ではありません。実は「ページ読み込み時の初期状態」を表しています。

ブラウザはページを読み込むと、この情報を元にDOMオブジェクトを生成します。そして別の場所に「現在状態」を持ちます。

つまりブラウザ内部には2つの値があります。

項目 意味
checked属性 初期状態(defaultChecked)
checkedプロパティ 現在状態

ユーザーがクリックすると変わるのはプロパティです。HTMLは書き換わりません。

attr()で壊れる理由

次のコードを書きます。

$('#mail').attr('checked', false);

これがやっているのは「初期状態の定義を外す」ことです。しかし現在のチェック状態は変わりません。すでにDOMが生成されているためです。

つまり

  • 画面:変わらないことがある
  • JavaScriptの判定:変わらない
  • 送信値:変わらない

という不整合が起きます。

特に分かりやすいのが次です。

$('#mail').attr('checked', false);
console.log($('#mail').is(':checked'));

trueになる場合があります。なぜならis(':checked')はDOMプロパティを参照しているからです。

prop()が正しい理由

prop()はDOMプロパティを変更します。

$('#mail').prop('checked', false);

これはブラウザが管理している「現在状態」を変更します。

  • 表示が変わる
  • JavaScript判定が変わる
  • 送信値が変わる

すべて一致します。ユーザーがクリックしたときと同じレイヤーを操作しているためです。

defaultCheckedというもう一つの存在

さらにややこしいのがdefaultCheckedです。HTML属性はここに対応します。

console.log(document.getElementById('mail').defaultChecked);

これは初期値です。つまり

  • defaultChecked:ページロード時
  • checked:現在状態

attr()はdefaultChecked側を触っています。

なぜフォームで問題が顕在化するのか

フォーム送信は現在状態を送信します。つまりcheckedプロパティです。そのためattr()で操作したコードは、画面とサーバの値がズレます。

特に次のケースで顕在化します。

  • Ajax送信
  • バリデーション後の再描画
  • SPA風の画面更新

ユーザーから見ると「勝手に戻る」バグに見えます。

ありがちな失敗コード

if(condition){
  $('#mail').attr('checked', true);
}

このコードは初期値を変更しているだけです。現在状態を変えたいならpropです。

$('#mail').prop('checked', true);

なぜjQueryがpropを追加したのか

jQuery1.5まではattrが両方を扱っていました。しかしブラウザ差異で不具合が頻発しました。

  • IEでは動く
  • Firefoxでは動かない
  • 逆もある

そのため1.6で分離されました。つまりpropは新機能ではなく、正しいDOMの扱いを明示するAPIです。

checked以外でも同じ問題は起きる

同様の現象は他でも発生します。

  • optionのselected
  • inputのdisabled
  • radioの選択状態

すべて「現在状態」はプロパティです。

覚え方

迷ったら次の基準で判断できます。

  • HTMLの意味を変える → attr
  • ユーザーの操作状態を変える → prop

チェックボックスは明らかに後者です。

最後に

checkedは見た目の問題ではありません。DOMの状態管理の問題です。jQueryのattrとpropの違いは、ブラウザがHTML文書でありながらアプリケーションでもあることを示しています。

「チェックをつける」という操作は、HTMLを書き換えているのではなく、ブラウザの内部状態を変更しています。propを使うのはjQueryの流儀ではなく、ブラウザの仕様に従っているだけです。