Explaining Value vs. Reference in JavaScript

Explaining Value vs. Reference in JavaScript

JavaScript variables

In JavaScript, a variable is a named reference to a memory location that can hold different types of data, such as numbers, strings, booleans, objects, or functions. To access a variable or modify the data stored in a variable, we use its name.

If you have a basic understanding of computer memory then you must know that Computer memory has millions of cells on it and every cell has its own address

https://cdn.hashnode.com/res/hashnode/image/upload/v1684952050726/05637dcb-66e9-4c08-a66a-eeef3ff5a894.jpeg?auto=compress,format&format=webp

The problem is we can't use this address directly in our program. because you know the computer works in the binary system and in the binary system this memory address looks too weird and confusing. also, it's almost impossible to memorize.

Here variable comes in to solve this problem.

Variable gives us the simplest solution to handle it. We can simply create a variable and assign it to the value we want to store. now we don't need to memorize the weird and confusing memory address we can do the same using a simple and human-readable name.

javascript Variables Beginner Thinking (robiul.dev).jpg

That means,

  • Creating a variable

  • Giving a standard name to the variable

  • Assigning a value to it.

Think of these steps as

  • Taking a container

  • Labeling it

  • Putting something in this container.

javascript Variables Beginner Thinking (robiul.dev).jpg


Javascript has 5 data types that are passed by value: Boolean, null, undefined, String, and Number. We’ll call these primitive types.

Javascript has 3 data types that are passed by reference: Array, Function, and Object. These are all technically Objects, so we’ll refer to them collectively as Objects.

Primitives

If a primitive type is assigned to a variable, we can think of that variable as containing the primitive value.

var x = 10;
var y = 'abc';
var z = null;

x contains 10. y contains 'abc'. To cement this idea, we’ll maintain an image of what these variables and their respective values look like in memory.

When we assign these variables to other variables using =, we copy the value to the new variable. They are copied by value.

var x = 10;
var y = 'abc';var a = x;
var b = y;console.log(x, y, a, b); // -> 10, 'abc', 10, 'abc'

Both a and x now contain 10. Both b and y now contain 'abc'. They’re separate, as the values themselves were copied.

Changing one does not change the other. Think of the variables as having no relationship to each other.

var x = 10;
var y = 'abc';var a = x;
var b = y;a = 5;
b = 'def';console.log(x, y, a, b); // -> 10, 'abc', 5, 'def'

Primitive Types

The in-memory value of a primitive type is its actual value (e.g. boolean true, number 42). A primitive type can be stored in the fixed amount of memory available.

  • null

  • undefined

  • Boolean

  • Number

  • String

Primitive types are also known as scalar types or simple types.


Objects

This will feel confusing, but bear with me and read through it. Once you get through it, it’ll seem easy.

Variables that are assigned a non-primitive value are given a reference to that value. That reference points to the object’s location in memory. The variables don’t actually contain the value.

Objects are created at some location in your computer’s memory. When we write arr = [], we’ve created an array in memory. What the variable arr receives is the address, the location, of that array.

When we assign and use a reference-type variable, what we write and see is:

1) var arr = [];
2) arr.push(1);

A representation of lines 1 and 2 above in memory is:

1.

2.

Notice that the value, the address, contained by the variable arr is static. The array in memory is what changes. When we use arr to do something, such as pushing a value, the Javascript engine goes to the location of arr in memory and works with the information stored there.

Reference Types

A reference type can contain other values. Since the contents of a reference type can not fit in the fixed amount of memory available for a variable, the in-memory value of a reference type is the reference itself (a memory address).

  • Array

  • Object

  • Function

Reference types are also known as complex types or container types.

Assigning by Reference

When a reference type value, an object, is copied to another variable using =, the address of that value is what’s actually copied over as if it were a primitive. Objects are copied by reference instead of by value.

var reference = [1];
var refCopy = reference;

The code above looks like this in memory.

Each variable now contains a reference to the same array. That means that if we alter reference, refCopy will see those changes:

