StrongJS proposes to freeze classes. If you are writing a very long class, it is useful to break it in to multiple files. Currently I don't think the ES6 syntax allows for this: you simply must have the entire class definition in one file. One workaround is to have a short class declaration in one file, then revert back to the old fashioned "add functions to the prototype" pattern in other files, e.g.:
part1.js -----------
class Foo {
constructor() { this.a = 0; this.b = 0; }
method() { ... }
};
part2.js -----------
Foo.prototype.anotherMethod = function () { ... }
and so on. However it appears strong mode appears to prevent this working since the class prototype is strong and frozen so part2.js will throw when trying to add anotherMethod to Foo.prototype. I don't believe this pattern has any negative performance impact either, since it is just another way to add a predictable and (ultimately) static set of methods on the object prototype, and will still produce identical instances with the same shape and the functions using them will remain monomorphic.
Further and more generally, the general restriction of "objects are strong after creation" (so that setting or getting missing properties after the constructor throws) can actually be implemented in JS with Proxies. This provides an alternative solution to building this restriction in to the language itself. For example a useful similar pattern is to define a Proxy that throws on setting or getting non-existent properties, and wrap any objects returned by new in that proxy, e.g.:
let foo = defend(new Foo());
where defend() is a function that wraps the result of new Foo() in a proxy that implements those restrictions. This would probably only be done during development in a "debug" mode, and then for production release "defend" would be replaced with an implementation that just passes its object through without a Proxy. This solution is interesting compared to strong objects since:
- it still guarantees the objects have a fixed property set for maximum performance
- it can be implemented entirely in JS without needing any new language mode
- it makes objects strong at the point of the "new" call, instead of after the constructor, which allows patterns where object prototypes are modified after their definition (like the above class-methods-in-different-files pattern, and probably others) to continue working unmodified
I'd point out Proxies don't yet appear to be supported in V8, which is a shame and has probably encouraged the extend-the-language approach before the JS approach, which definitely deserves more research IMO.
The shortcoming of the defend() function would be that it is difficult to make sure it is used everywhere correctly. It must wrap every single use of "new", as well as definitions of literals. Therefore I think it may actually be more useful to extend the language in a different way: add some kind of global scope callback that is invoked whenever objects are allocated. For example, something like this:
window[Symbol.onCreate] = function (o)
{
// called whenever an object is created, either via "new" or a literal
};
Therefore in development an onCreate handler could be used to wrap everything allocated in a proxy implementing strong mode:
window[Symbol.onCreate] = function (o)
{
return defend(o);
};
In production this method would simply not be overridden, restoring the default behavior.
This would allow implementing much of strong mode in JS with this single addition, which is an interesting alternative direction to making extensive changes to the language definition. I guess using proxies would reduce performance during development, but this is comparable to the debug vs. release builds that native developers work with.
Ashley Gullen
Scirra Ltd