Can I extend Proxy with an ES2015 class?

2019-02-04 07:45发布

问题:

I tried to extend Proxy, like so:

class ObservableObject extends Proxy {}

I used Babel to transpile it to ES5, and I got this error in the browser:

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

I looked at the line of code it pointed to. Here's that portion of the code with arrows pointing to the offending line of code:

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);

Does anyone know why I might be getting this error? Is this a bug in Babel? What is supposed to happen when you try to extend Proxy?

回答1:

No, an ES2015 class cannot extend Proxy1.

Proxy objects have very atypical semantics and are considered "exotic objects" in ES2015, meaning that they do "not have the default behaviour for one or more of the essential internal methods that must be supported by all objects". They do not have any prototype, which is where you'd normally get most of the behaviour for a type you're extending. From section 26.2.2: "Properties of the Proxy Constructor" in the specification:

The Proxy constructor does not have a prototype property because proxy exotic objects do not have a [[Prototype]] internal slot that requires initialization.

This is not a limitation of Babel. If you attempt to extend Proxy in Chrome, where it and the class syntax are both natively supported, you'll still get a similar error:

Uncaught TypeError: Class extends value does not have valid prototype property undefined

1 "No" is the practical answer. However, Alexander O'Mara pointed out that if you assign a value to Proxy.prototype (gross!), it does become possible to extend, at least in some browsers. We experimented with this a little. Due to the behaviour of exotic Proxy instances this can't be used to accomplish much more than you could do with a function wrapping the constructor, and some behaviour does not appear to be consistent between browsers (I'm not sure what the specification expects if you do this). Please don't attempt anything like this in serious code.



回答2:

Well, I had forgotten about this question, but someone recently upvoted it. Even though you technically can't extend a proxy, there is a way to force a class to instantiate as a proxy and force all its subclasses to instantiate as proxies with the same property descriptor functions (I've only tested this in 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;


回答3:

From @John L. self response:
Inside constructor we can use Proxy to wrap the newly created instance. No need to extend Proxy.

Example, provide an observed point from an existing Point class:

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
            }
        })
    }
}

test:

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)


回答4:

Babel doesn't support Proxy, simply because it can't. So until browsers add support, it doesn't exist.

From the Babel docs: "Unsupported feature Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled"



回答5:

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;



回答6:

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 is kind of MyProxy and it was extended by A of class simultaneously. A isn't original prototype , it was proxied, sort of.