◆ 「super.foo = value」 で親クラスで定義がないとプロパティが定義される
  ◆ this にすでに setter 定義などがあればなにもされない
  ◆ __proto__ で getter / setter があるクラス定義だと getter / setter が隠される
◆ 例)A←B←C というプロトタイプチェーンで
  ◆ A : 定義なし, B: setter で super.x に代入, C : 定義なし
  ◆ C.x に代入すると B の setter が 1 度実行後に C.x にプロパティが作られる
  ◆ → 以降は C.x に隠されて B.x の setter は使われない
  ◆ B.x に代入すると B には x プロパティが定義済みなので何も起きない

super のプロパティへ代入したときの動き

class A {}
class B extends A {
constructor() { super(); this.foo = 10 }
get foo() { console.log("G"); return super.foo }
set foo(v) { console.log("S"); super.foo = v }
}

const b = new B()
// S

b
// B {foo: 10}

b.foo
// 10

b.foo = 100

b.foo
// 100

A には foo の定義はありません
それを継承した B の foo setter で super.foo に代入します
すると b のインスタンスにプロパティが作られます
super にしても A.prototype 上に作られたりはしません

またこのときに B.prototype にある setter が無視されてプロパティが作られるため B.prototype にある foo getter と foo setter は以降使えなくなります
代入や参照をしても B や S は表示されません
作成時の 1 回だけです


A に getter と setter がある場合は 普通の動きです
A.prototype にある setter が実行されます
自動でプロパティが作られたりはしません

class A {
_foo = null
get foo() { return this._foo }
set foo(v) { this._foo = v }
}
class B extends A {
constructor() { super(); this.foo = 10 }
get foo() { console.log("G"); return super.foo }
set foo(v) { console.log("S"); super.foo = v }
}

const b = new B()
// S

b
// B {_foo: 10}

b.foo
// G
// 10

b.foo = 100
// S

b.foo
// G
// 100

LitElement の場合

lit-element を使った場合は このあたりで 3 段くらいの罠にハマりました

<script type="module">
import { LitElement, html } from "https://unpkg.com/lit-element?module"

customElements.define("a-b", class extends LitElement {
static get properties() {
return {
xx: { type: String }
}
}

constructor() {
super()
this.xx = 1
}

set xx(value) {
super.xx = value
console.log(value)
}

render() {
return html `
<h1>(${this.xx})</h1>
`
}
})
</script>
<a-b></a-b>

lit-element では properties getter でプロパティを定義します
こうすると自動で getter / setter が作られます
作られた setter では 値を保存するとともに requestUpdate メソッドを実行し 更新をスケジュールします

なので上のコードのように 「super.xx = value」 と書いておけば LitElement がやってくれる部分に加えて自分で追加の処理ができると思ってました
しかしこれだと動きません

LitElement のソースを見てみるとプロパティ定義をする前に hasOwnProperty のチェックをしています

this.prototype.hasOwnProperty(name)

getter や setter を定義しているとこれでその定義を取得できてしまうので プロパティを作る対象になりません
その結果 properties から作られる getter や setter がなく 「super.xx = value」 を実行すると getter / setter を隠してしまうプロパティが作られます

あとは CustomElements のクラスだと constructor が引数を取らない上に super() が必要で constructor を書くのが面倒なのでクラスプロパティにしたら defineProperty なので getter / setter が消えるという問題もありました

クラスじゃないとき

クラス構文を使ってインスタンスを作る場合は上で書いた動きですが 自分でオブジェクトを作るとちょっと変わります
クラス構文の getter や setter は prototype に定義されます
普通にオブジェクトを作って オブジェクト自身に getter や setter をつけた場合は少し異なります

const a = { foo: 1 }
const b = Object.create(a, {
foo: { set(value) { super.foo = value } }
})
b.foo = 100
b
// {}
a
// {foo: 1}

b の __proto__ が a になっています
b の foo setter では super.foo に value を代入しています
a に foo setter はないので b.foo に値が設定されそうに見えます

ですが これは何も起きません
b.foo にはすでに setter があるので 代入もされず何も更新されません
a の foo プロパティも同じままです

クラスの場合は b 自身には foo の定義はなく b.__proto__ の B.prototype に foo の定義があるから b.foo にプロパティが代入されて b.__proto__ の getter/setter が隠されるという動きでした
同じように b をプロトタイプオブジェクトにもつオブジェクトを作ってそれに対して foo プロパティに代入すると同じ動きになります

const a = { foo: 1 }
const b = Object.create(a, {
foo: { set(value) { super.foo = value } }
})
const c = Object.create(b)
c.foo = 100
c
// {foo: 100}