Frozen class prototypes and replacing strong mode with proxies

75 views
Skip to first unread message

Ashley Gullen

unread,
Mar 11, 2015, 9:47:42 AM3/11/15
to streng...@googlegroups.com
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

Andreas Rossberg

unread,
Mar 11, 2015, 12:23:07 PM3/11/15
to Ashley Gullen, streng...@googlegroups.com
On 11 March 2015 at 14:47, Ashley Gullen <ashl...@gmail.com> wrote:
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.

That is true. But to be honest, I wouldn't recommend this as a general pattern, regardless of strong mode. The primary purpose of classes is having a declarative syntax that defines the interface of a constructor in one coherent place. Patching classes after the fact is best left to cases where you can't avoid it.

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.

Locking down classes actually has significant potential for optimisations. The motivation bit in the strawman doc mentions it. In brief, you can flatten immutable prototype chains to do much more efficient method dispatch.

Having all methods defined in place in the class also is pretty much essential if you want to do any reliable type checking.

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.

Yes, but proxies would require a separate "opt-in" for every individual object, wouldn't allow propagating strongness through library functions, and they'd cost you 100x performance. ;)

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.

FWIW, proxies are implemented, though following an outdated spec and being behind a flag.
 
/Andreas

Youness Belfkih

unread,
Mar 26, 2016, 4:11:23 AM3/26/16
to Strengthen JS
One solution is would be an altjs language allowing you to declare multi part classes much like C# patial classes

Youness Belfkih

unread,
Mar 26, 2016, 4:13:09 AM3/26/16
to Strengthen JS
Then the output would be one long class combining both of them
Reply all
Reply to author
Forward
0 new messages