PHP week PHP week
This week up to 80% off on PHP courses. More info

Lesson 4 - Reference And Value Data Types In JavaScript

In the previous lesson, Object Properties and Constructors in JavaScript, we created our first regular object in JavaScript, a company. Objects are reference data types that behave in a different way than value data types, e.g. numbers, in certain aspects. It's important to know what exactly is going on inside the program, otherwise, we'd end up with undesired results.

Value And Reference Data Types

Generally, value types are simple structures, e.g. one number. We work with them mostly to get the job done as fast as possible. There are usually a lot of them in a program and occupy a small amount of memory. They're often described as "light-weight" structures. Examples of value types include number or boolean variables.

An application, more so, its thread, allocates memory from the operating system in the form of a stack. It accesses this memory at very high speeds, but the application can't control its size and the resources are assigned by the operating system. This small, fast memory is used to store local variables of the value type, although the JS standard doesn't require it anyhow and we could write a book about JS memory management in practice. Value types are sometimes synonymous with immutable objects in JavaScript. But let's not bother with things like this for now :)

We can imagine a value-type variable in memory as follows:

Stack in computer memory

The image above shows the memory available to be used by our application. We've created a variable a and JavaScript assigned the number data type to it. Its value is 56 and it was stored directly into the stack. The corresponding code might look something like this:

let a = 56;

You could think of it as the a variable having an allocated part of memory in the stack, of the size of the number data type which is 64 bits, where the value 56 is stored.

Lets create a new simple class that will represent a user of some system:

class User {

    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getUser() {
        return `${this.name} (${this.age})`;
    }
}

The class has 2 simple properties, the constructor, and the getUser() method, so that the users can be printed easily. In some other script, for example, in app.js, we'll create an instance of this class:

let a = 56;
let u = new User("James Brown", 28);

The u variable with the user is now of the reference type. We used let instead of const to save the instance this time, because we'll change the value of the variable in a moment, the keyword doesn't affect how the variable is stored in memory. Let's see how this situation looks like in memory:

Stack and heap in computer memory

We can see that an object, a variable of the reference data type, is not stored in the stack, but in the memory called the heap. It's for this very reason that objects are generally more complicated than value data types. They usually contain other properties and occupy more space in memory.

Both stack and heap are located in the RAM memory. Reference-type variables are actually stored in the memory twice, once in the stack and once in the heap. Within the stack there is something we call a reference, a link to the heap where an actual object can be found.

There are several reasons why things are done this way:

  1. The stack size is limited.
  2. When we want to use the same object multiple times, e.g. to pass it as a parameter into several methods, we don't need to copy it. We only have to pass a small value type containing the reference to the object instead of copying a whole heavy-weight object.
  3. Thanks to references we are able to create structures with dynamic size easily, for example array-like structures in which we can add new elements at run-time. These elements reference each other, like a string of objects.

Now let's declare two variables of the number type and two variables of the User type:

let a = 56;
let b = 28;
let u = new User("James Brown", 28);
let v = new User("Jack White", 32);

Here's what this would look like in memory:

Reference values in computer memory in JavaScript

Now let's assign the b variable to the a variable. We'll also assign the v variable to the u variable. During value assignments, value types are just copied to the stack. Alternatively, when it comes to objects, only its reference is copied (which is in fact a value type too). Assigning references does not create new objects. Now, our code should look something like this:

let a = 56;
let b = 28;
let u = new User("James Brown", 28);
let v = new User("Jack White", 32);
// Assignment
a = b;
u = v;

Memory-wise, it would look like so:

Reference values in computer memory in JavaScript

Now, let's verify the reference mechanism, so we can confirm that it truly works this way :) First, we'll print all 4 variables before and after re-assigment. Since, we'll be printing several times, we'll write a simple function for it in app.js. This time we'll print a clear table to the console where there will be one variable in each row and its values in the columns. So let's edit the code to:

function printVariables() {
    console.table({
            "a": a,
            "b": b,
            "u": u,
            "v": v
        });
}

let a = 56;
let b = 28;
let u = new User("James Brown", 28);
let v = new User("Jack White", 32);

printVariables();

// Assignment
a = b;
u = v;

printVariables();

After running it in your browser, open the Developer Console using F12, then click the Console tab where you will find tables like this:

(index) Value name age
a 56    
b 28    
u   "James Brown" 28
v   "Jack White" 32
(index) Value name age
a 28    
b 28    
u   "Jack White" 32
v   "Jack White" 32

