メモリにやさしくインスタンスの動的プロパティ名にgetter/setterを

インスタンスのプロパティに対してgetter/setterを使いたい時は以下のコードで事足ります。

```javascript
class SomeClass {
    constructor(arg){
        this.someProp = arg;
    }
    get someProp(){
        console.log("getter");
        return "value of someProp";
    }
    set someProp(val){
        console.log("setter", val);
    }
}
```

しかし、そうそうあることではありませんがプロパティ名が動的な時はどうすればいいのでしょうか。
答えは以下の通りです。

```javascript
const propName = "hogefuga";
class SomeClass {
    constructor(arg){
        this[propName] = arg;
    }
    get [propName](){
        console.log("getter");
        return `value of ${propName}`;
    }
    set [propName](val){
        console.log("setter", val);
    }
}
```

クラス構文にもcomputed propertyは使えるようなのでこれでよいでしょう。

インスタンスごとに関数を生成せず、どのインスタンスも同じ関数を参照するのでインスタンスを大量に生成してもメモリを圧迫しにくくなっています。getter/settterの中でのthisがインスタンスを指します。

また、クラスのprototypeにgetter/setterを定義するという方法もあります。(後述)

「変更があるとこの関数を呼ぶ」というプロパティを作りたいとき、ひとつなら以下のスクリプトで実現できます。

```javascript
const propName = "hogefuga";

const SomeClass = (() => {
    const map = new WeakMap();
    class SomeClass {
        constructor(){
            this.listeners = {
                change: []
            };
        }
        addEventListener(type, fn){
            this.listeners[type].push(fn);
        }
        removeEventListener(type, fn){
            const arr = this.listeners[type];
            arr.splice(arr.indexOf(fn), 1);
        }
        get [propName](){
            return map.get(this);
        }
        set [propName](val){
            map.set(this, val);
            this.listeners.change.forEach(fn => fn());
        }
    }
    return SomeClass;
})();
```
```javascript
const foobar = new SomeClass();

foobar.hogefuga = 10;
console.log(foobar.hogefuga); // 10

const listener1 = () => console.log("listener1");
foobar.addEventListener("change", listener1);
foobar.hogefuga = 20;
// listener1

foobar.addEventListener("change", () => console.log("listener2"));
foobar.hogefuga = 30;
// listener1
// listener2

foobar.removeEventListener("change", listener1);
foobar.hogefuga = 40;
// listener2
```

しかし、これが複数になるとどうでしょう。

```javascript
const propNames = ["hoge", "fuga"];

const SomeClass = (() => {
    const maps = {};
    propNames.forEach(n => maps[n] = new WeakMap());

    class SomeClass {
        constructor(){
            this.listeners = {
                change: []
            };
        }
        addEventListener(type, fn){
            this.listeners[type].push(fn);
        }
        removeEventListener(type, fn){
            const arr = this.listeners[type];
            arr.splice(arr.indexOf(fn), 1);
        }
        get [propNames[0]](){
            return maps[propNames[0]].get(this);
        }
        set [propNames[0]](val){
            maps[propNames[0]].set(this, val);
            this.listeners.change.forEach(fn => fn());
        }
        get [propNames[1]](){
            return maps[propNames[1]].get(this);
        }
        set [propNames[1]](val){
            maps[propNames[1]].set(this, val);
            this.listeners.change.forEach(fn => fn());
        }
    }
    return SomeClass;
})();

//略
```

なんとも冗長ですしこれではpropNamesの長さが変わるときお手上げです。そこでObject.definePropertiesを使い、prototypeにgetter/setterを定義します。

```javascript
const propNames = ["hoge", "fuga"];

const SomeClass = (() => {
    class SomeClass {
        constructor(){
            this.listeners = {
                change: []
            };
        }
        addEventListener(type, fn){
            this.listeners[type].push(fn);
        }
        removeEventListener(type, fn){
            const arr = this.listeners[type];
            arr.splice(arr.indexOf(fn), 1);
        }
    }

    // ↓ ここからがポイント ↓
    const maps = {};
    propNames.forEach(n => maps[n] = new WeakMap());

    const o = {};
    propNames.forEach(n => o[n] = {
        get(){
            return maps[n].get(this);
        },
        set(val){
            maps[n].set(this, val);
            this.listeners.change.forEach(fn => fn());
        }
    })
    Object.defineProperties(SomeClass.prototype, o);
    // ↑ ここまでがポイント ↑

    return SomeClass;
})();
```
```javascript
const foobar = new SomeClass();

foobar.hoge = 10;
foobar.fuga = "abc";

console.log(foobar.hoge, foobar.fuga); // 10 "abc"

const listener1 = () => console.log("listener1");
foobar.addEventListener("change", listener1);

foobar.hoge = 20;
// listener1

foobar.addEventListener("change", () => console.log("listener2"));

foobar.fuga = "def";
// listener1
// listener2

foobar.hoge = 30;
// listener1
// listener2

foobar.removeEventListener("change", listener1);

foobar.fuga = 40;
// listener2
```