Share 8 JavaScript interview questions about advanced front-end

84b0d057f3bc8ac40600fca3beee1e8d.jpeg

English | https://levelup.gitconnected.com/8-advanced-javascript-interview-questions-for-senior-roles-c59e1b0f83e1

JavaScript is a powerful language and one of the main building blocks of the web. This powerful language also has some quirks. For example, did you know that 0 === -0 evaluates to true, or that Number("") evaluates to 0?

The problem is, sometimes these quirks can leave you scratching your head and questioning the day Brendon Eich invented JavaScript. Well, the point is not that JavaScript is a bad programming language, or that it's evil, as its detractors say. All programming languages ​​have some kind of quirk associated with them, and JavaScript is no exception.

So, in today's article, we will see an in-depth explanation of some important JavaScript interview questions. My goal is to explain these interview questions thoroughly so that we can understand the basic concepts and hopefully address other similar questions in the interview.

1. Carefully observe the + and - operators

console.log(1 + '1' - 1);

Can you guess how JavaScript's + and - operators behave in the above situation?

When JavaScript encounters 1 + '1', it processes the expression with the + operator. An interesting property of the + operator is that it prefers string concatenation when one of its operands is a string. In our case, "1" is a string, so JavaScript implicitly coerces the value 1 to a string. Therefore, 1 + '1' becomes '1' + '1', which results in the string '11'.

Now, our equation is '11' - 1. The - operator behaves exactly the opposite. It gives preference to numeric subtraction regardless of the types of the operands. When the operand is not of numeric type, JavaScript performs an implicit cast, converting it to a number. In this example, "11" is converted to the number 11, and the expression simplifies to 11 - 1.

Putting it all together:

'11' - 1 = 11 - 1 = 10

2. Copy the array elements

Consider the following JavaScript code and try to find any problems in this code:

function duplicate(array) {
  for (var i = 0; i < array.length; i++) {
    array.push(array[i]);
  }
  return array;
}


const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

In this code snippet, we need to create a new array containing the repeated elements of the input array. Upon initial inspection, the code appears to create a new array, newArr, by duplicating each element in the original array arr. However, a key problem arises with the repeating function itself.

The repeat function uses a loop to iterate through each item in a given array. But inside the loop, it uses the push() method to add a new element at the end of the array. This makes the array grow longer each time, creating the problem that the loop never stops. The loop condition (i < array.length) always remains true because the array keeps getting bigger. This makes the loop go on forever, causing the program to get stuck.

In order to solve the problem that the length of the array keeps growing and leads to an infinite loop, you can store the initial length of the array in a variable before entering the loop.

You can then use that initial length as a limit for loop iterations. That way, the loop will only run against the original elements in the array, and won't suffer from the growth of the array due to adding duplicates. Here's a modified version of the code:

function duplicate(array) {
  var initialLength = array.length; // Store the initial length
  for (var i = 0; i < initialLength; i++) {
    array.push(array[i]); // Push a duplicate of each element
  }
  return array;
}


const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

The output will show repeated elements at the end of the array, and the loop will not cause an infinite loop:

[1, 2, 3, 1, 2, 3]

3. The difference between prototype and __proto__

Prototype properties are properties related to constructors in JavaScript. Constructors are used to create objects in JavaScript. When defining a constructor, you can also attach properties and methods to its prototype properties.

These properties and methods are then accessible to all instances of the object created from that constructor. Thus, the prototype property acts as a common repository for methods and properties shared between instances.

Consider the following code snippet:

// Constructor function
function Person(name) {
  this.name = name;
}


// Adding a method to the prototype
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};


// Creating instances
const person1 = new Person("Haider Wain");
const person2 = new Person("Omer Asif");


// Calling the shared method
person1.sayHello();  // Output: Hello, my name is Haider Wain.
person2.sayHello();  // Output: Hello, my name is Omer Asif.

In this example, we have a constructor named Person. By extending Person.prototype with a method like sayHello, we add this method to the prototype chain of all Person instances. This allows each instance of Person to access and utilize shared methods. Instead of each instance having its own copy of the method.

On the other hand, the __proto__ property (often pronounced "dunder proto") exists on every JavaScript object. In JavaScript, everything except primitive types can be considered an object. Each of these objects has a prototype that is used as a reference to another object. The __proto__ attribute is just a reference to this prototype object. The prototype object is used as a fallback source for properties and methods when the original object does not have them. By default, when you create an object, its prototype is set to Object.prototype.

When you try to access a property or method of an object, JavaScript follows a lookup process to find it. This process involves two main steps:

Object's own properties: JavaScript first checks whether the object itself directly owns the desired property or method. If the property is found in the object, it is accessed and used directly.

Prototype chain lookup: If the property is not found in the object itself, JavaScript looks at the object's prototype (referenced by the __proto__ attribute) and searches for the property there. This process continues recursively up the prototype chain until a property is found or a lookup reaches Object.prototype.

If the property is not found even in Object.prototype, JavaScript will return undefined, indicating that the property does not exist.

4. Scope

