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 as10
.
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
- What will be the output in the console?
- Why do we set
Dog.prototype.constructor = Dog
after updatingDog.prototype
?
- When we set
Dog.prototype
toObject.create(Animal.prototype)
, we're making sure thatDog
instances inherit fromAnimal
. However, this operation also overwrites theconstructor
property ofDog.prototype
toAnimal
.
- So, to correct this, we set
Dog.prototype.constructor
back toDog
. This ensures that theconstructor
property correctly points to theDog
function, which is useful for introspection or if we need to create a new instance from an existing instance.
3. What do the instanceof
checks at the end demonstrate?
- If you don’t add the line
Dog.prototype.constructor = Dog
, then theconstructor
property of the prototype objectDog.prototype
will point toAnimal
instead ofDog
. - This is because you've assigned a new object created with
Animal.prototype
toDog.prototype
, so you've inheritedAnimal.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());
- 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 theCar
prototype, not theVehicle
prototype. Thus, instances ofVehicle
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 ofVehicle
used for the prototype. This can lead to unexpected shared state amongCar
instances. A better approach is to useObject.create(Vehicle.prototype)
to set up inheritance without creating an instance ofVehicle
.
- 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());
- 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 theCoffee
constructor to inherit the properties of theBeverage
constructor.- By using
.call()
, we can execute theBeverage
function within the context of theCoffee
instance, ensuring that thetype
property of theBeverage
is correctly set on theCoffee
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 theconstructor
property ofCoffee.prototype
will point toBeverage
and notCoffee
. This can be misleading when checking the constructor of aCoffee
instance and can lead to unintended behaviors