Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active April 4, 2021 00:33
Show Gist options
  • Save WebReflection/ee4695c107339e039878b02afb90dc0d to your computer and use it in GitHub Desktop.
Save WebReflection/ee4695c107339e039878b02afb90dc0d to your computer and use it in GitHub Desktop.
Using `this.super()` and `this.super.method()` via any kind of object or prototype (proof of concept)
var withSuperContext = (function (Object) {
//! (c) Andrea Giammarchi
var
defineProperty = Object.defineProperty,
getPrototypeOf = Object.getPrototypeOf,
setPrototypeOf = Object.setPrototypeOf ||
function (o, p) { o.__proto__ = p; },
handler = {
get: function get(target, property, receiver) {
return function () {
var
self = target.self,
proto = getPrototypeOf(self),
method = self[property],
parent = proto,
result
;
do {
parent = getPrototypeOf(parent);
} while (method === parent[property]);
setPrototypeOf(self, parent);
try {
result = parent[property].apply(self, arguments);
setPrototypeOf(self, proto);
} catch(e) {
setPrototypeOf(self, proto);
throw e;
}
return result;
};
}
}
;
return function withSuperContext(object) {
defineProperty(
typeof object === 'function' ?
object.prototype : object,
'super',
{
get: function get() {
var
self = this,
parent = function () {
return proxy.constructor.apply(self, arguments);
},
proxy = new Proxy(parent, handler)
;
parent.self = self;
return proxy;
}
}
);
return object;
};
}(Object));
@WebReflection
Copy link
Author

Usage example

function A() {
  console.log(this.constructor.name);
}
A.prototype.method = function () {
  console.log('method from ' + this.constructor.name);
};

function B() {
  this.super();
  console.log(this.constructor.name);
}
B.prototype = Object.create(A.prototype, {constructor: {value: B}});
withSuperContext(B.prototype).method = function () {
  this.super.method();
  console.log('method from ' + this.constructor.name);
};

function C() {
  this.super();
  console.log(this.constructor.name);
}
C.prototype = withSuperContext(
  Object.create(B.prototype, {constructor: {value: C}})
);
C.prototype.method = function () {
  this.super.method();
  console.log('method from ' + this.constructor.name);
};

var c = new C;
// will log
// A
// B
// C

c.method();
// method from A
// method from B
// method from C

@bergus
Copy link

bergus commented Jul 20, 2016

This does not seem to work if C does not define the method: When B.prototype.method does call super, the self will refer to the C instance, proto to C.prototype, and you'll get an infinite recursion when Bs method calls itself.
Instead of assuming that the super was gotten in a method on the current direct prototype of this/self, you rather should pass the object/object.prototype into the proxy to be used as the proto.

@bergus
Copy link

bergus commented Jul 20, 2016

Also you will totally want to wrap setPrototypeOf(self, proto); in a finally clause, or otherwise the prototype chain will explode in your face when one of the method throws an exception.

@WebReflection
Copy link
Author

which part of quick and dirty wasn't clear? it's a proof of concept ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment