◆ getter/setter のオーバーライド
◆ getOwnPropertyDescriptor で取り出して call する
◆ クラス構文で定義できるようなとこだと super 使って楽できる

すでにあるその辺の web ページの構造やどうやって動いてるのかとか調べるときの役立つ方法に DOM メソッドのオーバーライドがあります
remove メソッドをオーバーライドして console.log したあとに本来の動きをするようにしておくと どのタイミングでどの要素の remove がされてるかわかる という感じです

メソッドならやり方には困らないと思いますが innerHTML みたいなプロパティではどうすればいいのか と思うかもしれません

そんなわけで 今回は innerHTML を使ってやってみます

単純に setter の設定ではだめ

プロパティが上書きされた時に console.log をしたいならまず思いつくのは setter でしょう
でも単純に setter だけつけるのはだめです

こういうやつ
Object.defineProperty(elem, "innerHTML", {
get: function(){
return this.__innerHTML
},
set: function(x){
console.log(this, x)
this.__innerHTML = x
}
})

ちゃんと 上書きした時に ログがでますし get で取り出せています
ですが innerHTML 本来の HTML の書き換えが行われません

ある意味当然ですね

HTML の書き換えは Element.prototype で setter を使って行われています
そこの setter が実行されていないと HTML は更新されません

正しいやり方

↑の方法では this に実際の値を保存していましたが 独自に保存するのじゃなくて 本来の setter に値を渡します
function monitorOverwriteInnerHTML(elem){
var desc = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML")
Object.defineProperty(elem, "innerHTML", {
get: function(){
return desc.get.call(this)
},
set: function(x){
console.log(this, x)
desc.set.call(this, x)
}
})
}

getOwnPropertyDescripter で get set の関数を取得して call してるだけです


上のコードは簡単に使える関数にしていますので 引数に要素を入れると その要素が監視されます

DIV 全部を監視したいというなら HTMLDivElement.prototype を引数に渡せばいいです
とりあえず全部の要素だったら HTMLElement.prototype です

monitorOverwriteInnerHTML(document.body)
document.body.innerHTML = "test"
// <body>​test​</body>​ "test"

ここでは innerHTML でしたが textContent なども同じように作れます


ところで本来の setter に渡すならわざわざ getter は作らなくても勝手に本来の getter から取得するからいらなくない?と思うかもしれないですけど ないと getter なしということになって 常に undefined が返って来ます
親の getter を見てくれないようです

クラスだと

ところで継承先の getter/setter を使いたい時って PropertyDescriptor を取得して call ってちょっとめんどうなのはどうにかならないのかな

もしかして ES6 のクラスの super って getter/setter いけたりして
var v = new class B extends class A {
get test(){
console.log("parent getter")
return this.xxx
}
set test(x){
console.log("parent setter")
this.xxx = x
}
} {
get test(){
console.log("child getter")
return super.test
}
set test(x){
console.log("child setter")
console.log(x)
super.test = x
}
}

console.log(v)
// Object { }

v.test = "tetete"
// child setter
// tetete
// parent setter

console.log(v)
// Object { xxx: "tetete" }

console.log(v.test)
// child getter
// parent getter
// tetete

おー できてる

クラスキライなので避けてましたが 別に中身はプロトタイプベース変わらないですし 楽に書ける点ではいいかもですね


ですが! 今回のようなビルトインなところをいじるときには使えない方法です