Bug Tracker

Opened 9 years ago

Closed 8 years ago

#14411 closed feature (migrated)

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: Rick Waldron
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.

Change History (12)

comment:1 Changed 9 years ago by gibson042

Component: unfiledcore
Milestone: None1.next/2.next
Priority: undecidedlow

comment:2 Changed 9 years ago by dmethvin

Cc: Rick Waldron added

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?

comment:3 in reply to:  2 Changed 9 years ago by Rick Waldron

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.

comment:4 Changed 9 years ago by gibson042

Owner: set to 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.

comment:5 in reply to:  4 Changed 9 years ago by Rick Waldron

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"

comment:6 Changed 9 years ago by scottgonzalez

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.

comment:7 Changed 9 years ago by dmethvin

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

comment:8 Changed 9 years ago by Rick Waldron

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.

comment:9 Changed 9 years ago by 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.

comment:10 in reply to:  9 ; Changed 9 years ago by Rick Waldron

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.

comment:11 in reply to:  10 Changed 9 years ago by 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.

Last edited 9 years ago by gibson042 (previous) (diff)

comment:12 Changed 8 years ago by m_gol

Resolution: migrated
Status: assignedclosed
Note: See TracTickets for help on using tickets.