2016-06-08 6 views
14

Ich versuchte Proxy zu erweitern, etwa so:Kann ich Proxy mit einer ES2015-Klasse erweitern?

class ObservableObject extends Proxy {} 

ich Babel verwendet es ES5 transpile, und ich habe diesen Fehler im Browser:

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

ich auf der Codezeile sah es zeigte auf. Hier ist der Teil des Codes mit Pfeilen auf die beanstandeten Codezeile zeigen:

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

Weiß jemand, warum ich diesen Fehler könnte zu bekommen? Ist das ein Fehler in Babel? Was soll passieren, wenn Sie versuchen, den Proxy zu erweitern?

Antwort

18

Nein, eine ES2015-Klasse erweitern kann nicht Proxy .

Proxy-Objekte haben eine sehr untypische Semantik und werden in ES2015 als "exotische Objekte" betrachtet, was bedeutet, dass sie "nicht das Standardverhalten für eine oder mehrere der wesentlichen internen Methoden haben, die von allen Objekten unterstützt werden müssen". Sie haben keinen Prototyp, wo Sie normalerweise das meiste Verhalten für einen Typ erhalten würden, den Sie erweitern. Von 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.

Dies ist keine Einschränkung von Babel. Wenn Sie versuchen, Proxy in Chrome zu erweitern, wo sie und die Klasse Syntax sowohl nativ unterstützt, werden Sie noch eine ähnliche Fehlermeldung erhalten:

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

„Nein“ ist die praktische Antwort. Allerdings hat Alexander O'Mara darauf hingewiesen, dass, wenn Sie Proxy.prototype (brutto!) Einen Wert zuweisen, es möglich ist, zumindest in einigen Browsern zu erweitern. We experimented with this a little. Aufgrund des Verhaltens exotischer Proxy-Instanzen kann dies nicht dazu verwendet werden, viel mehr zu erreichen als mit einer Funktion, die den Konstruktor umschließt, und einige Verhaltensweisen scheinen zwischen Browsern nicht konsistent zu sein (ich bin mir nicht sicher, was die Spezifikation erwartet) wenn du das tust). Bitte versuchen Sie es nicht in seriösem Code.

+0

Auf einer Seite zur Kenntnis, umgehen in einer 'prototype' Eigenschaft Patchen diesen' TypeError' in native, non-babel-Implementierungen, aber die Art und Weise, wie "Proxy" scheinbar implementiert wird, überschreibt alle untergeordneten Klassen und lässt nur einen gewundenen Alias ​​für "Proxy" übrig. –

+0

@ AlexanderO'Mara Das ist sehr interessant. Ich bin nicht vertraut genug mit der internen Semantik, um zu verstehen, ob dies eindeutig spezifiziert ist oder ein Implementierungsdetail. Ich setzte '.prototype = null' und testete dies ein wenig. Ihr Unterklassenkonstruktor kann verschiedene Argumente annehmen, und wenn er ein Object zurückgibt, wird das Objekt korrekt anstelle einer Proxy-Instanz erzeugt, so dass der Unterklassenkonstruktor korrekt verwendet wird. Ich denke, wir können erfolgreich eine Unterklasse von Proxy erstellen, aber es gibt keine Möglichkeit für die Unterklasse, das Verhalten ihrer Instanzen aufgrund der exotischen Behaviours tatsächlich zu modifizieren. –

+0

I * think * Dies hängt damit zusammen, wie ein JavaScript-Konstruktor ein Objekt zurückgeben kann, das sich von dem impliziten 'this 'unterscheidet. Wenn ein Elternkonstruktor dies tut, ersetzt dieses Objekt 'this ', wenn die Unterklasse super aufruft und die Vererbung im Grunde genommen verworfen wird. Ich vermute also, dass Proxy das im Grunde unter der Haube macht. –

0

Babel unterstützt Proxy nicht, einfach weil es nicht möglich ist. Bis der Browser Support hinzufügt, existiert er nicht mehr.

Aus der Dokumentation Babel: "Nicht unterstützte Funktion Aufgrund der Einschränkungen von ES5, Proxies nicht transpiled oder polyfilled werden"

0
class c1 { 
    constructor(){ 
     this.__proto__ = new Proxy({}, { 
      set: function (t, k, v) { 
       t[k] = v; 
       console.log(t, k, v); 
      } 
     }); 
    } 
} 

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

+0

es kann funktionieren, aber ein bisschen Erklärung bitte – cske

+0

Sollte es Object.setPrototypeOf statt dieser sein .__ proto__? –

17

Nun, ich hatte diese Frage vergessen, aber vor kurzem wurde sie von jemandem neu bewertet. Obwohl Sie einen Proxy technisch nicht erweitern können, gibt es eine Möglichkeit, eine Instanz als Proxy zu erzwingen und alle Unterklassen dazu zu zwingen, als Proxies mit den gleichen Eigenschaften des Deskriptors zu arbeiten (ich habe das nur in Chrome getestet):

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; 
+0

Ich habe das versucht und es funktioniert (derzeit). Allerdings verstehe ich nicht warum, kannst du erklären wie/warum das funktioniert?Es scheint, als würde man einen Fehler clever ausnutzen, aber wenn das der Fall ist, vermute ich, dass das nicht auf unbestimmte Zeit weitergeht ... Bitte erweitern Sie dies! –

+0

@Wim Barelds Ich bin kein Experte für ECMA-Standards, aber hier ist eine Vermutung: Wenn Sie keinen Konstruktor für die Kindklasse definieren, wird der Konstruktor 'constructor() {super(); } '. 'super()' ist ein Aufruf des Konstruktors der Elternklasse. Wenn ein Konstruktor etwas zurückgibt, gibt 'new ()' das zurück, was '()' zurückgibt. Dieses Verhalten ist seit einiger Zeit in den altmodischen JavaScript-Konstruktoren vorhanden. Wenn also "super" aufgerufen wird, wird das konstruierte Objekt zu einem Proxy. –

+1

@WimBarelds Die ECMAScript 6/7-Spezifikation (I forget) besagt, dass die Basisklasse in einer Klassenhierarchie für die Zuordnung des Objekts zuständig ist. In einer Hierarchie ist die Klasse, die nicht erbt (außer implizit von Object), für die Zuweisung verantwortlich. In diesem Fall weist ExtendableProxy neue Instanzen als Instanzen von Proxy zu. Das funktioniert also. Und ich glaube nicht, dass es ein Fehler ist. –

0
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 ist eine Art MyProxy und es wurde gleichzeitig von A der Klasse erweitert. A ist kein originaler Prototyp, es war eine Art Proxy.

0

Von @ John L. Antwort:
Innerhalb Konstruktor können wir Proxy verwenden, um die neu erstellte Instanz zu umbrechen. Keine Notwendigkeit, Proxy zu erweitern.

Beispiel, einen beobachtete Punkt aus einer bestehenden Point-Klasse bieten:

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) 
Verwandte Themen