reference.push(2);
console.log(reference, refCopy); // -> [1, 2], [1, 2]

We’ve pushed 2 into the array in memory. When we use reference and refCopy, we’re pointing to that same array.

Reassigning a Reference

Reassigning a reference variable replaces the old reference.

var obj = { first: 'reference' };

In memory:

When we have a second line:

var obj = { first: 'reference' };
obj = { second: 'ref2' }

The address stored in obj changes. The first object is still present in memory, and so is the next object:

When there are no references to an object remaining, as we see for the address #234 above, the Javascript engine can perform garbage collection. This just means that the programmer has lost all references to the object and can’t use the object any more, so the engine can go ahead and safely delete it from memory. In this case, the object { first: 'reference' } is no longer accessible and is available to the engine for garbage collection.

\== and ===

When the equality operators, == and ===, are used on reference-type variables, they check the reference. If the variables contain a reference to the same item, the comparison will result in true.

var arrRef = [’Hi!’];
var arrRef2 = arrRef;console.log(arrRef === arrRef2); // -> true

If they’re distinct objects, even if they contain identical properties, the comparison will result in false.

var arr1 = ['Hi!'];
var arr2 = ['Hi!'];console.log(arr1 === arr2); // -> false

If we have two distinct objects and want to see if their properties are the same, the easiest way to do so is to turn them both into strings and then compare the strings. When the equality operators are comparing primitives, they simply check if the values are the same.

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);console.log(arr1str === arr2str); // true

Passing Parameters through Functions

When we pass primitive values into a function, the function copies the values into its parameters. It’s effectively the same as using =.

var hundred = 100;
var two = 2;function multiply(x, y) {
    // PAUSE
    return x * y;
}var twoHundred = multiply(hundred, two);

In the example above, we give hundred the value 100. When we pass it into multiply, the variable x gets that value, 100. The value is copied over as if we used an = assignment. Again, the value of hundred is not affected. Here is a snapshot of what the memory looks like right at the PAUSE comment line in multiply.

Pure Functions

We refer to functions that don’t affect anything in the outside scope as pure functions. As long as a function only takes primitive values as parameters and doesn’t use any variables in its surrounding scope, it is automatically pure, as it can’t affect anything in the outside scope. All variables created inside are garbage-collected as soon as the function returns.

Let’s go into an example of a pure vs. impure function.

function changeAgeImpure(person) {
    person.age = 25;
    return person;
}var alex = {
    name: 'Alex',
    age: 30
};var changedAlex = changeAgeImpure(alex);console.log(alex); // -> { name: 'Alex', age: 25 }
console.log(changedAlex); // -> { name: 'Alex', age: 25 }

Let’s look at a pure function.

function changeAgePure(person) {
    var newPersonObj = JSON.parse(JSON.stringify(person));
    newPersonObj.age = 25;
    return newPersonObj;
}var alex = {
    name: 'Alex',
    age: 30
};var alexChanged = changeAgePure(alex);console.log(alex); // -> { name: 'Alex', age: 30 }
console.log(alexChanged); // -> { name: 'Alex', age: 25 }

Test

function changeAgeAndReference(person) {
    person.age = 25;
    person = {
        name: 'John',
        age: 50
    };

    return person;
}var personObj1 = {
    name: 'Alex',
    age: 30
};var personObj2 = changeAgeAndReference(personObj1);console.log(personObj1); // -> ?
console.log(personObj2); // -> ?
console.log(personObj1); // -> { name: 'Alex', age: 25 }
console.log(personObj2); // -> { name: 'John', age: 50 }

Remember that assignment through function parameters is essentially the same as assignment with =. The variable person in the function contains a reference to the personObj1 object, so initially it acts directly on that object. Once we reassign person to a new object, it stops affecting the original.

This reassignment does not change the object that personObj1 points to in the outer scope. person has a new reference because it was reassigned but this reassignment doesn’t change personObj1.

Notes

All primitive datatype excepted null tested by typeof "typeof(null) == "object"".

