Skip to content

Investigate implementing jQuery (specifically .init) as an Array subclass #1754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mgol opened this issue Oct 21, 2014 · 11 comments
Closed

Investigate implementing jQuery (specifically .init) as an Array subclass #1754

mgol opened this issue Oct 21, 2014 · 11 comments
Assignees

Comments

@mgol
Copy link
Member

mgol commented Oct 21, 2014

Originally reported by gibson042 at: http://bugs.jquery.com/ticket/14411

Inspired by an  interesting Zepto proposal that seems to have legs for us:  http://jsperf.com/jquery-prototype-array/4

I suspect that it could reduce size as well, especially in 2.x.

Issue reported for jQuery 1.10.2

@mgol mgol added this to the Future milestone Oct 21, 2014
@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: dmethvin

It won't work in oldIE, at least. The .length property is squirrely; jeresig tried it around 1.1 timeframe and bailed. @rwaldron wasn't this something still being refined by TC39 for ES6?

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: rwaldron

Replying to dmethvin:

It won't work in oldIE, at least. The .length property is squirrely; jeresig tried it around 1.1 timeframe and bailed. @rwaldron wasn't this something still being refined by TC39 for ES6?

In ES6, it will be as simple as:

class jQuery extends Array {
   constructor(selector, context = document) {
      super();
      // this is now a legit subclass of Array.
   }
   // ... implement jQuery prototype methods here
}

The issue of course is that new subclass semantics can't be "polyfilled" or "ported" to traditional function declaration/expression syntax, because "super" can't be reserved in a function body.

While jQuery was actually quite vocal in the design and specification of this mechanism, we did so understanding that jQuery-the-code wouldn't benefit from real subclassing of builtins for some time.

As far as hackish cases like Zepto, proto is never going to be officially standardized, as it has been sent to die in an annex. Instead, Object.setPrototypeOf will compliment Object.getPrototypeOf.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: gibson042

I'm petty sure I already solved the oldIE length issues in the jsperf by accepting immutability on existing instances (which doesn't change any current API promises), but I'll look into it more later. This is mostly just a focal point for link aggregation until and unless it gets to an actual pull request.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: rwaldron

Replying to gibson042:

I'm petty sure I already solved the oldIE length issues in the jsperf by accepting immutability on existing instances (which doesn't change any current API promises), but I'll look into it more later.

There are actually 2 length related issues:

  • In oldIE, trailing commas produce a "hole"

http://gyazo.com/767c310037b8d505ea54e8bb2dd0bc81.png

  • In every standard, modern JS implementation, Array sub-classes will always have a broken length property.

Compare the following programs:

var a = new Array(1, 2, 3, 4);
console.log( a );
// [ 1, 2, 3, 4 ]
a.length = 10;
console.log( a );
// [ 1, 2, 3, 4, , , , , ,  ]
console.log( a.length );
// 10
a.length = 2;
console.log( a );
// [ 1, 2 ]
console.log( a.length );
// 2

vs.

function List() {
  Array.call(this);
  this.push.apply(this, arguments);
}
List.prototype = Object.create(Array.prototype, {
  constructor: {
    value: List
  }
});
var l = new List(1, 2, 3, 4);
console.log( l );
// [ 1, 2, 3, 4 ]
l.length = 10;
console.log( l );
// [ 1, 2, 3, 4 ]
console.log( l.length );
// 10
l.length = 2;
console.log( l );
// [ 1, 2, 3, 4 ]
console.log( l.length );
// 2

This behaviour will be broken with __proto__ as well:

function List() {
  this.__proto__ = Array.prototype;
  this.push.apply(this, arguments);
}
var l = new List(1, 2, 3, 4);
console.log( l );
// [ 1, 2, 3, 4 ]
l.length = 10;
console.log( l );
// [ 1, 2, 3, 4 ]
console.log( l.length );
// 10
l.length = 2;
console.log( l );
// [ 1, 2, 3, 4 ]
console.log( l.length );
// 2

And of course, the latter version won't work in browsers that don't support proto.

Unrelated to those specific issues, I think inheriting from Array will create a lot of confusing API quirks. Consider the following:

  • Same APIs where jQuery's definition doesn't match the built-in:
    • jQuery.fn.filter and Array.prototype.filter (predicate functions written for use with Array.prototype.filter won't work with jQuery.fn.filter)
    • jQuery.fn.map and Array.prototype.map (mapping functions written for use with Array.prototype.map won't work with jQuery.fn.map)
    • jQuery.fn.find and Array.prototype.find (ES6). Same as "filter"

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: scott.gonzalez

I'm not sure how any of these problems are relevant. As gibson042 already said, this "doesn't change any current API promises". Nobody is doing var divs = $( "div" ); divs.length = 10; And it doesn't matter if our methods are incompatible with Array.prototype methods, unless the incompatibility will actually break things.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: rwaldron

Replying to scott.gonzalez:

I'm not sure how any of these problems are relevant.

They are exactly relevant to these two statements:

"The .length property is squirrely; " - dmethvin

"I already solved the oldIE length issues in the jsperf by accepting immutability on existing instances" - gibson042

Nobody is doing var divs = $( "div" ); divs.length = 10;

I agree, I'm sure they aren't—but I didn't say any was, I was just trying to thoroughly illustrate the "length problems".

And it doesn't matter if our methods are incompatible with Array.prototype methods, unless the incompatibility will actually break things.

I'm not saying it will break extant code, I'm saying that the behaviour could create confusion.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: gibson042

To be clear, I'm interested in performance improvement and size reduction. We've never recommended that anyone set .length (or documented the effects of doing so), and we generally can't expose Array methods because our patterns are to accept selectors in place of elements and pushStack new instances instead of mutating context. Nor will we make new promises like Object.prototype.toString( jQuery() ) === "[object Array]"... this is exclusively about seeking more efficient techniques for implementing our extant API.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: rwaldron

Replying to gibson042:

To be clear, I'm interested in performance improvement and size reduction. We've never recommended that anyone set .length (or documented the effects of doing so),

Yes, I thought I made it clear that this was understood? http://bugs.jquery.com/ticket/14411?replyto=9#comment:8 My point is that once we say "jQuery is now subclassed from Array", developers will expect these things to work like they do with arrays.

and we generally can't expose Array methods because our patterns are to accept selectors in place of elements and pushStack new instances instead of mutating context. Nor will we make new promises like Object.prototype.toString( jQuery() ) === "[object Array]"...

They will be exposed by default and developers will use them. Unless you plan to keep a blacklist of methods to delete? jQuery instance objects will become instanceof Array as well.

I promise I'm not trying to rain on your parade, these are just real things to consider.

@mgol
Copy link
Member Author

mgol commented Oct 21, 2014

Comment author: gibson042

Replying to rwaldron:

My point is that once we say "jQuery is now subclassed from Array", developers will expect these things to work like they do with arrays.

They will be exposed by default and developers will use them. Unless you plan to keep a blacklist of methods to delete? jQuery instance objects will become instanceof Array as well.

We never have to crow about that, and we can continue chastising everyone who tries to take advantage of undocumented implementation details like we currently do with jQuery.support and jQuery.data.

I promise I'm not trying to rain on your parade, these are just real things to consider.

Yep, and I'm actually with you. Changing our prototype chain would be a huge deal... but it might be worthwhile.

@timmywil
Copy link
Member

Moving to Roadmap.

@timmywil timmywil modified the milestone: Future Jan 15, 2016
@lock lock bot locked as resolved and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

3 participants