Subclassing built-in constructors

# David Bruant (6 years ago)

Le 26/05/2012 17:55, Allen Wirfs-Brock a écrit :

On May 26, 2012, at 2:37 AM, David Bruant wrote:

The use case of subclassing native constructors (just to clarify I'm only talking about Array, Date, WeakMap, etc. Not DOM constructors) is legitimate and has been raised and detailed in length [1] (the initial message does not point that out, but the rest of the thread does). Being able to securely do that is still a use case. The proto operator was a solution to it but it's out. proto is out because secure JS code first instruction will be to delete it on go on as if it never existed. Extracting just a proto setter is not an option according to what you're saying below. What is there left for the subclassing use case? So far, I don't see anything. The aforementioned thread raised 2 interesting alternatives to proto [2] [3] for the subclassing use case. Could it be considered to standardize any of these functions (or something else as long as it solves the use case)? Unlike grawlix, this function will be polyfillable with proto where it's available. In platform with a native support for this function, it will be possible to both use this function (solving the use case) and delete proto (doing it safely).

Does it sounds like a good alternative?

Because max-min classes are inching towards consensus, I suggest the that appropriate way to subclass the built-in constructors will be via |class extends|. eg

class MyArray extends Array { someMyArrayProtoMethod () {...} constructor (tag) { super(); this.tag=tag } }

If that's the case, then that's perfect! It suffers from the same issue than <| which is that it's syntax-based, so minimum cost is an ES6->ES5 compiler

# Brendan Eich (6 years ago)

David Bruant wrote:

Because max-min classes are inching towards consensus, I suggest the that appropriate way to subclass the built-in constructors will be via |class extends|. eg

class MyArray extends Array { someMyArrayProtoMethod () {...} constructor (tag) { super(); this.tag=tag } } If that's the case, then that's perfect! It suffers from the same issue than <| which is that it's syntax-based, so minimum cost is an ES6->ES5 compiler

But that is why subclassing the DOM via proto has already dug in as a de-facto standard:

zepto.js: return isObject(value) && value.proto == Object.prototype zepto.js: // to the array. Note that __proto__ is not supported on Internet zepto.js: dom.proto = arguments.callee.prototype

and why IE faces pressure to support proto, including setting for DOM objects.

New syntax may some day clean all this up but the short path wins and it'll be hard to displace. Let's be realistic. I agree we should, assuming classes make it, support DOM subclassing. That is good but it won't relieve DOM implementors from supporting proto.

# Domenic Denicola (6 years ago)

-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Brendan Eich Sent: Saturday, May 26, 2012 15:08

New syntax may some day clean all this up but the short path wins and it'll be hard to displace. Let's be realistic. I agree we should, assuming classes make it, support DOM subclassing. That is good but it won't relieve DOM implementors from supporting proto.

Is there a parallel to be drawn with (define|lookup)(Getter|Setter), or is proto different? I quite liked Allen's blog post about why IE decided to never support them [1]. Following that reasoning seems to lead to specifying Object.setPrototypeOf as a proto replacement, and hoping library authors follow suit in switching to that from proto, just like they did for the property API.

blogs.msdn.com/b/ie/archive/2010/08/25/chakra-interoperability-means-more-than-just-standards.aspx

# Brendan Eich (6 years ago)

Domenic Denicola wrote:

-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Brendan Eich Sent: Saturday, May 26, 2012 15:08

New syntax may some day clean all this up but the short path wins and it'll be hard to displace. Let's be realistic. I agree we should, assuming classes make it, support DOM subclassing. That is good but it won't relieve DOM implementors from supporting proto.

Is there a parallel to be drawn with (define|lookup)(Getter|Setter), or is proto different? I quite liked Allen's blog post about why IE decided to never support them [1]. Following that reasoning seems to lead to specifying Object.setPrototypeOf as a proto replacement,

No. First, you missed Mark's argument that I paraphrased against a per-frame (window, global context) static method, which David Bruant acknowledged in this thread just eight messages back:

Once we're at it, for the sake of completeness there is probably no harm in adding a Reflect.setPrototype at this point, is there?

There is, just as there's a cost to Object.setPrototypeOf (the obvious place to put it to match Object.getPrototypeOf from ES5). Mark pointed out that he'd have to delete that static method too, and from every frame that might run SES code. But when mashing up SES and non-SES code, it would be better not to break the non-SES code by such deletion.

Having only the SES environment's Object.prototype.proto to delete is better.

And I realize that the new hazard is not due to proto in itself, but rather to the capability of arbitrarily changing the prototype of an object, so adding an Object.setPrototypeOf really is a step backward.

David

--- end quote ---

Second, Allen's rationale, made while he was at Microsoft (no comment), addressed particular extensions with particular arguments. We need to look at the cases:

  • Getters and setters were more often, and better, used by the get/set object literal syntax. That's in ES5 without change.

  • {define,lookup}{G,S}etter could indeed have been standardized but they were not so often used as proto is.

Indeed it was Microsoft's 2005-era "Atlas" AJAX knock off used by live maps that made use of these SpiderMonkey extensions, to dress up non-IE DOMs as if they were the IE DOM -- which assumes non-IE browsers have these __define/lookup... methods. This forced Apple and Opera to reverse-engineer and implement rapidly, but it did not have any effect on IE, of course.

Finally, everyone on TC39 wanted more than these old APIs provided. That led to ES5's meta-programming API under Object.

  • proto is widely used, especially on "the mobile web", which is not "the web" that many who focus on desktop see or think of when authoring. Thus Zepto's use of proto, but not of __define/lookup....

So between proto being much more in demand, __define/lookup not being so much in demand, get/set syntax winning without change, and especially the "mobile web" content divergence due to WebKit, the same rationale for rejecting __define/lookup... did not and does not apply to rejecting proto.

You can't treat all __-affixed extensions the same, in short. They're neither all cursed nor all blessed. Usage in the market matters.

# Brendan Eich (6 years ago)

Brendan Eich wrote:

  • proto is widely used, especially on "the mobile web", which is not "the web" that many who focus on desktop see or think of when authoring. Thus Zepto's use of proto, but not of __define/lookup....

To sharpen this point, Zepto does use Object.keys without polyfill. This is an ES5 method. WebKit on "the mobile web" has ES5 support sufficient to obviate the need for __define/lookup... but not the need for proto.

This is the pressure IE and any non-WebKit browser lacking proto support faces on mobile.

Another way of looking at it: ES5 happened and it is relied upon by mobile content authors, but ES5's Object.create is not sufficient to eliminate uses of proto, e.g. in Zepto.

# Brandon Benvie (6 years ago)

ES5's Object.create is not sufficient to eliminate uses of proto, e.g. in Zepto.

On that note, I find this piece of JavaScript that lives inside V8 slightly amusing as it relates to this discussion:

// ES5 section 15.2.3.5. function ObjectCreate(proto, properties) { if (!IS_SPEC_OBJECT(proto) && proto !== null) { throw MakeTypeError("proto_object_or_null", [proto]); } var obj = new $Object(); obj.proto = proto; if (!IS_UNDEFINED(properties)) ObjectDefineProperties(obj, properties); return obj; }

# David Bruant (6 years ago)

Le 26/05/2012 21:08, Brendan Eich a écrit :

David Bruant wrote:

Because max-min classes are inching towards consensus, I suggest the that appropriate way to subclass the built-in constructors will be via |class extends|. eg

class MyArray extends Array { someMyArrayProtoMethod () {...} constructor (tag) { super(); this.tag=tag } } If that's the case, then that's perfect! It suffers from the same issue than <| which is that it's syntax-based, so minimum cost is an ES6->ES5 compiler

But that is why subclassing the DOM via proto has already dug in as a de-facto standard:

zepto.js: return isObject(value) && value.proto == Object.prototype zepto.js: // to the array. Note that __proto__ is not supported on Internet zepto.js: dom.proto = arguments.callee.prototype "proto", "arguments" and "callee" on the same line. What else? For context: madrobby/zepto/blob/9c79b624b3101b70e0bbd465ea2619f4d2cd832a/src/zepto.js#L108-117

and why IE faces pressure to support proto, including setting for DOM objects.

New syntax may some day clean all this up but the short path wins and it'll be hard to displace. Let's be realistic. I agree we should, assuming classes make it, support DOM subclassing.

I'm not sure we mean the same thing when talking about subclassing. What I mean is being able, from a constructor to create another constructor. Instances of the latter will have the internal properties of instances of the former (instances of SubArray are Arrays (magic 'length'), instances of SubWeakMap are WeakMaps, instances of SubNodeList are NodeLists, etc.). With this definition, it's not possible to subclass the DOM constructors, since they all throw. The DOM API itself would need to be changed to allow subclassing.

Regardless of throwing, even if we could create a SubNodeList type, that would be cool, but it wouldn't change the fact that document.querySelectorAll returns a NodeList and not a SubNodeList. The entire DOM API works this way and doesn't allow method return values to be subclassed as far as I know. Constructors are coming (Event in DOM4 for instance), but all DOM methods still only return objects of types defined in the DOM-related specs. Having instances of SubNodeList is Zepto.js use case [1] and the definition of subclassing I gave above is insufficient to fulfill it. To subclass the DOM, ECMAScript scope isn't enough, changes on the DOM API would be necessary too.

Depressingly enough, document.querySelectorAll calls don't even construct objects with the dynamic NodeList.prototype value [2].

That is good but it won't relieve DOM implementors from supporting proto.

We agree on this.

David

[1] see in zepto.js, the zepto.init function, the call the zepto.qsa followed by "return zepto.Z(dom, selector)" [2] gist.github.com/2815846

# Brandon Benvie (6 years ago)

You're talking about something akin to this?

function Directory(path, child){ var self = []; self.proto = Directory.prototype; self.path = path; self.pushall(files); return self; }

Directory.prototype = []; Directory.prototype.constructor = Directory; Directory.prototype.pushall = Function.prototype.apply.bind(Array.prototype.concat, []); Directory.prototype.children = function children(){ return this.map(function(child){ return this.path + '/' + child; }, this); }

# Brendan Eich (6 years ago)

David Bruant wrote:

With this definition, it's not possible to subclass the DOM constructors, since they all throw. The DOM API itself would need to be changed to allow subclassing.

Neither classes as proposed (sugar for the prototypal pattern) nor proto can fix the DOM.

Note that Date has the same issue. You cannot make a SubDate because the internal state is not exposed to subclassers via private names. Allen propose to fix this, but I'm not sure of the proposal or status.

# Allen Wirfs-Brock (6 years ago)

On May 27, 2012, at 4:13 PM, Brendan Eich wrote:

David Bruant wrote:

With this definition, it's not possible to subclass the DOM constructors, since they all throw. The DOM API itself would need to be changed to allow subclassing.

Neither classes as proposed (sugar for the prototypal pattern) nor proto can fix the DOM.

Note that Date has the same issue. You cannot make a SubDate because the internal state is not exposed to subclassers via private names. Allen propose to fix this, but I'm not sure of the proposal or status.

see my comments in esdiscuss/2012-May/022899

proposal for subclass built-ins on the way...

# Aymeric Vitte (6 years ago)

Le 26/05/2012 22:01, Brendan Eich a écrit :

Domenic Denicola wrote:

Is there a parallel to be drawn with (define|lookup)(Getter|Setter), or is proto different? I quite liked Allen's blog post about why IE decided to never support them [1]. Following that reasoning seems to lead to specifying Object.setPrototypeOf as a proto replacement,

No. First, you missed Mark's argument that I paraphrased against a per-frame (window, global context) static method, which David Bruant acknowledged in this thread just eight messages back:

Once we're at it, for the sake of completeness there is probably no harm in adding a Reflect.setPrototype at this point, is there?

There is, just as there's a cost to Object.setPrototypeOf (the obvious place to put it to match Object.getPrototypeOf from ES5). Mark pointed out that he'd have to delete that static method too, and from every frame that might run SES code. But when mashing up SES and non-SES code, it would be better not to break the non-SES code by such deletion.

Having only the SES environment's Object.prototype.proto to delete is better. And I realize that the new hazard is not due to proto in itself, but rather to the capability of arbitrarily changing the prototype of an object, so adding an Object.setPrototypeOf really is a step backward.

David

If I understand correctly the SES issues are linked to the capability of being able to modify the [[prototype]] internal property of an existing object, that's why it did not surface with the triangle proposal (because a new object is created)

Then what about something doing : obj = Object.create(proto,obj's properties) instead of obj.proto = proto or setPrototypeOf ? Where obj's properties are handled internally

# Brendan Eich (6 years ago)

Aymeric Vitte wrote:

Le 26/05/2012 22:01, Brendan Eich a écrit :

Domenic Denicola wrote:

Is there a parallel to be drawn with (define|lookup)(Getter|Setter), or is proto different? I quite liked Allen's blog post about why IE decided to never support them [1]. Following that reasoning seems to lead to specifying Object.setPrototypeOf as a proto replacement,

No. First, you missed Mark's argument that I paraphrased against a per-frame (window, global context) static method, which David Bruant acknowledged in this thread just eight messages back:

Once we're at it, for the sake of completeness there is probably no harm in adding a Reflect.setPrototype at this point, is there?

There is, just as there's a cost to Object.setPrototypeOf (the obvious place to put it to match Object.getPrototypeOf from ES5). Mark pointed out that he'd have to delete that static method too, and from every frame that might run SES code. But when mashing up SES and non-SES code, it would be better not to break the non-SES code by such deletion.

Having only the SES environment's Object.prototype.proto to delete is better. And I realize that the new hazard is not due to proto in itself, but rather to the capability of arbitrarily changing the prototype of an object, so adding an Object.setPrototypeOf really is a step backward.

David

If I understand correctly the SES issues are linked to the capability of being able to modify the [[prototype]] internal property of an existing object, that's why it did not surface with the triangle proposal (because a new object is created)

Then what about something doing : obj = Object.create(proto,obj's properties) instead of obj.proto = proto or setPrototypeOf ? Where obj's properties are handled internally

We covered this already, I think.

The problem remains that proto is out now, all over the "mobile web". An API requires polyfilling and authors won't do it just to help a new browser enter that market, and the browser vendor trying to enter will throw in the towel and implement proto to solve this chicken-and-egg problem.

An Object.make (nicer Object.create, as you sketch: pass an object literal by which to extend the proto parameter, instead of a pdmap) is plausible, but a separate proposal that does not take away one bit of proto's inertia and momentum.

Allen showed how mustache can be used to make up for lack of <| or Object.make:

var obj = Object.create(proto).{prop: val, prop2: val2};

It's only slightly longer and it falls out of ES5 + mustache (if we can agree on mustache).