JavaScript | Tricky Output | SCOPE | IIFE | Prototypal Inheritance

Have you guessed it?

.

.

.

Let’s see…

Answer

The following code will write the value 10 times.

What if I want values from 0-9 in the console?

There are different ways to achieve this.

By using IIFE

  • IIFE is Immediately Invoked Function Expressions.
  • By using IIFE’s we can also scope the value of the variable i to print the current index, instead of printing the final value as 10.

There is another way to fix this issue using ES6 syntax

Promise

1.

console.log("1");

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

Promise.resolve().then(() => {
console.log("3");
}).then(() => {
console.log("4");
});

console.log("5");

OUTPUT:
1
5
3
4
2

2.

function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Data fetched!"), 2000);
});
}

async function logData() {
console.log("Fetching data...");
console.log(await fetchData());
console.log("Done!");
}

logData();

OUTPUT:
Fetching data...
// Waits for 2 seconds
Data fetched!
Done!

3.

async function getResult() {
return 'Hello World';
}

const result = getResult();
console.log(result);



OUTPUT
/** The output will be Promise {<fulfilled>: "Hello World"}.
This is because async functions always return a promise.
To get the actual value 'Hello World',
one would need to either await the getResult function or
use .then() on the result. **/

Closure | IIFE | Object


for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}


OUTPUT:
3
3
3



// To make correct output: change in below code

// ADD IIFE
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(() => {
console.log(index);
}, 0);
})(i);
}

or

// ADD let
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}


OUTPUT:
0
1
2

2.


function createFunctions() {
var functions = [];
for (var i = 0; i < 5; i++) {
functions.push(function() {
return i;
});
}
return functions;
}

var funcs = createFunctions();

console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());


OUTPUT:
5
5
5


// add let
function createFunctions() {
var functions = [];
for (let i = 0; i < 5; i++) {
functions.push(function() {
return i;
});
}
return functions;
}

// make code change - ADD IIFE
function createFunctions() {
var functions = [];
for (var i = 0; i < 5; i++) {
(function(index){
functions.push(function() {
return index;
});
})(i)
}
return functions;
}

var funcs = createFunctions();

console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());


// add IIFE
function createFunctions() {
var functions = [];
for (var i = 0; i < 5; i++) {
functions.push((function(index) {
return function() {
return index;
};
})(i));
}
return functions;
}

OUTPUT
0
1
2

3.

const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = [];

numbers.forEach(function(n) {
setTimeout(() => {
doubledNumbers.push(n * 2);
}, 0);
});

console.log(doubledNumbers);

// *********************************

OUTPUT
[]


Explanation:
/** Before any of the setTimeout callbacks have had a chance to execute,
console.log(doubledNumbers); runs,
logging the current state of the doubledNumbers array,
which is an empty array. **/

4.

let a = { greeting: 'Hello' };
let b = a;
a.greeting = 'Hola';

console.log(b.greeting);


OUTPUT:
Hola
  • Objects and arrays are assigned by reference, not by value. When we assign an object to another variable, we’re essentially passing around a reference to the same memory location, not creating a fresh copy of the object.

5.


const obj = {
value: "Hello",
print: function() {
console.log(this.value);

function innerFunction() {
console.log(this.value);
}
innerFunction();
}
};

obj.print();


OUTPUT:
Hello
Undefined

7.

function tricky() {
return
{
message: "Hello, World!"
};
}

const output = tricky();
console.log(output);


OUTPUT:
undefined

8.

var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
return index;
}
})(i);
}

console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());



OUTPUT
0
1
2

Prototypal Inheritance

Super Knowledgable!

1.


function Animal(name) {
this.name = name;
}

Animal.prototype.sayName = function() {
console.log("My name is " + this.name);
};

function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
console.log("Woof!");
};

const rex = new Dog("Rex", "Golden Retriever");

rex.sayName();
rex.bark();

console.log(rex instanceof Dog);
console.log(rex instanceof Animal);



OUTPUT:
My name is Rex
Woof!
true
true


