-2

I was working on a personal project when I found that something was going wrong in my code. After few minutes of debugging, I was able to tell what was wrong and how to workaround. But in fact I don't have resolved my original issue.

Look at this:

interface Animals {
    id: number;
    name: string;
    color: string;
}

interface Zoo {
    name: string;
    animals: Animals[];
}

function main() {
    let zoo: Zoo = {
        name : "Valley of monkeys",
        animals : [
            {
                id : 1,
                name: "Foufou",
                color: "brown"
            },
            {
                id: 2,
                name: "Toutou",
                color: "brown"
            },
            {
                id: 3,
                name: "Moumou",
                color: "blue"
            }
        ]
    };

    let zoobis: Zoo;

    zoobis = zoo;

    console.log(zoobis);
    console.log(zoo);

    zoobis.animals = zoo.animals.filter((animal) => animal.color === "brown");

    console.log("============");
    console.log(zoobis);
    console.log(zoo);
}

main();

Link to Typescript Playground to test it :

As you can see there are two mains problems :

  • The first one is that both arrays are modified! Even though in the javascript official documentation, it specified that "filter() does not mutate the array on which it is called."

  • The second one is that both arrays are modified before the call of filter which is weird actually.

The workaround is basically to convert "zoobis" into an Animal[] instead of taking the whole Zoo object. It seems to normally work in this way.

Am I misunderstanding a basic concept of javascript or am I doing stupid mistakes that makes my code doing weird things?

adiga
  • 34,372
  • 9
  • 61
  • 83
JGrenet
  • 32
  • 4

1 Answers1

1

The line

zoobis = zoo;

just makes both the zoobis and the zoo variables point at the same object. Then, the line

zoobis.animals = zoo.animals.filter((animal) => animal.color === "brown");

modifies the state of the one object both variables are pointing at, replacing its animals property with a new filtered array.

Naturally, you see the same object state regardless of whether you look at it via zoobis or via zoo, as both of those variables refer to the same object.

Some ASCII-art:

Once you've created the zoo object, you have something vaguely like this in memory:

                                                                          +−−−−−−−−−−−−−−−−+                    
                                                                       +−>|    (object)    |                    
                     +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  +−−−−−−−−−−−−−−−−+                    
zoo:[Ref71234]−−−−−−>|         (object)          |                     |  | id: 1          |                    
                     +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  | name: "Foufou" |                    
                     | name: "Valley of monkeys" |   +−−−−−−−−−−−−−−−+ |  | color: "brown" |                    
                     | animals: [Ref55412]       |−−>|    (array)    | |  +−−−−−−−−−−−−−−−−+                    
                     +−−−−−−−−−−−−−−−−−−−−−−−−−−−+   +−−−−−−−−−−−−−−−+ |                                        
                                                     | 0: [Ref45132] |−+                      +−−−−−−−−−−−−−−−−+
                                                     | 1: [Ref45174] |−−−−−−−−−−−−−−−−−−−−−−−>|    (object)    |
                                                     | 2: [Ref45228] |−+                      +−−−−−−−−−−−−−−−−+
                                                     +−−−−−−−−−−−−−−−+ |  +−−−−−−−−−−−−−−−−+  | id: 2          |
                                                                       +−>|    (object)    |  | name: "Toutou" |
                                                                          +−−−−−−−−−−−−−−−−+  | color: "brown" |
                                                                          | id: 3          |  +−−−−−−−−−−−−−−−−+
                                                                          | name: "Moumou" |                    
                                                                          | color: "blue"  |                    
                                                                          +−−−−−−−−−−−−−−−−+                    

Notice that zoo contains a reference to the object (conceptually shown above as [Ref71234], but you never actually see the value of an object reference in code).

Then after zoobis = zoo; you have:

zoo:[Ref71234]−−+
                |                                                         +−−−−−−−−−−−−−−−−+                    
                |                                                      +−>|    (object)    |                    
                |    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  +−−−−−−−−−−−−−−−−+                    
                +−−−>|         (object)          |                     |  | id: 1          |                    
                |    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  | name: "Foufou" |                    
                |    | name: "Valley of monkeys" |   +−−−−−−−−−−−−−−−+ |  | color: "brown" |                    
                |    | animals: [Ref55412]       |−−>|    (array)    | |  +−−−−−−−−−−−−−−−−+                    
