shian
shian

Reputation: 124

underscore.js every() with undefined

I wrote some code like this:

var a = new Array(10); // should be [undefined * 10] 
var b = _.every(a, function(m){
    if(_.isUndefined(m)){
        return false;
    }
    return true;
});

I expect b is 'false', but it return 'true'. Why it return 'true'?

Then, I change to this:

var c = [undefined, undefined];
var d = _.every(c, function(m){
    if(_.isUndefined(m)){
        return false;
    }
    return true;
});

it return 'false' in d.

Why they are different?

You can test this in http://jsfiddle.net/3qj4B/3/

Upvotes: 1

Views: 474

Answers (2)

Pointy
Pointy

Reputation: 413720

There's a subtle difference between initializing an array with the Array constructor and an initial size, and initializing with an explicit list of undefined entries. The former (the Array constructor) doesn't create properties corresponding to the indexes, while the explicit initialization does.

I suspect that Underscore is using the native .forEach() when it can, and that won't call its callback for uninitialized indexes. Thus, in the first test, the callback to _.every() is never being called at all.

edit — the Array constructor does something more-or-less the same as:

var a = []; a.length = 10;

When you extend an array by increasing its length like that, the new implicit index positions aren't initialized. It's the basic difference between the non-existence of a property, and the presence of a property with no value. In both cases, a dereference of the property results in undefined. Thus:

var o = {};

if (o.something == undefined) // this will be true

and then:

var o = { something: undefined };

if (o.something == undefined) // also true

One way to tell the difference between the two situations is the in operator:

if ('something' in o) // only true in the second case

Upvotes: 1

Niccolò Campolungo
Niccolò Campolungo

Reputation: 12042

When you create an array using the class initializer you are creating an array with 10 space of memory available to be used, but none of them is yet initialized. So you're not looping over anything.

Now look at the source of every:

_.every = _.all = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = true;
    if (obj == null) return result;       
    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
    each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
};

result is set to be true and it is returned since each does no iterations. That's why you're returning true.

If, in your fiddle, you try to add a console.log(m) inside the handler you'll see no logs in the console since each iterates 0 times.

Upvotes: 2

Related Questions