Michael Yagudaev
Michael Yagudaev

Reputation: 6129

Overriding Object's Length Property

I found myself in an interesting situation. I am using an object literal to represent a product in the real-world. Now each product has a length associated to it for shipping purposes. It looks something like this:

var product = {
  name: 'MacBook Pro 15 Inch',
  description: 'The new macbook pros....',
  length: 15
  height: 15
  Weight: 4
}

This this works fine. But for products that have unknown length they default to length -1.

Again this works fine, until you try to do this:

console.log('Product has the following properties');
_.each(product, function(val, key) {
    console.log(key + ":" + val);
});

No keys will be printed for a product that has a length of -1. Why? Well because internally underscore uses the length attribute, that every object in Javascript has to loop over all the attributes of the passed in object. Since we overwrote that value, it is now -1, and since we start looping at i = 0, the loop will never be executed.

Now for the question, how can I prevent the length property from being overridden? Best practices to prevent this from happening would also be appreciated.

Upvotes: 0

Views: 1102

Answers (4)

user64417
user64417

Reputation:

If you can't change the name of the length property, you could add an additional check to underscore to prevent this case:

_.each = _.forEach = function(obj, iterator, context) {
  if (obj == null) return;
  if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
    obj.forEach(iterator, context);
  } else if (obj.length === +obj.length && obj.constructor != Object) {
    for (var i = 0, l = obj.length; i < l; i++) {
      if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
    }
  } else {
    for (var key in obj) {
      if (_.has(obj, key)) {
        if (iterator.call(context, obj[key], key, obj) === breaker) return;
      }
    }
  }
};

The addition here is the && obj.constructor != Object on line 5. It works, though monkeypatching underscore may not be the most desirable thing in the world.

EDIT: Actually, this breaks on pseudo-arrays like arguments, since its constructor is Object, but <an array-like object>.hasOwnProperty(<an integer>) is false. Woo JavaScript.

Upvotes: 1

Hacker Wins
Hacker Wins

Reputation: 1289

try this:

    var product = {
        name: "MacBook Pro 15 Inch",
        description: 'The new macbook pros....',
        length: 15,
        height: 15,
        weight: 4
    };
    console.log('Product has the following properties');
    _.each(_.keys(product), function(key){
        console.log(key + ":" + product[key]);
    });

Upvotes: 1

Evert
Evert

Reputation: 99531

I can't think of anything off-hand that overrides it. I suppose using the length property is a form of duck typing that marks it by jQuery as iterable.

But.. you can just not use the jQuery each method, and do it the manual way..

By simply using

for (key in product) { 


}

You get your basic loop. Now if you are potentially overriding the object's prototype, you should also check product.hashOwnProperty(key) to make sure the current key you're iterating is defined in the product instance.

Now if you also need a new closure scope, that's pretty simple too.. here's an alternative each function..

var myEach = function(subject, callback) {

  for (key in subject) {

     if (subject.hasOwnProperty(key)) {
        callback(subject[key], key);
     }
  }

}

Note: untested.

Upvotes: 1

Halcyon
Halcyon

Reputation: 57729

This is perhaps due to some magic in _'s each function as I suspect it accepts both Objects and Arrays .. make your own?

Simple implementation (that avoids some common pitfalls):

function each(obj, map_func) {
    var key;
    for (key in obj) {
        if (Object.prototype.hasOwnerProperty.call(obj, key)) {
            map_func(key, obj[key]);
        }
    }
}

The magic in _'s each is probably due a nasty 'habit' in JavaScript of having things that 'look' like Arrays but aren't actually Arrays, like the magical variable arguments, and I think options in a select DOM Elements as well.

Upvotes: 1

Related Questions