6 JavaScript basic subtle details that you should know.

Nicola Hu
6 min readJan 6, 2021

Basic details, yet fundamental to know.

Tiraya Adam from Unsplash

1. Objects and Arrays referencing.

You know that when declaring a variable with const we cannot modify it.

const c = 6c = 8 //Error!

However, with arrays/objects it is not the same:

const a = ["Z", "X"]a[2] = "Y" // No error.

We did modify the array and it didn’t return error. What’s behind this?
This has to do with variable assignments. When we assign a variable to a primitive type, there is a direct assignment. When we assign to a collection (array, object, etc..) the variable is assigned first to the container. Then the container contains the references to other data.

assignment to a primitive type
assignment to a collection

As we observe from the pictures, assigning to a collection has two level of arrows. The first in blue is to the container. The container then has some other arrows, in red in the picture, directed to the data of the container. While assigning to a primitive type is a direct assignment, i.e. a direct arrow to the data.

const prevents to the first arrow (the blue one) to be redirected. Hence we can add other references, and modify the existing in the collection. In simple words, we cannot change the blue arrow, but we can change the red arrows.

This is a subtle difference, yet is very important. When Trying to make copies. For example, if we have an object and we want to create one similar, but with few differences

const Alice = {height: 180, student: true}const Bob = Alice 
Bob["height"] = 190
// Bob --> { height: 190, student: true}
// What is Alice? It is not anymore {height:180, student:true}

As you might guess it, now also Alice has the same height as Bob.

Why is this? We see it from the picture. When assigning Bob to the object Alice, (const Bob = Alice), we are creating an arrow from the variable Bob to the container to which Alice variable is pointing. When we modify the height, the change affect both Bob and Alice since they’re pointing to the same container.

The solution is a DeepCopy of the object. Which is: a new container is created with reference to variables with the same value. There are many ways to do it (it is not simple as it seems, as there might be nested collections). One would think that the Spread Operator works:

let arr = [1,2,3]
let arrCopy = [...arr] // copy of arr
let obj = {a: 2, b:3}
let objCopy = {...obj} // copy of obj

But this is not, as it makes a deep copy only if the data is not nested, i.e. if the single elements are not objects, arrays, collections.

The right way of doing it is:

// deep clone of: obj = {...}let deepClone = Object.create(Object.getPrototypeOf(obj),  
Object.getOwnPropertyDescriptors(obj));

2. Scope: From inwards to outwards.

let i = 0function test() {
let i = 5;
console.log(i)
}
test()// What does it print?

This function prints 5.

JavaScript (or better, the browser), when executing some code, it looks for the variables first in the scope that is being executed. If it doesn’t find it there, it goes to the outer scope. If it isn’t there neither it goes outside and so on up to the window scope.

We remind that a scope, or lexical environment, is delimited by curly braces “{ .. }”. So whenever we have a class, a function, a for loop, an if… we have some scopes. In particular we call “block scopes” the scopes given by the conditional “if” and the loop “for”, “while”.

3. functions are immediately read.

You already saw that we invoke a function even when it is defined below.

printingHi()function printingHi() {
console.log("Hi")
}

This is the standard procedure of the browser. Functions are read in the actual scope before the execution line by line. For variables this is not true. So this doesn’t hold true if you declare a function like

let func = function() {... some code ...}

This is read at runtime when executed.

4. let vs var, the main difference: scope.

Let and var have many differences. The most important is that

let lives in the actual scope. In particular for the “block scopes” (those with if, for, while).

To var, block scopes are invisible. Let’s illustrate it

for(let k=0; k<1; k++) {  // USING LET
console.log(k)
}
console.log(k) // Error

While

for(var k=0; k<1; k++) {
console.log(k)
}
console.log(k) // Is visible in this outer scope!

So, even if we have the block scope given from the for cycle (we recognize scopes whenever we have curly braces), the “var” doesn’t see it and make the variable live in the outer scope (given it is not a block scope).

5. Loops. “ for … in”, “ for… of”

There is a difference between looping with “for … in” and “for … of”.

“for … in” iterates over the keys/indeces of a collection (that are enumerable). A collection might be an array, a string, an object and also there are some other types of collections, which we won’t discuss here.

For example the following

for(let key in ["a","b"]) {
console.log(key)
}
// Prints
0
1

prints the indeces of the array.
To remember this, think of it as “for let key in …”. The syntax being explicative itself.

“for … of” can be used on iterables (note that objects are not iterables), for example arrays, strings and returns the values in the iterables.


for(let value of [“a”,”b”]) {
console.log(value)
}
// Prints
a
b

This is the basic difference to know. We can go deeper in truth.

We wrote above that “for … in” loops over enumerable properties. What is enumerable?
Properties of literal objects in javascript, beyond being pairs of key-value, have some additional special attributes: “enumerable”, “writeable” and “configurable”. By default they are all set to true when we add new properties to the object. You usually don’t deal with them, but it’s good to know that they exist.

let a = {b: "hi"} // b has enumerable, writeable, configurable set  
// to true.

Also we remind that arrays inherit from objects (not only arrays, also primitive types and other). So we can treat them as objects and add properties to an array:

let arr = ["a", "b"]arr.newProperty = "hi" //Adding a property to the arrayconsole.log(arr['newProperty']) // Returns "hi"

Being a property, it has the attribute “enumerable” by default set to true. So if we were to loop over arr, it would also loop through the key “newProperty” , as we said earlier, since it loops over the key/indeces that are enumerable.

for(let key in arr) {
console.log(key)
}
// Prints
a
b
newProperty // Yes, also this is printed.

While “for … of” would ignore the pair “key-value”, as being of an object, which is not iterable.

6. The DOM is rendered at runtime top to bottom.

For those who do not know what the DOM is: in the simplest words “is a tree of your html page”. A longer definition would be: it is a javascript object, accessible with the variable “document”, that contains all the html elements in the web page. It is required as it allows javascript to communicate with the html.

When you add a script to the html, and try to access the DOM, for example by adding eventlistener, know that you cannot access the elements when they haven’t yet loaded. So for example

<html> 
<head>
<script>
alert(document.querySelector('h1')); // null
</script>
</head>
<body>
<h1> Hi Everyone</h1>
<script>
alert(document.querySelector('h1')); // Returns something
</script>
</body>
</html>

This is also the reason why you should put the script at the end of the body tag.

Also know that there is the document event “DOMContentLoaded”. Which is, the event that is triggered when all the DOM has been loaded (or , simply put, when all the html has been loaded from the browser so that the javascript can interact with). For those who know JQuery is the equivalent of $(document).ready( function() {….}), but in pure vanilla javascript.

Conclusion

Hope you found it useful. Especially if you are beginners there are many starting points of topics that you might want to dive in more deep.

Unilt next time, have a good day!

--

--