loading...

Why are objects like this?

tttaaannnggg profile image tang ・4 min read

I went to a workshop was called "JavaScript The Easier Parts: Objects, Functions, Execution Context". I thought that it was going to be a very basic overview of functions and scope, teaching me that a local variable isn't accessible outside of the function where it's defined, or how to declare an object in JS, or store or call its methods. All stuff I'd done before in other tutorials. I got those things, yeah, but I also got a lot of the theory and fundamentals that I was hoping for. The instructor laid out a fairly comprehensive model of the call stack, global memory, global execution context, and locally scoped execution context/memory.

Being taught that functions, objects, variables, and primitives are all stored in memory when initialized, and that using any of them just means calling the version that's currently stored in memory- that explains why reassigning a variable works: you're just rewriting what's stored in the (global or local) memory, and then accessing that stored value. The value that's stored in memory isn't necessarily the same one that you might explicitly see in the code itself.

I also learned about objects and pointers in JS, and that an array is an object in javascript. In order to save space, an object is a collection of pointers that store the location of data saved inside of them. One particularly insidious result of this is that when you assign, say object2 = object1, you're not copying the properties and methods of object1 into object2. You're only copying the pointers (or is it that you're having the pointers of object2 point at the pointers of object1? Either way, it seems to work the same).

This has a couple of interesting side effects. One is that if we're assigning object2 = object1, changing any of the properties in object2 will also change the same property in the same way in object1. This is because both are pointing at the same place in memory.

Here's how it works for objects:

let objA = {property1: 1};
let objB = objA;

(objA.property1 == 1) // returns true
(objB.property1 == 1) // also returns true

// now let's reassign objB:
objB.property1 = 2;

// and we see this behavior:
(objB.property1 == 2) // returns true
(objA.prooperty1 == 2) // ALSO RETURNS TRUE!

Contrast this with how primitives work.

let a = 1;
let b = a;
(a == 1) // returns true
(b == 1) // returns true

//now let's reassign b:
b = 2

// now we see:
a == 1 // still returns true
b == 2 // returns true

What trips me up is that we didn't do anything to objA after its initial assignment, but because objB points at objA, which points at the properties and values stored in memory, changing objB.property1 still changes objA.property.

That's kind of an unintuitive thing for me, but what really tripped me up in finishing my last problemset was that arrays are objects in javascript. That means that, when building a function to deal with an array, I can't just do this:


function arrayFunction(array){
    array2 = array;
}

and expect to be able to manipulate array2 without damaging the data in the array that's passed as an argument to arrayFunction. I haven't worked out the most elegant way to get it done, but I do know I can iterate through the values stored in an array and copy them one by one to a new array in order to ensure that they're stored in a separate block of memory, which I can manipulate or destroy without affecting the original array.

Now, if that wasn't enough, we also have certain things to consider when comparing objects.

Remember I mentioned that objects store data by essentially having a pointer directing you to where that data is stored in memory? Well, when we compare objects, we're really checking if they're pointing at the same place in memory. Let me show you an example.


let objA = {property1: 1};
let objB = objA;

objA == objB //returns true, as expected

//but let's make a third object here:
let objC = {property1: 1};

//and let's do some comparisons!

objC == objA // returns false
objC == objB // also returns false!

So, despite having the same number of properties, the same name for the property, and the same value, objC doesn't point at the same memory address where that data is stored, and therefore isn't the same as objA or objB. Instead, we have to individually check whether they have the same number of properties, whether the names of those properties are the same, and whether their values are the same by iterating through the object. If there are objects stored within the object, then we have to do the whole thing recursively until we get to the ultimate properties and values.

Whew.

Now, in Javascript, arrays are objects. That means all of this stuff applies to comparing arrays.

Luckily, it's much simpler to do this, because we can easily iterate through any array with a for loop.

for (let i=0; i<array.length; i++){
    console.log(array[i]);
}

for instance, will print all values of an array called array. We just have to nest two loops like this, and we can reliably do a comparison between two arrays.

So yeah, my brain is frying, and you can probably see it oozing out of my ears. I'll be headed back for a second workshop today, and it's gonna be about recursion. I expect I'll be nice and crispy by the end of it.

Posted on by:

tttaaannnggg profile

tang

@tttaaannnggg

"Poets do not go mad; but chess-players do. Mathematicians go mad, and cashiers; but creative artists very seldom." -GK Chesterton

Discussion

markdown guide
 

This are the (some) good reasons why a dev should learn first a programming language with more power ,lower level, that has pointers and be strong typed. After that is easier to learn a dynamic one like JS.

You can do some further research on:

  • pointers
  • pass by value vs pass by reference
  • primitives vs objects
  • scope
  • stack and heap
  • immutability

Not only in JS, but how how other languages does them too.

Knowing all these will save you the trouble of wasting hours debuging JS or any other language for small mistakes.
One of your examples is even in the MDN Primitive section: developer.mozilla.org/en-US/docs/G... and is a mix of the concepts I listed.
The 2nd foo is a new variable on the stack, shadowing the first foo, you can change its value but is a different variable, with a different address memory, so it will not affect the first foo. Also because the numbers are immuntable so the runtime will put the 7 in a new place in memory, so one side effect is consuming more RAM.

Bottom line is that if you do not know the side effects of each LOC, you cannot guarantee that is the best solution for a problem, and you will not know its limitations.