web-dev-qa-db-ja.com

ES2015クラスでプロキシを拡張できますか?

次のようにプロキシを拡張しようとしました:

class ObservableObject extends Proxy {}

Babelを使用してES5にトランスパイルすると、ブラウザーで次のエラーが表示されました。

app.js:15 Uncaught TypeError: Object prototype may only be an Object or null: undefined

私はそれが指すコード行を見ました。コードの問題のある行を指す矢印付きのコードの部分を次に示します。

var ObservableObject = exports.ObservableObject = function (_Proxy) {
    _inherits(ObservableObject, _Proxy); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    function ObservableObject() {
        _classCallCheck(this, ObservableObject);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(ObservableObject).apply(this, arguments));
    }

    return ObservableObject;
}(Proxy);

このエラーが発生する理由を誰か知っていますか?これはバベルのバグですか?プロキシを拡張しようとするとどうなりますか?

29
John L.

いいえ、ES2015クラスはProxyを拡張できません1

プロキシオブジェクトは非常に非定型のセマンティクスを持ち、ES2015で「エキゾチックオブジェクト」と見なされます。つまり、「すべてのオブジェクトでサポートする必要のある1つ以上の重要な内部メソッドのデフォルト動作はありません」。プロトタイプはありません。これは、通常、拡張する型のほとんどの動作を取得する場所です。 仕様のセクション26.2.2: "プロキシコンストラクターのプロパティ"

プロキシエキゾチックオブジェクトには初期化が必要な[[Prototype]]内部スロットがないため、Proxyコンストラクターにはprototypeプロパティがありません。

これはバベルの制限ではありません。 ChromeでProxyを拡張しようとすると、それとクラス構文の両方がネイティブにサポートされますが、同様のエラーが発生します:

Uncaught TypeError:クラス拡張値に未定義の有効なプロトタイププロパティがありません

1 「いいえ」が実用的な答えです。ただし、 Alexander O'Mara は、Proxy.prototype(グロス!)に値を割り当てると、少なくとも一部のブラウザーで拡張できるようになることを指摘しました。 これを少し試してみました 。エキゾチックなプロキシインスタンスの動作により、コンストラクターをラップする関数で実行できる以上のことを実行することはできず、一部の動作はブラウザー間で一貫していないようです(仕様が何を期待しているのかわかりませんこれを行う場合)。深刻なコードではこのようなことを試みないでください。

27
Jeremy Banks

さて、私はこの質問を忘れていましたが、最近誰かがそれを支持しました。技術的にプロキシを拡張することはできませんが、クラスをプロキシとしてインスタンス化し、すべてのサブクラスを同じプロパティ記述子関数でプロキシとしてインスタンス化するように強制する方法があります(これはChromeでのみテストしました)。

class ExtendableProxy {
    constructor() {
        return new Proxy(this, {
            set: (object, key, value, proxy) => {
                object[key] = value;
                console.log('PROXY SET');
                return true;
            }
        });
    }
}

class ChildProxyClass extends ExtendableProxy {}

let myProxy = new ChildProxyClass();

// Should set myProxy.a to 3 and print 'PROXY SET' to the console:
myProxy.a = 3;
44
John L.

@John L.の自己応答から:
コンストラクター内でProxyを使用して、新しく作成されたインスタンスをラップできます。プロキシを拡張する必要はありません。

たとえば、既存のPointクラスから観測ポイントを提供します。

class Point {

    constructor(x, y) {
        this.x = x
        this.y = y
    }

    get length() {
        let { x, y } = this
        return Math.sqrt(x * x + y * y)
    }

}

class ObservedPoint extends Point {

    constructor(x, y) {

        super(x, y)

        return new Proxy(this, {
            set(object, key, value, proxy) {
                if (object[key] === value)
                    return
                console.log('Point is modified')
                object[key] = value
            }
        })
    }
}

テスト:

p = new ObservedPoint(3, 4)

console.log(p instanceof Point) // true
console.log(p instanceof ObservedPoint) // true

console.log(p.length) // 5

p.x = 10 // "Point is modified"

console.log(p.length) // 5

p.x = 10 // nothing (skip)
8
class c1 {
    constructor(){
        this.__proto__  = new Proxy({}, {
            set: function (t, k, v) {
                t[k] = v;
                console.log(t, k, v);
            }
        });
    }
}

d = new c1(); d.a = 123;

1
user3027221
class A { }

class MyProxy {
  constructor(value, handler){
    this.__proto__.__proto__  = new Proxy(value, handler);
  }
}


let p = new MyProxy(new A(), {
  set: (target, prop, value) => {
    target[prop] = value;
    return true;
  },
  get: (target, prop) => {
    return target[prop];
  }
});

console.log("p instanceof MyProxy", p instanceof MyProxy); // true
console.log("p instanceof A", p instanceof A); // true

pはMyProxyの一種であり、クラスのAによって同時に拡張されました。 Aは元のプロトタイプではなく、プロキシされたものです。

0
Weizhe Ding

Babelは、サポートできないという理由だけでプロキシをサポートしていません。そのため、ブラウザがサポートを追加するまで存在しません。

Babel docsから:「サポートされていない機能ES5の制限のため、プロキシをトランスパイルまたはポリフィルできません」

0
omerts