DEV Community

Cover image for ๐Ÿ”ฅ JavaScript Interview Series(3): Mastering โ€œthisโ€, call, apply, and bind
jackma
jackma

Posted on

๐Ÿ”ฅ JavaScript Interview Series(3): Mastering โ€œthisโ€, call, apply, and bind

1. What does the this keyword refer to in JavaScript?

Core Concept: Understanding the dynamic nature of this and its different bindings.

Standard Answer: In JavaScript, the this keyword is a special identifier that is automatically defined in the scope of every function. Its value is determined by how a function is called, which is known as its "execution context." It doesn't refer to the function itself, but rather to the object that the function is a method of.

  • In the global context (outside of any function), this refers to the global object, which is window in a browser or global in Node.js.
  • When a function is called as a method of an object (object.myMethod()), this is bound to the object the method is called on.
  • When a function is called as a standalone function (not attached to an object), this will default to the global object in non-strict mode, or be undefined in strict mode.
  • With arrow functions, this is lexically bound, meaning it inherits the this value from its surrounding (enclosing) scope at the time of its creation.
  • When used with call, apply, or bind, this is explicitly set to the object passed as the first argument.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. How does "strict mode" ('use strict') affect the this keyword?
  2. Can you explain the difference between dynamic scope (how this works) and lexical scope?
  3. What happens to this inside a function that is used as an event handler?

2. What is the output of the following code and why?

const user = {
  name: 'John Doe',
  greet: function() {
    console.log(`Hello, I'm ${this.name}`);
  },
  farewell: () => {
    console.log(`Goodbye from ${this.name}`);
  }
};

user.greet();
user.farewell();
Enter fullscreen mode Exit fullscreen mode

Core Concept: Differentiating this in a regular function versus an arrow function within an object.

Standard Answer:

  • user.greet() will output: "Hello, I'm John Doe". This is because greet is a regular function and it's called as a method of the user object. Therefore, this inside greet refers to the user object itself.
  • user.farewell() will output: "Goodbye from undefined" (or an empty string, depending on the environment). This is because farewell is an arrow function. Arrow functions do not have their own this binding; they inherit this from their parent scope. In this case, the parent scope is the global scope where this.name is not defined.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. How could you modify the farewell method to correctly print "John Doe" while still keeping it as a property on the user object?
  2. If this code were running inside another function, what would this refer to for the farewell function?
  3. What if we assigned greet to a new variable and then called it? For example: const sayHello = user.greet; sayHello(); What would be the output?

3. What are call and apply, and what is the main difference between them?

Core Concept: Understanding explicit this binding and how arguments are passed.

Standard Answer: Both call and apply are methods that exist on all JavaScript functions. They allow you to invoke a function and explicitly specify the this context for that function call. This means you can "borrow" a function and have it execute in the context of an object it wasn't originally defined on.

The main difference lies in how they handle function arguments:

  • .call(thisArg, arg1, arg2, ...) accepts the this context as its first argument, followed by a comma-separated list of arguments for the function it's invoking.
  • .apply(thisArg, [arg1, arg2, ...]) accepts the this context as its first argument, and the second argument must be an array (or an array-like object) containing the arguments for the function.

A simple mnemonic is: C for call and comma, A for apply and array.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. Can you give a practical example where apply would be more convenient than call? (Hint: think about dynamic arguments).
  2. What happens if you pass null or undefined as the first argument to call or apply?
  3. Before the spread syntax (...), how was apply commonly used with arrays for functions like Math.max()?

4. What is bind and how does it differ from call and apply?

Core Concept: Explaining how bind creates a new function with a permanently bound this.

Standard Answer: The .bind() method also allows you to set the this value for a function. However, unlike call and apply which execute the function immediately, .bind() returns a new function with the this keyword permanently bound to the provided value. This new function can be called later.

The key differences are:

  • Execution: call and apply invoke the function immediately. bind returns a new function that you can execute later.
  • Arguments: You can pass arguments to bind just like you do with call (a comma-separated list). This is known as "partial application" or "currying," where you pre-fill some of the arguments.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. Can you provide a common use case for bind, for example, with event listeners or in React class components?
  2. Once a function has been bound, can you change its this context again using call or apply? Why or why not?
  3. How could you implement your own simple version of the bind function?

5. Explain the output of this code snippet:

var name = "Global";
function printName() {
  console.log(this.name);
}

const obj1 = {
  name: "Object 1",
  print: printName
};

const obj2 = {
  name: "Object 2",
  print: printName
};

const boundPrint = printName.bind(obj1);

boundPrint();
obj2.print.call(obj1);
Enter fullscreen mode Exit fullscreen mode

Core Concept: Testing the understanding of method calls, explicit binding with bind and call, and binding precedence.

Standard Answer:

  • boundPrint() will output "Object 1". This is because bind creates a new function where this is permanently set to obj1. When we call boundPrint(), it executes printName with that pre-configured context.
  • obj2.print.call(obj1) will also output "Object 1". Although we are accessing the function through obj2, the .call(obj1) method immediately invokes it and explicitly sets the this context to obj1, overriding the default context of obj2.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. What would be the output if we just called obj2.print()?
  2. What if we tried this: const newBound = boundPrint.bind(obj2); newBound(); What would it log and why?
  3. How does the concept of "binding precedence" rank bind vs. call/apply?

6. How can you use call or apply to borrow methods from other objects?

