初めに
この記事は DMMグループ Advent Calendar 2024 の7日目の記事です。
こんにちは。動画配信開発部でフロントエンドエンジニアをしている今西(@nisshii0313)です。主に動画再生プレイヤーまわりのコンポーネントの開発および保守・運用をしています。
ここ2年ほどは、DMM TVというサービスにおいて、所謂HTML5TVやゲーム機といった特殊なデバイス向けの開発・保守・運用に携わっています。
このDMM TVですが、おかげさまで今年の12月でリリースから2周年を迎えることができました。
現在は保守・運用フェーズに入ってきており、主にデバッグやパフォーマンスの改善に注力しています。
その際に、PCやスマホ以外の特殊なデバイスとなると、内蔵ブラウザの挙動も一筋縄ではいかないこともあります。DOMのイベントが必ずしも発火しなかったり、プロパティ変更の挙動に癖があったり。
そこで今回は、そういった特殊なデバイスのデバッグの際に、Object.defineProperty
によるDOMのプロパティ値の更新を確認することで、イベントの発火の前段での挙動に迫る方法について書いていきます。
Object.definePropertyとは
Object.defineProperty
(MDN)は、MDNで以下のように定義されています。
Object.defineProperty() は静的メソッドで、あるオブジェクトに新しいプロパティを直接定義したり、オブジェクトの既存のプロパティを変更したりして、そのオブジェクトを返します。
記載があるように、オブジェクトへのプロパティ追加や既存プロパティの上書きができます。 ここで言うオブジェクトには、自分で定義したオブジェクトはもちろん、DOMも含まれます。
つまり、これを用いることでDOM要素へのプロパティの追加・更新ができます。
実際のデバッグについて
今回はMedia系のDOM要素が持つcurrentTime
プロパティに適用してみます。
そうすることで、timeupdate
イベントが発火する前段での値の受け渡しをチェックできます。
ここから先は実際に書いたコードを見ていきましょう。
const element = document.getElementsByTagName("video")[0]; Object.defineProperty<HTMLVideoElement | undefined>(element, "currentTime", { get: function() { const getter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "currentTime")?.get?.call(this); console.log("getter", getter ? getter : "undefined"); return getter ? getter : 0; }, set: function(v) { console.log("setter", v); const setter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "currentTime")?.set?.call(this, v); return setter; }, configurable: true, enumerable: true, });
つまずきやすいポイントは2箇所あります。(自分がつまずいたポイントとも言います...)
1つ目はgetterとsetterをthisで縛る必要があると言うことです。
const getter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "currentTime")?.get?.call(this); const setter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "currentTime")?.set?.call(this, v);
これがないとIllegal Invocation
エラーが出てしまいます。
その際にcurrentTime
プロパティがHTMLMediaElement
に帰属していて、HTMLVideoElement
はそれを継承しているということにも注意です。
また、このthisはgetterやsetterが実際に呼び出される際に確定して欲しいので、getやsetの定義に安易にアロー関数を使ってはいけません。
2つ目はObject.defineProperty
によってオブジェクトのプロパティがまとめて定義されるということです。
つまり、自分で定義する際に書いていないプロパティには既定値が入ってしまいます。
特にアクセサー記述子(getやset)はMDNの解説に、既定値は undefined です。
とあるように、書かなければundefined
となってしまいます。
そこで、getterだけチェックする場合にもsetterも定義してあげましょう。
また、データ記述子とアクセサー記述子は共存できないことにも注意が必要です。
ここまでの内容が動いている環境がこちらになります。
こうすることで、currentTime
のgetterやsetterの動作をチェック可能になりました。
今回、直に挙動を探りにくい比較的複雑なDOMの例としてvideo要素を取り上げました。
もちろん他のオブジェクト(DOM含む)や他のプロパティに対しても適用可能であるため、デバッグの幅が広がります。
まとめ & 直近開催イベントの宣伝
Object.defineProperty
によってDOMのプロパティ値の更新を確認する方法について見てきました。
直接活きる機会は少ない飛び道具的な手法ですが、いざという時のために知っておいて損はないと思います。
最後に、運営している社外向け勉強会の宣伝をします!
DMM meetup #40 ~DMM.go × Think! FrontEnd~
これまで社外向けのフロントエンド勉強会として開催していたThink! FrontEndを、社外向けのGoの勉強会であるDMM.goとコラボ開催します。
さらに、今回はオフライン開催です。ぜひ遊びに来てください。
- 開催日時:12月11日(水) 19:30-21:30 (開場 19:00)
- 開催場所:DMM六本木オフィス + アーカイブ配信
- 参加費:無料
【学生向け勉強会】teamLab×DMM tech meetup ~frontend~
学生フロントエンドエンジニア向けの勉強会をteamLabさんとコラボ開催します。
両社の若手フロントエンドエンジニアによるオンラインのLT大会となっていますので、お気軽に覗きに来てください。
- 開催日時:12月 20日(金) 19:00~ (開場18:45~)
- 開催形式:オンライン (zoomでの開催を予定しています)
- 対象:新卒でフロントエンドエンジニアを目指す学生 (学年不問)
- 参加費:無料
それでは!