Safari で Text 継承すると
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 5
◆ prototype の __proto__ 変更できなかった
◆ Text.prototype やその先のプロトタイプのプロパティが見えてしまう
◆ Text.prototype やその先のプロトタイプのプロパティが見えてしまう
EventTarget の代わり
EventTarget の記事のコメントで Safari は未対応と教えていただきましたEventTarget を直接使えないなら EventTarget 継承してるなにかを使えばいいかと考えて思いついたのが Text です
Node や Element や HTMLElement は new できません
CustomElements に登録すれば HTMLElement を継承して new できますが そのためだけにカスタム要素を登録のも変ですし
Option や Image など new できるものも一部ありますが HTMLElement の中でなぜそれなのか感も残ります
そこでちょうど特別感あって new できる Text が良さそうかなと思いました
イベント関係が使えればいいので DOM の Node 系以外にも XMLHttpRequest とか FileReader とか WebSocket とか色々思いつきますが EventEmitter 代わりにしては気持ち的に重すぎます
__proto__ を変えると
普通にclass EE extends Text {}
とすると余計なプロパティやメソッドがいっぱいついてきます
困ることは特に無いですが 気持ち悪いので EE.prototype の __proto__ を EventTarget.prototype に設定します
こうすれば Text.prototype はプロトタイプチェーンにないので EventTarget のメソッドやプロパティのみになります
しかし これが Safari だとうまく動きませんでした
まず Text 以外の場合の挙動です
class A {
get a() { return 10 }
}
class B extends A {}
Object.setPrototypeOf(B.prototype, { foo: 20 })
const b = new B()
console.log(b.a, b.foo)
// undefined 20
A を継承していますが __proto__ を繋ぎ変えているので B のインスタンスは A ではなく {foo: 20} を見ます
その結果 b.a は undefined で b.foo は 20 になります
これは Chrome も Firefox も Safari も一緒でした
次に A を Text に置き換えます
class C extends Text {}
Object.setPrototypeOf(C.prototype, { foo: 20 })
const c = new C()
console.log(c.foo, c.nodeName)
// undefined #text
Text を継承した C の prototype の __proto__ を B と同様に { foo: 20 } に変更しています
それなのに c.foo は undefined です
さらに c.nodeName が存在して #text が入っています
Chrome や Firefox では c.foo に 20 が入っていて c.nodeName は undefined です
Safari の場合は Text を継承したクラスの prototype の先を変えられないようです
Safari でも直接 EventTarget につなぐ
Text のメソッドやプロパティがあるだけで困るわけでもないですが できないとなるとやりたくなるものなのでやってみましたclass EE {
constructor() {
const elem = document.createElement("_")
Object.setPrototypeOf(elem, this.__proto__)
return elem
}
on(...a) { this.addEventListener(...a) }
off(...a) { this.removeEventListener(...a) }
emit(type, ...args) { this.dispatchEvent(new CustomEvent(type, { detail: args })) }
}
Object.setPrototypeOf(EE.prototype, EventTarget.prototype)
console.log(new EE().nodeName)
// undefined
console.log(new EE().addEventListener)
// function addEventListener() { [native code] }
ここまで来ると class 構文使う必要があるのかってくらいです
constructor でオブジェクトを作ってそれを return します
new できる必要性もなくなったので createElement に _ を指定して HTMLUnknownElement にしました
ただし その __proto__ は EE.prototype にしていてちゃんと EE クラスのメソッドが使えるようになっています
また EE.prototype.__proto__ は EventTarget.prototype なので Element や Node などのメソッドは含まれません
本来の EventTarget の機能も問題なく動いてます
const ee = new EE()
ee.on("aa", eve => {
console.log(eve.type, eve.detail)
})
ee.emit("aa", 12)
// "aa" [12]