I can give you two examples that will explain why it is important. Let's say you have a class shown below:
class MyClass {
myProp = 'Hello World';
myMethod() {
console.log(this.myProp);
}
}
Let's assume you want to call myMethod on a button click event. This is what you'll do:
const myInstance = new MyClass();
document.querySelector('button').addEventListener('click', myInstance.myMethod);
Now there is a problem: With addEventListener the execution context changes and this refers to the element instead of class instance (which does not have myProp property on it). How do I change it back? In such a scenario you can use bind.
document.querySelector('button').addEventListener('click', myInstance.myMethod.bind(myInstance));
This essentially changes the reference back to the class. This is just one example. There can be numerous examples where you want to change the current execution context of a method. Let's take another example:
Let's assume there is a third party function which requires access to some class members.
Third party function requires target instance's name and lastname properties.
function getFullName() {
return this.name + ' ' + this.lastname;
}
If you execute this method without call or apply, the method assumes the current instance on which this function exists (in this case it is window), and search the properties on current object. But there can be requirements where you might wanna change this behavior. Let's say you have class which provides the name and lastname properties, and you wanna leverage the functionality of this method. This is what you can do:
class MyName {
name = 'Tom';
lastname = 'Hanks';
}
getFullName.apply(new MyName()); // --> 'Tom Hanks'
So you see how call and apply are helpful. You can refer to Zer0's answer to learn more about call, apply and bind.