Array filter returns strange results
Asked Answered
V

2

2

Related to this question, i wanted to try out this

var arr = [0,1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]];
arr.filter(Object.hasOwnProperty,"abc");//outputs [0, 1, 2]
arr.filter(Object.hasOwnProperty,"2222222") //[0, 1, 2, 4, 6]

Does anyone knows why filter return these values? Spec of filter and MDN doc also doesn't clearly tell how second argument of filter is used.

Vogler answered 26/3, 2016 at 8:20 Comment(6)
The spec says Array.prototype.filter ( callbackfn [ , thisArg ] ) right in the header and "If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn." on paragraph later - How could it be any more clear than that?Eclat
@Eclat well it wasn't clear to me, pardon my ignorance. Otherwise why would I ask a question? Saying that it will be used as this value for each invocation is not exaclty 2+2=4.Vogler
It says "as the this value for each invocation", and not having encountered this before in JS is strangely at odds with the ability to write lines like arr.filter(Object.hasOwnProperty,"2222222"). (BTW, I didn't downvote, if you think that.)Eclat
@Eclat I am pretty sure I have encountered this before in JS :) But I wasn't sure about how it is being used. Please check the discussion in below answer to understand why it may not be that straightforward. I am not worried about downvotes as much as long as I know how I can improve on my post.Vogler
@Eclat Perhaps you can provide insight at linked Question ? Please illuminateDissogeny
@Vogler It's too late, but I felt compelled to add an answer.Eclat
K
7

The second argument to the Array.prototype.filter is the value that will be set as this to the function that is passed as a first argument.

So your code ends up to be something like:

arr.filter(function(v, i, a) {
    return Object.hasOwnProperty.call("222", v, i, a);
});

So it basically checks if the "222" string has the properties you enumerate in the array.

From that it becomes clear why properties 0, 1 and 2 are found - since those are the indexes of the characters in the "222" string, and, say, 9 or {"abc":123} are not - since "222" string does not have such a properties.

It is the same story with the longer string, which also includes properties 4 and 6 just because it's longer.

Some examples:

Object.hasOwnProperty.call("222", 1); // true, because `"222"[1]` is there
Object.hasOwnProperty.call("222", 'foo'); // false, because `"222"['foo']` is not there
Kasher answered 26/3, 2016 at 8:35 Comment(19)
To be pedantic, it's not "222"[1] but new String("222")[1] - autoboxing.Maryleemarylin
@Maryleemarylin "222"[1] is an expression that involves auto boxing under the hood, so I don't see a point.Kasher
@Kasher [1,2] was returned here as n at var arr = [0,1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]];var n = arr.filter(Object.hasOwnProperty, "abc"); . If 0 is replaced with 7 , [2] is returned. Why is [1,2] returned where 0 is at index 0 , but [2] returned where 7 is at index 0 ?Dissogeny
@Dissogeny okay, and that complies with what I explained, doesn't it?Kasher
@Kasher Your Answer provides "From that it becomes clear why properties 0, 1 and 2 are found" , though only [1,2] are returned , not 0 . Is 0 converted to false at .hasOwnProperty call ? Not certain why substituting 7 for 0 would return [2] ?Dissogeny
I just tried your code and [0, 1, 2] are returned as expected: jsfiddle.net/q1w3csj5Kasher
@Kasher Actually, the way that you described the process is helpful. How do we get , believe, v to the position of "222" at return Object.hasOwnProperty.call("222", v, i, a); ? e.g., if "222" is the property of the object we want to be returned at .filter() ?Dissogeny
I'm not getting what you mean.Kasher
@Kasher Not sure why n was delivering [1,2] at console ?Dissogeny
@Kasher "I'm not getting what you mean" See #36233076Dissogeny
@Kasher Maybe it was the 7 that was returning [1, 2] earlier jsfiddle.net/q1w3csj5/1. Though fairly certain at some point at console [1,2] was returned . Will try again at consoleDissogeny
@Dissogeny maybe because I inserted 0 in the arr (first index) in my question.Vogler
@Dissogeny because "abc" doesn't have 7th index.Vogler
@Vogler Why is only [1,2] returned when result of .filter() is assigned to a variable ?Dissogeny
@Vogler The array has different items; compare jsfiddle.net/q1w3csj5/2 . Try with first array at linked QuestionDissogeny
@Dissogeny yes, and only first two of them have the indexes less than or equal to 2. Other items in array are values greater than 2. "abc".length === 3Vogler
@Vogler At least we can be fairly certain the value is reaching .hasOwnProperty; now, how to return the object that has property "abc", and not the indexDissogeny
@Dissogeny IMHO we need to explore another method than hasOwnProperty :)Vogler
@Vogler Yes, of course; any approach involving the least methods , function calls that can be consolidated to a single line. Perhaps even a method other than .filter(); some form of JSON.stringify() replacer "string" option to pass object properties #36228992 coupled with destructuringDissogeny
E
4

It's crystal clear from the spec

Array.prototype.filter ( callbackfn [ , thisArg ] ),

If athisArg parameter is provided, it will be used as the this value for each invocation of callbackfn.

So:

var arr = [0,1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]];
arr.filter(Object.hasOwnProperty,"2222222");

translates to these calls, in sequence

"2222222".hasOwnProperty(0);             // true     -> 0
"2222222".hasOwnProperty(1);             // true     -> 1
"2222222".hasOwnProperty(2);             // true     -> 2
"2222222".hasOwnProperty(true);          // false    -> 
"2222222".hasOwnProperty(4);             // true     -> 4
"2222222".hasOwnProperty({"abc":123});   // false    -> 
"2222222".hasOwnProperty(6);             // true     -> 6
"2222222".hasOwnProperty(7);             // false    -> 
"2222222".hasOwnProperty({"def":456});   // false    -> 
"2222222".hasOwnProperty(9);             // false    -> 
"2222222".hasOwnProperty([10]);          // false    -> 
                                         // filter() => [0,1,2,4,6]

The lines where it says true are because strings can be indexed into like arrays, so a string with two characters has the indexes 0 and 1 as own properties.

Eclat answered 26/3, 2016 at 9:22 Comment(3)
How do we re-arrange or otherwise persuade {"abc":123}.hasOwnProperty("abc"); ? Though in correct form to return {"abc":123} from .filter() ?Dissogeny
@Dissogeny you mean ({"abc":123}).hasOwnProperty("abc") ?Vogler
@Vogler Yes. Arrange for syntax to be correct, and for .hasOwnProperty to recognize {"abc":123} as the object that it is applying property check atDissogeny

© 2022 - 2024 — McMap. All rights reserved.