Core Concept: Demonstrating practical application of explicit binding for code reuse.

Standard Answer: You can borrow methods by using call or apply to invoke a method from one object on another object that doesn't have that method. A classic example is using array methods on arguments, which is an array-like object but not a true array.

For example, to use the slice method on the arguments object to convert it into a real array:

function list() {
  // arguments is not a real array, so it doesn't have .slice()
  // We "borrow" slice from Array.prototype
  const argsArray = Array.prototype.slice.call(arguments);
  return argsArray;
}
console.log(list(1, 2, 3)); // [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Here, we are calling the slice method and telling it that its this should be the arguments object, allowing it to work as intended.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. Can you show another example, perhaps borrowing forEach to iterate over a NodeList from document.querySelectorAll?
  2. What makes an object "array-like"? What properties must it have for this to work?
  3. Besides arguments and NodeList, can you name other array-like objects in JavaScript?

7. What is "binding precedence" in JavaScript?

Core Concept: Ranking the different ways this can be determined.

Standard Answer: Binding precedence is the set of rules JavaScript uses to determine the value of this when multiple rules could apply. The order of precedence, from highest to lowest, is:

  1. new binding: When a function is called with the new keyword (as a constructor), this is a newly created object. This has the highest precedence.
  2. Explicit binding: When a function is called using call, apply, or bind, this is explicitly set to the object passed as the first argument.
  3. Implicit binding: When a function is called as a method of an object (obj.method()), this is bound to that object.
  4. Default binding: If none of the above rules apply, this defaults to the global object (window in browsers) in non-strict mode, or undefined in strict mode.

Note: Arrow functions are an exception; they don't follow these rules and instead take the this of their lexical parent scope.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. Can you write a piece of code that demonstrates new binding taking precedence over bind?
  2. Where do arrow functions fit into this hierarchy?
  3. Explain why obj.method.call(anotherObj) is an example of explicit binding overriding implicit binding.

8. What is a hard-binding and how can you achieve it?

Core Concept: Understanding how to create a function that is permanently locked to a specific this context.

Standard Answer: Hard-binding is the process of creating a function that, no matter how it's called, is always bound to a specific this context. The most common and modern way to achieve this is by using the .bind() method.

When you use func.bind(obj), it creates a new function that is permanently "hard-bound" to obj. Any subsequent attempts to change its this context using call, apply, or even implicit binding will be ignored.

Here is an example:

function sayName() {
  console.log(this.name);
}
const user = { name: "Alice" };
const otherUser = { name: "Bob" };

const boundSayName = sayName.bind(user);
boundSayName(); // Logs "Alice"
boundSayName.call(otherUser); // Still logs "Alice"
Enter fullscreen mode Exit fullscreen mode

The hard-binding to user cannot be overridden.

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. Before .bind() was introduced in ES5, how might a developer have created a hard-bound function? (Hint: using a closure).
  2. Is an arrow function a form of hard-binding? Explain your reasoning.
  3. Can you use hard-binding to create a function that ignores the this from a new keyword instantiation?

9. How does this work with constructor functions?

Core Concept: Explaining the new binding rule.

Standard Answer: When a function is called using the new keyword, it acts as a constructor. JavaScript does four things automatically:

  1. A brand new, empty object is created.
  2. This new object is linked to the function's prototype.
  3. The this keyword inside the constructor function is bound to this newly created object.
  4. If the function does not explicitly return another object, it implicitly returns this (the new object).

So, inside a constructor function, this refers to the new instance of the object being created.

function Car(make) {
  // `this` here refers to the new object being created
  this.make = make;
  this.isRunning = false;
}
const myCar = new Car('Honda'); // `this` inside Car was bound to myCar
console.log(myCar.make); // "Honda"
Enter fullscreen mode Exit fullscreen mode

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. What happens if you forget to use the new keyword when calling a constructor function?
  2. What happens if you explicitly return a primitive value (like a string or number) from a constructor?
  3. What happens if you explicitly return an object from a constructor?

10. Can you implement your own version of Function.prototype.bind?

Core Concept: Demonstrating a deep understanding of closures, this, and apply by creating a polyfill.

Standard Answer: Yes. A custom bind function needs to do three things:

  1. Accept a context (thisArg) and return a new function.
  2. Capture any arguments passed during the binding phase ("partial application").
  3. When the new function is called, it should invoke the original function with the bound context and a combination of the bound arguments and any new arguments.

Here's a simplified implementation:

Function.prototype.myBind = function(context, ...boundArgs) {
  const originalFunc = this; // 'this' here is the function myBind is called on

  // Return a new function
  return function(...newArgs) {
    // When the new function is called, invoke the original
    // with the saved context and all arguments.
    return originalFunc.apply(context, [...boundArgs, ...newArgs]);
  };
};

// Example Usage:
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'Sarah' };
const greetSarah = greet.myBind(person, 'Hello');
greetSarah('!'); // Outputs: "Hello, Sarah!"
Enter fullscreen mode Exit fullscreen mode

Possible Follow-up Questions:
๐Ÿ‘‰ (Want to test your skills? Try a Mock Interviews)

  1. My implementation uses apply and spread syntax. How could you achieve the same result without them?
  2. A complete polyfill for bind also needs to handle being used with the new operator. How would you add that functionality?
  3. What are the advantages of creating polyfills like this?

Top comments (0)