console.log(rex.constructor === Animal); // false
console.log(rex.constructor === Dog); // true
  1. What will be the output in the console?
  2. Why do we set Dog.prototype.constructor = Dog after updating Dog.prototype?
  • When we set Dog.prototype to Object.create(Animal.prototype), we're making sure that Dog instances inherit from Animal. However, this operation also overwrites the constructor property of Dog.prototype to Animal.
Dog.prototype.contructor pointing to Animal
  • So, to correct this, we set Dog.prototype.constructor back to Dog. This ensures that the constructor property correctly points to the Dog function, which is useful for introspection or if we need to create a new instance from an existing instance.
Dog.prototype.constructor points to Dog

3. What do the instanceof checks at the end demonstrate?

  • If you don’t add the line Dog.prototype.constructor = Dog, then the constructor property of the prototype object Dog.prototype will point to Animal instead of Dog.
  • This is because you've assigned a new object created with Animal.prototype to Dog.prototype, so you've inherited Animal.prototype's constructor property.

console.log(rex.constructor === Animal); // true
console.log(rex.constructor === Dog); // false


const anotherDog = new rex.constructor("Buddy");
console.log(anotherDog instanceof Dog); // false
console.log(anotherDog instanceof Animal); // true

2.


function Vehicle(type) {
this.type = type;
}

Vehicle.prototype.describe = function() {
return `This is a ${this.type}`;
};

function Car(model) {
this.model = model;
}

Car.prototype = new Vehicle("Car");

Car.prototype.describeModel = function() {
return `${this.describe()} of model ${this.model}`;
};

const myCar = new Car("Sedan");

console.log(myCar.describe());
console.log(myCar.describeModel());
  1. What will be the output in the console?
This is a Car
This is a Car of model Sedan

2. If we create a new instance of Vehicle with const vehicle = new Vehicle("Bike"), will it have access to the describeModel method? Why or why not?

  • NO, This is because the method describeModel is defined on the Car prototype, not the Vehicle prototype. Thus, instances of Vehicle do not have this method in their prototype chain.

3. How does the above code implement inheritance? What could be a potential issue with this approach?

  • A potential issue with this approach is that every instance of Car will inherit properties that are set on the instance of Vehicle used for the prototype. This can lead to unexpected shared state among Car instances. A better approach is to use Object.create(Vehicle.prototype) to set up inheritance without creating an instance of Vehicle.
  • Car.prototype = new Vehicle(“Car”); This approach is generally considered less ideal because of the issues mentioned above. It leads to unexpected shared state among instances, and constructors shouldn’t be called unless you’re trying to create an instance of that class.

3.

function Beverage(type) {
this.type = type;
}

Beverage.prototype.describe = function() {
return `This is a refreshing ${this.type}`;
};

function Coffee(type, roast) {
Beverage.call(this, type); //why we need this call? type property of the Beverage is correctly set on the Coffee instance.
this.roast = roast;
}

Coffee.prototype = Object.create(Beverage.prototype); //why we need this call ?
Coffee.prototype.constructor = Coffee; //why we need this call ?

Coffee.prototype.flavor = function() {
return `${this.describe()} with a ${this.roast} roast.`;
};

const espresso = new Coffee("coffee", "dark");
console.log(espresso.flavor());
  1. What will be the output in the console?
This is a refreshing coffee with a dark roast.

2. Why is Beverage.call(this, type); necessary inside the Coffee function?

  • Beverage.call(this, type); is crucial because it allows the Coffee constructor to inherit the properties of the Beverage constructor.
  • By using .call(), we can execute the Beverage function within the context of the Coffee instance, ensuring that the type property of the Beverage is correctly set on the Coffee instance.

3. If we omit the line Coffee.prototype.constructor = Coffee; after setting the prototype with Object.create, what could be a potential drawback or confusion later in the code?

  • If you omit the line Coffee.prototype.constructor = Coffee;, then the constructor property of Coffee.prototype will point to Beverage and not Coffee. This can be misleading when checking the constructor of a Coffee instance and can lead to unintended behaviors

--

--