Logo
Published on

Interesting topics of the JS land

Authors

Why does this post exist?

This post was created to collect cool topics of the JS land, that sometimes may be crucial to remember, and sometimes are just funny to know about. Briefly, this post it's a collection of the 'JS moments' - that we as developers don't see very often, but understanding them may be very helpful.
Most of the topics gonna be something like: guess the output or why it doesn't work.

EventLoop execution order: promises, queueMicrotask.

Guess the output order.

Promise.resolve().then(() => console.log(1));

queueMicrotask(() => console.log(2));

setTimeout(() => {
    console.log(3);
}, 0);

console.log(4);

new Promise(() => {
    console.log(5);
});

(async () => console.log(6))();
Answer

4, 5, 6, 1, 2, 3

Explanation:

  1. console.log(4); - that is a synchronous function call.

  2. Body of the new Promise() always runs synchronously.

  3. Immediately invoked function expression IIFE:

    (async () => console.log(1))();
    new Promise(() => console.log(2));
    console.log(3);
    // 1, 2, 3
    
  4. Since the call stack is empty now, EventLoop can process queues: microtask and after macrotask. And first item in microtask queue is resolved promise: then(() => console.log(1).

  5. Next queueMicrotask(() => console.log(2)); - based on the name it's obvious from what queue this callback came to call stack.

  6. Since callstack is empty and nothing else in microtask queue, EventLoop will process macrotask queue: setTimeout(() => console.log(3), 0);

Which of the following are not considered microtask in JavaScript's EventLoop?

a. Script loading
b. setTimeout callback
c. mousemove event
d. requestAnimationFrame callback
e. UI rendering
f. then callback
g. new Promise execution function
h. fetch calls

Answer

All of them except then callback. Only then callback will be added to the microtask queue.

EventLoop execution order: promises, async/await.

What value will be last one?

async function asyncFunction() {
    console.log(1);
    new Promise(() => console.log(2));
    await new Promise((res) => {
        setTimeout(() => console.log(3), 0);
        res();
    });
}

new Promise((res) => {
    console.log(4);
    (async () => {
        console.log(5);
        await asyncFunction()
        console.log(6);
    })();
    res();
}).then(() => console.log(7));

console.log(8);
Answer

4, 5, 1, 2, 8, 6, 7, 3

So, What exactly happened?

aaa_so_what
  1. new Promise pushed to the call stack.
  2. First thing in its body is: console.log(4); pushed to the callstack, executed, and popped out.
  3. After that we have IIFE async () => :
    • this body runs synchronously till the await moment. console.log(5); -> to stack, executed, popped.
  4. asyncFunction - pushed to stack.
  5. console.log(1); - to stack, executed, popped.
  6. new Promise(() => console.log(2)); - to stack, executed, popped.
  7. await new Promise((res)- pushed to stack.
    • setTimeout(() => console.log(3), 0) - to stack, to the web timer API, and on the NextTick of EventLoop to the macrotask queue.
  8. Body of asyncFunction executed, pushed to its queues - we are still awaiting and asyncFunction itself pushed to the microtask queue and EventLoop will execute, rest of the code.
  9. res(); pushed to the stack, await new Promise popped out from the stack.
  10. then(() => console.log(7)); - to stack and its callback console.log(7) to the microtask queue.
  11. console.log(8); - to stack, executed, popped.
  12. Now callstack is empty: EventLoop will start to pop out from queues.
  13. asyncFunction from the microtask queue - implicit return undefined.
  14. Since asyncFunction executed completely, rest of (async () => { can be pushed to microtask queue - console.log(6); to the microtask queue.
  15. console.log(7) - to stack, executed, popped.
  16. Rest of the (async () => { to the stack -> console.log(6); - to stack, executed, popped.
  17. () => console.log(3) - to stack, executed, popped.

EventLoop execution order: async, Promise.all.

What value will be first one?

(async () => {
    const asyncFunc = async () => "asyncFunc";

    const promise = new Promise(() => {
        console.log("promise");
    }).then(() => console.log("then"));

    console.log("asyncBody");

    queueMicrotask(() => {
        console.log("queueMicrotask");
    });

    const results = await Promise.all([asyncFunc(), promise]);

    return results;
})();

console.log("script");
Answer

promise, asyncBody, script, queueMicrotask

Why, there is no then from the line 6? Because new Promise defined on line 4, was never resolved - it's remained in a pending state.

Scopes and Closures

There are three types of scopes in JavaScript: Global, Block, Function

const name = "John" ;        // Global Scope
{
    const name = "John";     // Block Scope
}
function foo() {
    const name = "John";     // Function Scope 
}

When one function is defined inside another - the inner function has access to the outer function's scope. JavaScript closure it's a combination of a function and references to its lexical environment. In JavaScript, closures are created every time a function is created, at function creation time.

What is the output?

function createCounter() {
    let global = 0;
    function incrementCounter() {
        const incremented = ++global;
        return incremented;
    }
    return { incrementCounter };
}

const counter = createCounter();

console.log(counter.incrementCounter());
console.log(counter.incrementCounter());
console.log(createCounter().incrementCounter());
Answer

1, 2, 1

Are these objects are equal?

function userFactory() {
    let user = { name: "", createAt: Date.now() };

    return (name) => {
        "use strict";
        user.name = name;
        user.createAt = Date.now();
        return user;
    };
}
const createUser = userFactory();
console.log(createUser("John") === createUser("NotJohn"));
Answer

Yes, it's the same object.

userFactory function it's a buggy implementation of the factory pattern, to fix the bug new object must be returned. But I think that is great example of the closure in JavaScript.

When we are comparing objects - we are comparing just reference in memory.

What is a Hoisting?

Spoiler alert: Word Hoisting is a bit a magic word. JS Engine does not move anything to the top of file while parsing it. So, there is no such thing as a hoisting...?

wth_joey

There is a term called hoisting, but it doesn't mean that something changing its position, we will return to the hoisting definition a bit later in this post, but for now, let's consider the next example.


Which statements are correct?

  • a. Hoisting is a process of moving functions and variables to the top of the file
  • b. Variables declared with the let and const are hoisted
  • c. Variables declared with the var keyword are uninitialized
  • d. Hoisting occurred during the execution phase
  • e. import declarations are hoisted
Answer

b and e

When the JS engine starts to work with any .js files it makes an environment called the Execution Context and it has two phases:

  1. Creation phase
  2. Execution phase

During the creation phase JS engine:

  • Creates a global object: window/global
  • Sets up memory for storing variables and functions.
  • Stores the variables with the var keyword with default value undefined and function declaration name with the function reference.

During the execution phase:

  • JavaScript engine executes the code line by line. This includes evaluating and executing statements.

Hoisting examples

const framework = "Next.js";
let renderingPattern = "SSR";
var bestProgrammingLanguage = "HTML๐Ÿ˜";

During the creation phase, all those variables except var bestProgrammingLanguage gonna be Uninitialized and only variables with var keyword will have an initial value as undefined, during the execution phase, all variables gonna be assigned to corresponding values.

Please take a look at the one more example below:

var counter = 1;

function resetCounter() {
    console.log(counter);   // undefined
    var counter = 0;
    console.log(counter);   // 0
};

resetCounter();

One more example with error:

let counter = 1;

function resetCounter() {
    console.log(counter);
    const counter = 0;
    console.log(counter);
};

resetCounter();

As you may guess above code gonna throws an error Uncaught ReferenceError: Cannot access 'counter' before initialization.

All JS code runs line by line during the execution phase and when we are trying to log console.log(counter); JS engine throws an error - that counter isn't initialized - this means that counter variable is so-called hoisted - it already exists in our Function lexical environment, otherwise error would be totally different. Check image below:

hoist_err

So shortly - Hoisting it's a process of setting the default value to the corresponding declaration during the creation phase.

What is a this keyword?

this_unbelievable

The value of this keyword depends on where we are using it.
Global context - when this is used in a global context the value of this gonna be a global object.
Function context - when this is used in function context the value of this gonna be the object on which the function is invoked.
Arrow function - when this is used in arrow function the value of this gonna be determined by the lexical environment in which the arrow function is defined.
Classes - when this is used in classes the value of this gonna be an instance of that class.
Strict mode - when this is pointing to a global object in strict mode the value of this gonna be undefined.

this examples

Would you be able to answer all of these questions correctly?

#0_this.js
function printContext() {
    console.log(this);
}

const thatObj = {
    printContext,
    printContext_1() {
        return printContext();
    },
    printContext_2() {
        return thatObj.printContext();
    },
};

thatObj.printContext();
thatObj.printContext_1();
thatObj.printContext_2();
Answer

thatObj, global, thatObj

  • thatObj.printContext - printContext function was assigned to the printContext props of thatObj, and we called body of this function in the context of the thatObj.
  • thatObj.printContext_1 - is just the return result of printContext function, so nothing related to thatObj.
  • thatObj.printContext_2 - is the same as this.printContext().
#1_this.js
const thatObj_1 = {
    foo() {
        console.log(this);
    },
    bar: () => {
        console.log(this);
    },
};

const thatObj_2 = {
    foo: thatObj_1.foo,
    bar: () => thatObj_1.bar(),
    baz() {
        thatObj_1.foo();
    },
    bam() {
        thatObj_1.foo.call(this);
    },
    bah() {
        const nestedF = () => {
            console.log(this);
        };
        nestedF();
    },
};

thatObj_2.foo();
thatObj_2.bar();
thatObj_2.baz();
thatObj_2.bam();
thatObj_2.bah();
Answer

thatObj_2, global, thatObj_1, thatObj_2, thatObj_2

  • thatObj_2.bam - we called foo method with this of thatObj_2. In case of object methods, it doesn't matter where object method is defined - it only matters how it's invoked.
  • thatObj_2.bah - in this case this will be pointing to the parent scope, which is thatObj_2.
#2_this.js
const thatObj = {
    printContext() {
        console.log(this);
    },
    printContext_1() {
        function nestedF() {
            console.log(this);
        };
        nestedF.apply(this);
    },
};

const { printContext, printContext_1 } = thatObj;

printContext();
printContext_1();
thatObj.printContext();
thatObj.printContext_1();
Answer

global, global, thatObj, thatObj

  • printContext() and printContext_1() - when those methods were destructured, the value of this was pointed to the context in which these methods were called, in this case, global object.

Is it possible to iterate through an object using Array.forEach method?

Yes! We can just call it๐Ÿ˜Š.

const arrayLike = {
  length: 3,
  0: 2,
  1: 3,
  2: 4,
  3: 5, // ignored by forEach() since length is 3
};

Array.prototype.forEach.call(arrayLike, (x) => console.log(x)); // 2, 3, 4

Thanks for reading, I hope this post was useful and a bit entertaining. See Ya!๐Ÿ‘‹

thanks_monica