Array filter changes the main array

后端 未结 1 1196
鱼传尺愫
鱼传尺愫 2021-01-20 06:01

I\'ve noticed some strange behavior in the array filters on node.js. There is a simple array and a loop:

var array = [
{
    name:\"bob\",
    planet:\"earth         


        
1条回答
  •  南方客
    南方客 (楼主)
    2021-01-20 06:56

    Your code here:

    for(var i = 0; i < filtered.length; i++)
    {
        delete filtered[i].planet; //remove planet
        filtered[i].name = filtered[i].name + "[NEW]"; //add NEW to the name
    }
    

    ...isn't changing either array. It's changing the state of the objects that both arrays refer to.

    Simpler example:

    var a = [{answer:null}];
    var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis
    a[0].answer = 42;
    console.log(b[0].answer); // 42
    

    This line:

    a[0].answer = 42;
    

    doesn't change a or b, it changes the state of what a[0] refers to, which b[0] also refers to.

    Let's throw some ASCII-art Unicode-art at it:

    After this line:

    var a = [{answer:null}];
    

    this is what we have in memory (ignoring some irrelevant details);

    +−−−−−−−−−−−−−−+
    | variable "a" |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+
    | Ref11542     |−−−−>|    array     |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+
                         | 0: Ref88464  |−−−−>|    object    |
                         +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+
                                              | answer: null |
                                              +−−−−−−−−−−−−−−+
    

    a refers to an array object, which has a 0 property, which refers to the object with the answer property. I'm using "Ref11542" and "Ref88494" to represent the object references that a and a[0] contain, but of course we never actually see the value of those references; they're private to the JavaScript engine.

    Then we do this:

    var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis
    

    Now we have:

    +−−−−−−−−−−−−−−+
    | variable "a" |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+
    | Ref11542     |−−−−>|    array     |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+   
                         | 0: Ref88464  |−−+
                         +−−−−−−−−−−−−−−+  |
                                           |
                                           |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+                       +−>|    object    |
    | variable "b" |                       |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+  |  | answer: null |
    | Ref66854     |−−−−>|    array     |  |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+  |
                         | 0: Ref88464  |−−+
                         +−−−−−−−−−−−−−−+
    

    Note that both arrays contain the same object reference (shown here as "Ref88464"); they point to the same object.

    Now we do this:

    a[0].answer = 42;
    

    All that does is change the state of the object; it has no effect on a or b or the arrays they refer to:

    +−−−−−−−−−−−−−−+
    | variable "a" |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+
    | Ref11542     |−−−−>|    array     |
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+   
                         | 0: Ref88464  |−−+
                         +−−−−−−−−−−−−−−+  |
                                           |
                                           |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+                       +−>|    object    |
    | variable "b" |                       |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+  |  | answer: 42   |
    | Ref66854     |−−−−>|    array     |  |  +−−−−−−−−−−−−−−+
    +−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−+  |             ^
                         | 0: Ref88464  |−−+             +−−−−−−− only change is here
                         +−−−−−−−−−−−−−−+
    

    So naturally

    console.log(b[0].answer);
    

    ...outputs 42.


    In a comment you've asked:

    But then how do I set the state of filtered object and not the main one?

    Remember, the objects are the same in both arrays. You haven't copied the objects, you've just created a new array that only has some of them in it.

    If you want to be able to change those objects' properties without affecting the objects in the first array, you need to make copies of the objects.

    If the objects contain only simple primitive values, that's easy; the general case is quite hard. :-)

    In your case, though, since your objets just have name and planet properties, and the very next thing you do is remove the planet property and change the name, we can easily create objects as we go; see comments:

    var array = [
    {
        name:"bob",
        planet:"earth"
    },
    {
        name:"mike",
        planet:"mars"
    },
    {
        name:"vlad",
        planet:"jupiter"
    }];
    
    var filtered = array.filter(function(x){
        return x.name !== "mike";
    }).map(function(x) {
        // Create a *new* object, setting its `name` to the `name`
        // of the original object plus [NEW], and ignoring its
        // `planet` property entirely
        return {name: x.name + "[NEW]"};
    });
    
    console.log(array);
    console.log("---");
    console.log(filtered);

    Alternately, you might want to make just one pass through the array:

    var filtered = [];
    array.forEach(function(x){
        if (x.name !== "mike") {
            filtered.push({name: x.name + "[NEW]"});
        }
    });
    

    0 讨论(0)
提交回复
热议问题