When writing JavaScript code, it's important to understand the concept of scope. Scope refers to the accessibility or visibility of variables in different parts of the code. Before continuing with the example, if you are not familiar with hoisting and how JavaScript code is executed, you can learn about it from this link. This will help you understand how the JavaScript code works in more detail.

Let's take a closer look at the code snippet:

function foo() {
    console.log(a);
}


function bar() {
    var a = 3;
    foo();
}


var a = 5;
bar();

The code defines 2 functions foo() and bar() and a variable a with value 5. All of these declarations happen at the global scope. Inside the bar() function, a variable a is declared and assigned the value 3. So when thebar() function is called, what do you think it will print the value of a?

When the JavaScript engine executes this code, declare a global variable a and assign it the value 5. Then, call the bar() function. Inside the bar() function, a local variable a is declared and assigned the value 3. The local variable a is different from the global variable a. After that, the foo() function is called from within the bar() function.

Inside the foo() function, the console.log(a) statement attempts to log the value of a. Since the local variable a is not defined within the scope of the foo() function, JavaScript looks up the scope chain to find the closest variable named a. The scope chain refers to all the different scopes that a function can access when trying to find and use a variable.

Now, let's address where JavaScript will search for the variable a. Will it look in the scope of the bar function, or will it explore the global scope? It turns out that JavaScript will search in the global scope, and this behavior is driven by a concept called lexical scope.

Lexical scoping refers to the scope of a function or variable as it is written in the code. When we defined the foo function, it was granted access to its own local and global scopes. This feature remains the same no matter where we call the foo function, whether it's inside the bar function or if we export it to another module and run it there. Lexical scope is not determined by where we call the function.

The effect of this is that the output is always the same: the value of a found in the global scope, in this case 5.

However, if we define the foo function inside the bar function, a different situation occurs:

function bar() {
  var a = 3;


  function foo() {
    console.log(a);
  }


  foo();
}


var a = 5;
bar();

In this case, foo 's lexical scope would contain three distinct scopes: its own local scope, the scope of the bar function, and the global scope. Lexical scope is determined by where in the source code the code is placed at compile time.

When this code runs, foo is inside the bar function. This arrangement changes the range dynamics. Now, when foo tries to access the variable a, it will first search in its own local scope. Since it can't find a there, it extends its search to the scope of the bar function. Lo and behold, a exists with a value of 3. Therefore, the console statement will print 3.

5. Object coercion

const obj = {
  valueOf: () => 42,
  toString: () => 27
};
console.log(obj + '');

An interesting aspect worth exploring is how JavaScript handles the conversion of objects to primitive values ​​such as strings, numbers, or booleans. This is an interesting question, testing whether you know how casts work with objects.

This conversion is crucial when working with objects in scenarios such as string concatenation or arithmetic operations. To achieve this, JavaScript relies on two special methods: valueOf and toString.

The valueOf method is a fundamental part of the JavaScript object transformation mechanism. When an object is used in a context that expects a primitive value, JavaScript first looks for the valueOf method in the object.

If the valueOf method does not exist or does not return an appropriate primitive value, JavaScript will fall back to the toString method. This method is responsible for providing the string representation of the object.

Back to our original code snippet:

const obj = {
  valueOf: () => 42,
  toString: () => 27
};


console.log(obj + '');

When we run this code, the object obj is converted to primitive value. In this case, the valueOf method returns 42, which is then implicitly converted to a string due to concatenation with the empty string. Therefore, the output of the code will be 42.

However, JavaScript will fall back to the toString method if the valueOf method does not exist or does not return an appropriate primitive value. Let's modify the previous example:

const obj = {
  toString: () => 27
};


console.log(obj + '');

Here, we've removed the valueOf method, leaving only the toString method, which returns the number 27. In this case, JavaScript will resort to the toString method for object conversion.

6. Understanding Object Keys

When working with objects in JavaScript, it's important to understand how keys are handled and assigned in the context of other objects. Consider the following code snippet and spend some time guessing the output:

let a = {};
let b = { key: 'test' };
let c = { key: 'test' };


a[b] = '123';
a[c] = '456';


console.log(a);

At first glance, this code seems like it should generate an object a with two distinct key-value pairs. However, due to the way JavaScript handles object keys, the results are quite different.

JavaScript uses the default toString() method to convert object keys to strings. but why? In JavaScript, object keys are always strings (or symbols), or they are automatically converted to strings via an implicit cast. When you use any value other than a string (such as a number, object, or symbol) as a key in an object, JavaScript internally converts the value to its string representation before using it as a key.

So when we use objects b and c as keys in object a, both are converted to the same string representation: [object Object]. Because of this behavior, the second assignment a[b] = '123'; will override the first assignment a[c] = '456';.

Now, let's break down the code step by step:

  • let a = {};: Initialize an empty object a.

  • let b = { key: 'test' };: Create an object b whose property key is 'test'.

  • let c = { key: 'test' };: define another object c with the same structure as b.

  • a[b] = '123';: Set the property with key [object Object] in object a to the value '123'.

  • a[c] = '456';: Update the value of the same attribute of the key [object Object] in object a to '456', replacing the previous value.

Both assignments use the same key string [object Object]. As a result, the second assignment overrides the value set by the first assignment.

