Skip to content

Instantly share code, notes, and snippets.

@joliss
Last active October 20, 2015 18:21
Show Gist options
  • Select an option

  • Save joliss/8a3968c354acd7e3497d to your computer and use it in GitHub Desktop.

Select an option

Save joliss/8a3968c354acd7e3497d to your computer and use it in GitHub Desktop.
// Sketch for exposing [[Call]] and [[Construct]] machinery to JavaScript
// 1. First, let's add the ability to make any object callable with a __call__
// property (using double underscores for some quick strawman syntax).
// Let __target__ be the object that is being called.
var container = { obj: {} };
container.obj.__call__ = function() {
console.log('this = ' + this + ', __target__ = ' + __target__);
};
// container.obj is now callable:
container.obj();
// => this = container, __target__ = obj
// 2. Second, let's make object construction (`new`) overridable with
// __construct__:
// Default `new` implementation, from http://www.2ality.com/2014/01/new-operator.html
Function.prototype.__construct__ = function(...args) {
// Create `this` and call the function.
var thisValue = Object.create(__target__.prototype);
var result = __target__.apply(thisValue, args);
if (typeof result === 'object' && result !== null) {
return result;
}
return thisValue;
}};
// 3. Now that we have __call__ and __construct__, we can use it to express
// the current `class` semantics. Let `Class` be the metaclass of all classes
// created with the `class` keyword.
// handles `C()`
Class.prototype.__call__ = function() {
throw new TypeError("Class constructors cannot be invoked without 'new'");
};
// handles `new C()`
Class.prototype.__construct__ = function(...args) {
// Unlike Function.prototype.__construct__, we don't try to call the class
// itself - that would throw a TypeError. Rather, we call the constructor
// function, which we've stowed away at, say, class.__constructor__:
var thisValue = Object.create(__target__.prototype);
var result = __target__.__constructor__.apply(thisValue, args);
if (typeof result === 'object' && result !== null) {
return result;
}
return thisValue;
};
// 4. Now `Date` is expressible as a Class like so:
class Date {
static __call__() { // similar to `call constructor`
// Instead of throwing a TypeError, we return a string:
return 'Tue Oct 20 2015 16:37:21 GMT+0000 (UTC)';
}
constructor(year, month, day, hour, minutes, seconds, milliseconds) {
// ...
}
}
// 5. Finally, for the extremely common case where we want calling a class to
// be some form of instantiation: Here's how to enable newless instantiation
// for a class hierarchy deriving from OurBase by implementing __call__:
class OurBase {
static __call__(...args) {
return new __target__(...args)
}
}
class OurSub extends OurBase { }
OurSub() // => OurSub instance
// [Ignore this 6th point, I don't think there's consensus for this!]
// 6. Bonus: If we're feeling ambitious, we can even make newless class
// instantiation allowed-by-default in ES7. This is probably
// backwards-compatible, and I think it's very sensible, but some people might
// find it objectionable:
// ES6 disallows `C()`
Class.prototype.__call__ = function() {
throw new TypeError("Class constructors cannot be invoked without 'new'");
};
// ES7 allows `C()`, unless overridden by subclass
Class.prototype.__call__ = function(...args) {
return new __target__(...args)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment