Skip to main content

Bug Tracker

Side navigation

#14411 closed feature (migrated)

Opened October 01, 2013 10:24PM UTC

Closed October 21, 2014 12:23AM UTC

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

Reported by: gibson042 Owned by: gibson042
Priority: low Milestone: 1.next/2.next
Component: core Version: 1.10.2
Keywords: Cc: rwaldron
Blocked by: Blocking:
Description

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.

Attachments (0)
Change History (12)

Changed October 01, 2013 10:26PM UTC by gibson042 comment:1

component: unfiledcore
milestone: None1.next/2.next
priority: undecidedlow

Changed October 01, 2013 10:33PM UTC by dmethvin comment:2

cc: → rwaldron

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?

Changed October 01, 2013 10:41PM UTC by rwaldron comment:3

Replying to [comment:2 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.

Changed October 02, 2013 12:15AM UTC by gibson042 comment:4

owner: → gibson042
status: newassigned

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.

Changed October 02, 2013 04:29PM UTC by rwaldron comment:5

Replying to [comment:4 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"

[[Image(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"

Changed October 02, 2013 04:36PM UTC by scottgonzalez comment:6

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.

Changed October 02, 2013 04:59PM UTC by dmethvin comment:7

I'm not seeing the perf benefit, strange. http://wnd8.com/grab/c42af8.png

Changed October 02, 2013 05:02PM UTC by rwaldron comment:8

Replying to [comment:6 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.

Changed October 03, 2013 03:37AM UTC by gibson042 comment:9

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.

Changed October 03, 2013 03:47AM UTC by rwaldron comment:10

Replying to [comment:9 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.

Changed October 03, 2013 04:09AM UTC by gibson042 comment:11

_comment0: Replying to [comment:10 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.1380773464446051

Replying to [comment:10 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.

Changed October 21, 2014 12:23AM UTC by m_gol comment:12

resolution: → migrated
status: assignedclosed