Panda Noir

JavaScript の限界を究めるブログです。

クラスにProxyを設定したい

ES2015でClassとProxyが追加されました。この二つをうまく組み合わせると強力なツールになりそうですよね。しかし、Proxyを継承するといったことができないので、ややトリッキーなやり方をしなければなりません。

stackoverflow.com

stack overflowにまさにその解答がありました。

class Hoge {
    constructor() {
        const handler = {/* ... */};
        return new Proxy(this, handler);
    }
}

コンストラクタの返り値を使うというエキセントリックテクニックを駆使していますね。

コンストラクタの返り値

コンストラクタに返り値を設定した時の動作について復習してみましょう。動作は以下の4パターンのようです(参考: JavaScript - コンストラクタは返り値は不要?(23939)|teratail)。

// 返り値がObject型であれば、その結果を返す。
class Hoge {
    constructor() {return {hoge: 42};}
}
// クラスが基本型(派生型では無い)であれば、作成したオブジェクトを返す
class Hoge2 {
    constructor() {return 42;}
}
// そうではなく、returnの結果がundefinedでなければ、TypeError例外を発生させる。 
class Hoge3 extends Hoge{
    constructor() {return 42;}
}
// returnがないか、上のどれでもなければ、作成したオブジェクトを返す。 
class Hoge4 {
    constructor() {}
}
console.log(!(new Hoge() instanceof Hoge) && (new Hoge2() instanceof Hoge2) && (new Hoge4() instanceof Hoge4)); // => true
try {
    new Hoge3();
} catch(e) {
    console.log(e.name === "TypeError");
}

Proxyを返す場合は1番目に当てはまります。

Proxyでラップしたインスタンスでも、きちんとinstanceofでtrueを返してくれます。しかもプロトタイプチェーンをなにも汚染していません(stack overflowにプロトタイプチェーンを汚染して実現する解答もありました)。つまり、通常のインスタンスとして扱ってもほぼ問題ありません。すばらしいですね。