We still can't tell what the difference is between value and reference data types are based on the output. However, we do know that while a and b are really two different numbers with the same value, u and v is the exact same object. Let's change the name of the user v and based off what we know, the change should be reflected in the variable u. We'll add the following to app.js:

v.jmeno = "John Doe";

printVariables();

We've changed the object in the variable v. Now let's print u and v once more:

(index) Value name age
a 56    
b 28    
u   "James Brown" 28
v   "Jack White" 32
(index) Value name age
a 28    
b 28    
u   "Jack White" 32
v   "Jack White" 32
(index) Value name age
a 28    
b 28    
u   "John Doe" 32
v   "John Doe" 32

The user u changes along with v because both variables point to the same object. If you're asking how to create a true copy of an object, the easiest way is to re-create the object by using the constructor and initializing the new object with the same data. Let's get back to our memory situation again and focus on James Brown this time:

Reference types in computer memory in JavaScript

Now what will happen to him, you ask? He'll be "eaten" by what we call the Garbage collector.

Garbage collector

Garbage collector and dynamic memory management

We can allocate memory statically in our programs, meaning that we declare how much memory we'll need in the source code. We've done it several times already and had no problems doing it. We have written the necessary variables in the source code, but soon, we'll make applications, where we won't know how much memory we'll need before we run it. In this case, we deal with dynamic memory management.

In the past, particularly in the era of the languages C, Pascal, and C++, direct memory pointers were used. Altogether, it worked like this: we'd ask the operating system for a piece of memory of certain size. Then, it would reserve it for us and give us its address. We would then create a pointer to this place, through which we worked with the memory. The problem was that no one was looking after what we put into this memory, the pointer just pointed to the beginning of the reserved memory. When we put something larger there, it would be simply stored anyway and overwrite the data beyond our memory's limits, which belonged to some another program or even to the operating system (in this case, OS would probably kill or stop our application). We would often overwrite our program's data in the memory and the program would start to behave chaotically. Imagine that you add a user to an array and it ends up changing the user's environment color which is something that has nothing to do with it. You would spend hours checking the code for mistakes, and you would end up finding out that there's a memory leak in the user's creation that overflew into the color values in memory.

A colleague of mine once said: "The human brain can't even deal with its own memory, so how could we rely on it for program memory management?" Of course, he was right, except for a small group of geniuses, people became tired of solving permanent and unreasonable errors. For the price of a slight performance decrease, managed languages were developed with what we call a Garbage collector, these include JavaScript as well.

Garbage collector

Garbage collector is a program that runs in parallel with our applications, in a separate thread. It weaks up time after time and looks in memory for objects to which there is no longer a reference. It removes them and frees the memory. The performance loss is minimal and it'll significantly reduce the suicide rate of programmers who're trying to debug broken pointers in the evenings. Because the language is managed and doesn't work with direct pointers, it isn't possible to disrupt the memory anyhow, letting it overflow etc., the interpreter will take care of the memory automatically.

Null and undefined value

The last thing to mention today is the null and undefined values.

null

The null keyword indicates that the reference doesn't point to any data. When we set the variable v to null, we only delete this one reference. If there are still any references to our object, it will still exist. If not, GC will remove the object. Let's change the last lines of our program:

v.name = "John Doe";
u = null;

printVariables();

We'll get:

(index) Value name age
a 56    
b 28    
u   "James Brown" 28
v   "Jack White" 32
(index) Value name age
a 28    
b 28    
u   "Jack White" 32
v   "Jack White" 32
(index) Value name age
a 28    
b 28    
u null    
v   "John Doe" 32

We can see that the object still exists and the variable u points to it; however, there is no reference in the variable v anymore.

undefined

Now to undefined. If we create a variable and don't assign a value to it, it's value will be undefined. Let's try it:

let variable;
console.log(variable); // prints undefined

We also get this value, for example, at array indexes where there is no value yet. Thus, it indicates that the variable exists but does not contain anything. The same would happen if we created a function with a parameter and did not specify the parameter when calling it:

function test(parameter) {
    console.log(parameter);
}

test(); // prints undefined

null, unlike undefined, indicates that the variable has already been assigned to and has been set to an empty reference.

In the next lesson, Creating an OOP Diary In JavaScript, we'll program something practical again so that we can practice the new knowledge. I can tell you it will be an electronic diary :)


 

 

Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!