zoo:[Ref71234]−−+    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+   +−−−−−−−−−−−−−−−+ |                                        
                                                     | 0: [Ref45132] |−+                      +−−−−−−−−−−−−−−−−+
                                                     | 1: [Ref45174] |−−−−−−−−−−−−−−−−−−−−−−−>|    (object)    |
                                                     | 2: [Ref45228] |−+                      +−−−−−−−−−−−−−−−−+
                                                     +−−−−−−−−−−−−−−−+ |  +−−−−−−−−−−−−−−−−+  | id: 2          |
                                                                       +−>|    (object)    |  | name: "Toutou" |
                                                                          +−−−−−−−−−−−−−−−−+  | color: "brown" |
                                                                          | id: 3          |  +−−−−−−−−−−−−−−−−+
                                                                          | name: "Moumou" |                    
                                                                          | color: "blue"  |                    
                                                                          +−−−−−−−−−−−−−−−−+                    

Notice how the value of zoo ([Ref71234]) was copied into zoobis, but that value is just a reference to the one object.

Then after [filter]:

zoo:[Ref71234]−−+
                |                                                         +−−−−−−−−−−−−−−−−+                    
                |                                                      +−>|    (object)    |                    
                |    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  +−−−−−−−−−−−−−−−−+                    
                +−−−>|         (object)          |                     |  | id: 1          |                    
                |    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+                     |  | name: "Foufou" |                    
                |    | name: "Valley of monkeys" |   +−−−−−−−−−−−−−−−+ |  | color: "brown" |                    
                |    | animals: [Ref65241]       |−−>|    (array)    | |  +−−−−−−−−−−−−−−−−+                    
zoo:[Ref71234]−−+    +−−−−−−−−−−−−−−−−−−−−−−−−−−−+   +−−−−−−−−−−−−−−−+ |                                        
                                                     | 0: [Ref45132] |−+                      +−−−−−−−−−−−−−−−−+
                                                     | 1: [Ref45174] |−−−−−−−−−−−−−−−−−−−−−−−>|    (object)    |
                                                     +−−−−−−−−−−−−−−−+                        +−−−−−−−−−−−−−−−−+
                                                                                              | id: 2          |
                                                                                              | name: "Toutou" |
                                                                                              | color: "brown" |
                                                                                              +−−−−−−−−−−−−−−−−+

Notice how you replaced the old animals value ([Ref55412]) with a new value ([Ref65241]) because you created and stored a new array.

If you want to copy zoo, you can use Object.assign or property spread (ES2018+):

zoobis = Object.assign({}, zoo);
// or
zoobis = {...zoo};

That makes a shallow copy (so for instance, both objects' animals property points to the same array). A shallow copy is sufficient for what you've shown (but might be problematic if you have other properties on zoo referring to objects).

If you want to make a deep copy, see this question's answers.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • But i'm not asking for a reference in this case. How can i create a duplicate of "zoo" and assign it to "zoobis" ? – JGrenet Nov 26 '18 at 13:56
  • You can copy the zoo object as zoobis by using zoobis = {...zoo}; (spread into new object) – MikeOne Nov 26 '18 at 13:59
  • 1
    @Ragaan - *"But i'm not asking for a reference in this case."* That's what `zoobis = zoo;` does, it copies the value of `zoo` (an object reference) into `zoobis`. I've updated the answer to show more detail and to talk about object copying at the end. – T.J. Crowder Nov 26 '18 at 14:10
  • Big big thanks guys, i was not aware of this default behavior of javascript. In my experience you have to specify explicitely if you want to have a reference instead of a copy (like the "&" in C). Thank you for taking the time to answer me :) – JGrenet Nov 26 '18 at 14:15
  • @Ragaan - No worries! FWIW, this is fairly common outside the C family of languages, even in languages that are in the same syntactic tradition. Java, JavaScript, Go, C#, PHP, Python -- they all work this way. C++'s object variables that trigger a copy constructor on assignment (IIRC) are a bit unusual (but in their way, cool). Object references in the languages I mentioned basically work like C pointers. Happy coding! – T.J. Crowder Nov 26 '18 at 14:25
  • That ASCII-art is fantastic. Did you use any site to create that or copied one *box* from somewhere and created rest on your own? – adiga Nov 26 '18 at 14:27
  • 1
    @adiga - Thanks! Just used `vim` (the text editor). :-) – T.J. Crowder Nov 26 '18 at 14:38