When we log object a, we observe the following output:

{ '[object Object]': '456' }

7. == operator

console.log([] == ![]);

This one is a bit more complicated. So what do you think the output will be? Let's evaluate it step by step. Let's first look at the types of the two operands:

typeof([]) // "object"
typeof(![]) // "boolean"

For [] it is an object, which is understandable. Everything in JavaScript is an object, including arrays and functions. But how can the operand ![] have type Boolean? Let's try to understand this. When you use ! for primitive values, the following conversion occurs:

False: If the original value is a false value (such as false, 0, null, undefined, NaN, or the empty string ''), applying ! will convert it to true.

true-value: If the original value is a true value (any non-false value), then apply! will convert it to false.

In our case, [] is an empty array, which is a true value in JavaScript. Since [] is true, ![] becomes false. So, our expression becomes:

[] == ![]
[] == false

Now let's move on to the == operator. JavaScript performs an abstract equality comparison algorithm whenever 2 values ​​are compared using the == operator. 

The algorithm has the following steps:

6aedf5d74cfce24fe2e17b79dd0afb25.png

As you can see, the algorithm takes into account the types of the compared values ​​and performs the necessary conversions.

For our example, we denote x as [] and y as ![]. We checked the types of x and y and found that x is an object and y is a boolean. Since y is a boolean and x is an object, condition 7 in the abstract equality comparison algorithm applies:

If Type(y) is Boolean, returns the comparison result of x == ToNumber(y).

This means that if one of the types is a boolean, we need to convert it to a number before comparing. What is the value of ToNumber(y)? As we can see, [] is a truth value, negation makes it false. As a result, Number(false) is 0.

[] == false
[] == Number(false)
[] == 0

Now we have the comparison[] == 0, this time condition 8 comes into play:

If Type(x) is String or Number and Type(y) is Object, return the comparison x == ToPrimitive(y).

Based on this condition, if one of the operands is an object, we have to convert it to a primitive value. This is where the ToPrimitive algorithm comes into play. We need to convert []x to a primitive value. Arrays are objects in JavaScript. As we saw earlier, the valueOf and toString methods come into play when converting an object to a primitive. 

In this case, valueOf returns the array itself, which is not a valid primitive value. So we turn to toString for output. Applying the toString method to an empty array results in an empty string, which is a valid primitive:

[] == 0
[].toString() == 0
"" == 0

Converting an empty array to a string results in an empty string "", now we face the comparison: "" == 0.

Now, one of the operands is of type string and the other is of type number, then condition 5 holds:

If Type(x) is String and Type(y) is Number, return the comparison ToNumber(x) == y.

Therefore, we need to convert the empty string "" to a number, which is 0.

"" == 0
ToNumber("") == 0
0 == 0

Finally, both operands have the same type and condition 1 is true. Since both have the same value, the final output is:

0 == 0 // true

The last few questions we've explored so far have used casting, which is an important concept for mastering JavaScript and for tackling these kinds of questions in interviews, which tend to be asked a lot. 

8. Closure

This is one of the most famous interview questions related to closures:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

It's good if you know the output. So, let's try to understand this snippet. On the surface, this snippet of code will give us the following output:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

But that's not the case here. The actual output will vary due to the concept of closures and the way JavaScript handles variable scope. When the setTimeout callbacks execute after a delay of 3000 milliseconds, they will both refer to the same variable i, which will have a final value of 4 after the loop completes. As a result, the output of the code will be:

Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined

This behavior occurs because the var keyword does not have block scope, and the setTimeout callback captures a reference to the same i variable. When the callbacks execute, they all see the final value of i, which is 4, and try to access arr[4] which is undefined.

To achieve the desired output, you can use the let keyword to create a new scope for each iteration of the loop, ensuring that each callback captures the correct value for i:

const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

With this modification, you'll get the expected output:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

Using let creates a new binding for i on each iteration, ensuring each callback refers to the correct value.

Often, developers are already familiar with solutions involving the let keyword. However, interviews sometimes go a step further and challenge you to solve problems without using let. Another approach in this case is to create a closure by immediately calling a function (IIFE) inside the loop. This way, each function call has its own copy of i. You can do this:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  (function(index) {
    setTimeout(function() {
      console.log('Index: ' + index + ', element: ' + arr[index]);
    }, 3000);
  })(i);
}

In this code, the immediately called function (function(index) { ... })(i); creates a new range for each iteration, capturing the current value of i and passing it as the index parameter. This ensures that each callback function has its own separate index value, preventing issues related to closures and giving you the expected output:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

final summary

The above are the 8 front-end interview questions about JS that I want to share with you in this article today. I hope this article will be helpful to your interview preparation journey. 

If you still have any questions, please leave us a message in the message area, and we will exchange learning progress together.

Finally, thank you for reading and following us, you will receive more high-quality articles.

Fan benefits

Share a NodeJs background template source code based on Tailwind Css, if you want to learn nodejs and tailwindcss, don't miss this source code.

learn more skills

Please click the public number below

6040a5181fe547da96563d41de21b6eb.gif

Guess you like

Origin blog.csdn.net/Ed7zgeE9X/article/details/132702997