Undefined

  • A return statement with no value (return;) implicitly returns undefined.

  • Accessing a nonexistent object property (obj.iDontExist) returns undefined.

  • A variable declaration without initialization (let x;) implicitly initializes the variable to undefined.

  • Many methods, such as Array.prototype.find() and Map.prototype.get(), return undefined when no element is found.

Null

null is used much less often in the core language. The most important place is the end of the prototype chain — subsequently, methods that interact with prototypes, such as Object.getPrototypeOf(), Object.create(), etc., accept or return null instead of undefined.

null is a keyword, but undefined is a normal identifier that happens to be a global property.

Difference between null and undefined

Undefined -> means that a var has been declared but has not yet been assigned a value.

Null -> is an assignment value it can be assigned to a var as a representation of nonvalue.

Undefined is a type itself, and null is an object.

null = "value"; //Syntaxerror invalid left-hand side undefined value
undefined = "value"; // non error

undefined means a variable has been declared but has not yet been assigned a value :

var testVar;
console.log(testVar); //shows undefined
console.log(typeof testVar); //shows undefined

null is an assignment value. It can be assigned to a variable as a representation of no value :

var testVar = null;
console.log(testVar); //shows null
console.log(typeof testVar); //shows object

From the preceding examples, it is clear that undefined and null are two distinct types: undefined is a type itself (undefined) while null is an object.

Proof :

console.log(null === undefined) // false (not the same type)
console.log(null == undefined) // true (but the "same value")
console.log(null === null) // true (both type and value are the same)

and

null = 'value' // Uncaught SyntaxError: invalid assignment left-hand side
undefined = 'value' // 'value'

In JavaScript we can have the following types of variables:

  1. Undeclared Variables

  2. Declared but Unassigned Variables

  3. Variables assigned with literal undefined

  4. Variables assigned with literal null

  5. Variables assigned with anything other than undefined or null

The following explains each of these cases one by one:

  1. Undeclared Variables

    • Can only be checked with the typeof operator which returns string 'undefined'

    • Cannot be checked with the loose equality operator ( == undefined ), let alone the strict equality operator ( === undefined ),
      as well as if-statements and ternary operators ( ? : ) — these throw Reference Errors

  2. Declared but Unassigned Variables

    • typeof returns string 'undefined'

    • == check with null returns true

    • == check with undefined returns true

    • === check with null returns false

    • === check with undefined returns true

    • Is falsy to if-statements and ternary operators ( ? : )

  3. Variables assigned with literal undefined
    These variables are treated exactly the same as Declared But Unassigned Variables.

  4. Variables assigned with literal null

    • typeof returns string 'object'

    • == check with null returns true

    • == check with undefined returns true

    • === check with null returns true

    • === check with undefined returns false

    • Is falsy to if-statements and ternary operators ( ? : )

  5. Variables assigned with anything other than undefined or null

    • typeof returns one of the following strings: 'bigint', 'boolean', 'function', 'number', 'object', 'string', 'symbol'

Following provides the algorithm for correct type checking of a variable:

  1. Get the typeof our variable and return it if it isn't 'object'

  2. Check for null, as typeof null returns 'object' as well

  3. Evaluate Object.prototype.toString.call(o) with a switch statement to return a more precise value. Object's toString method returns strings that look like '[object ConstructorName]' for native/host objects. For all other objects (user-defined objects), it always returns '[object Object]'

Number type

Positive floating-point numbers between 2-1074 (Number.MIN_VALUE) and 21024 (Number.MAX_VALUE) as well as negative floating-point numbers between -2-1074 and -21024.

Values outside the range ±(2-1074 to 21024) are automatically converted:

The Number type has only one value with multiple representations: 0 is represented as both -0 and +0 (where 0 is an alias for +0). In practice, there is almost no difference between the different representations; for example, +0 === -0 is true. However, you are able to notice this when you divide by zero:

console.log(42 / +0); // Infinity
console.log(42 / -0); // -Infinity