34 comments

[ 2.9 ms ] story [ 34.6 ms ] thread
The thing that shocks me most in this article is the subtleness of the bug inducing this behaviour: http://trac.webkit.org/changeset/182058
Yep! Somebody explained quite clearly the bug over at reddit: http://www.reddit.com/r/webdev/comments/3104m3/ios_8_insidio...

I quote:

> There is not length property inserted on the object, the jQuery folks had already confirmed that. It is a branch on the form of object that was failing due to a bug in the linkage of a jump. That made that bug very bizarre: you can access a length value by identifier, but now get a length property on the same object.

The workaround in the article was:

    var thingsGroupedById = _.groupBy(things, function(t){
      return 'seller' + t.sellerId
    });
    // { seller1: [...], seller2: [...] }
But I actually would have failed this in code review:

    _.map(thingsGroupedById, function (things) {
      // ...
    });
and replaced it with:

    _.map(_.keys(thingsGroupedById), function (thingKey) {
      var thing = thingsGroupedById[thingKey];
      // ...
    });
because although mapping over objects is "normal", it has never been 100%.
This interests me very much. Are there known problems about mapping over objects? What do you mean it has never been 100%? Thanks!
The latest version of underscore has a _.mapObject function.
Why not instead do

    _.map(_.values(thingsGroupedById), function(thing) {
        // operate on thing
    }
Then there's no need to do the property access yourself.

Chaining makes this even nicer:

    _(thingsGroupedById).values().map(function(thing) {

    }
(comment deleted)
Depends on what //... is. You might need the key for something, and you can get the value from the key but not vice versa.
The crazy bug aside, I could have done without the animated image macros.
Everyone vents in their own way. I've used stupid pictures before to get over spending a whole afternoon fixing someone else's screw up.
Array.isArray() would be more reliable than testing if it has a property "length" :/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Except as MDN says, that works in IE9+, and jQuery 1.x works in IE6+.
MDN has a polyfill for older browsers, right below the examples.
It's not quite so simple since JavaScript has the concept of array-like objects - basically objects with a length property and numeric keys (but which aren't arrays).

From the Underscore documentation: "Collection functions work on arrays, objects, and array-like objects such as arguments, NodeList and similar".

So basically this bug can lead to false positives when checking for array-like objects.

People still use Underscore?
I am aware of underscore as a nice blob of simple utility functions. I'm not aware of what you would replace it with?
lodash is basically an underscore replacement. https://lodash.com/
For a second there I thought we might get a thread that mentions Underscore without Lodash being plugged.

Oh well, maybe next time.

While the map() should be written in pure JS standard lib, the _.groupBy() is reasonable.
I didn't realize anyone used integers as keys. That feels so unnatural to me. I'm also surprised any libraries would check for .length to see if it's an array or not; there are plenty of objects you may want to create with a length property. It seems to me this would be a more accurate check:

Object.prototype.toString.call(maybeArray) === "[object Array]"

Also sometimes I feel like I'm the only one who doesn't like to use underscore or jQuery for things like this; iterating over properties is so easy it seems weird to me to use a library to create a new object for me to then iterate through.

It's easy if your browser has Object.keys or nobody has mucked around with Object.prototype. As for the ease of testing if something is an array, figuring out if something is an array is relatively easy but array like objects like arguments and domlist are where length checking comes from.
> It's easy if your browser has Object.keys

Most browsers can do a for..in. At least for me there is no browser in existence that I care about that doesn't have that :)

> nobody has mucked around with Object.prototype

That's just what object.hasOwnProperty(key) is for. An annoying extra check but not a big deal.

> As for the ease of testing if something is an array, figuring out if something is an array is relatively easy but array like objects like arguments and domlist are where length checking comes from.

There is an incredibly limited list; I'd just check for those few items instead of relying on a property existing that other objects could use but I digress.

> Most browsers can do a for..in. At least for me there is no browser in existence that I care about that doesn't have that :)

then why are you using the underscore/lodash/jquery method, it's main advantage (besides working on array like objects) is compatibility for older browsers..

I'm, btw, on your side here and just use Object.keys and move on for objects and use a while loop for array like objects.

> then why are you using the underscore/lodash/jquery method, it's main advantage (besides working on array like objects) is compatibility for older browsers..

Oh I'm not my point was it's not really necessary anymore. It's been so long since browsers didn't have support for these kinds of things I can't imagine the compatibility is that important anymore except in say corporate environments.

Not to be a jerk, but why not use objects for key value and arrays for int value?

Yes, JavaScript object/array system has it's warts, but to me that's clearly the way it was intended to be used.

The article described the type of situation this arises in: when you want to use arbitrary data as keys of a map, for example when grouping a collection of objects by one of their properties. Any implementation of a hash/map/dictionary should support this process, whether or not it coerces the types of its keys.

The problem with the engine in question is that it makes a hash change its behavior when all keys are integers.

I ran into this bug myself, and I spent an afternoon or so debugging it.

A simple library upgrade (Underscore 1.7.0 -> 1.8.2) broke a key feature of a site I was working on, but only on my iPhone. It worked just fine on my iPad running the same exact version of iOS.

I ended up using git bisect to narrow down the commit, which consisted of several minor updates to various packages. Of those packages, it was my 3rd or 4th guess as to which would have caused that failure (it wasn't clear since I was having trouble connecting the Safari debugger).

I noticed an instance of _.size(obj) returning "undefined" on a collection using number indices, but in each instance_.keys(obj).length would work fine.

This was occurring on iOS 7.1.2 on a single device, from time to time. I realize after reading this, that this might be related to the WebKit bug, since it was the only 64 bit ARM device we had at the time.

The bug disappeared when I simply used _.keys(obj).length instead of _.size(obj). It was very hard to reproduce and I could never isolate it in a sample...

It's NOT the same bug though. It's kind of the opposite. The "length" property would disappear. Would that be caused by the same WebKit bug? Or that's a new one?