• Register
110,660 points
12 7 5

I have to admit that the ignorance of these methods, together with the Class syntax, were the biggest triggers of wanting to delve deeper into the fundamentals of the language.

Now that I've mentioned them to you, you will start to see them everywhere. They were actually already there, but did you know what they do? Well, they are very popular, and much more in ES5 and previous projects.

These methods are part of the foundation of JavaScript's object-oriented programming, and they are crucial to understanding the language, and it's a shame that syntaxes like Class and the new keyword are deprecating them. And I say it's a shame because they are fundamental and very important in the prototype inheritance system, while the others are sugar syntax that all they do is obscure the language.

Before we begin, these two methods are available in the prototype of the global Function object:

Function.prototype.call
Function.prototype.apply

In this post I will show examples of use so that you understand and put it to the test.

Call () and apply () methods

First of all and I'm going to save you headaches, they are exactly the same. The only difference is in the second argument , where call () will be an infinite list of arguments and apply () will be an array.

fn.call(this, arg1, arg2, arg3...)
fn.apply(this, [arg1, arg2, arg3...])

A tip that helps me remember which is the letter C of call, which reminds me of commas ; the a of apply, reminds me of array

Let's go to an example with call (). Let's create a classic pseudo-inheritance. This is a classic pseudo-inheritance because we are defining the structure of the final object, "instance" of Person.

    
function Human(gender) {
  this.gender = gender;
  this.isAlive = true;
}

function Person(gender, age) {
  // this = {}
  Human.call(this, gender);
  // this = { gender: 'male', isAlive: true }
  this.age = age;
  // this = { gender: 'male', isAlive: true, age: 18 }
  return this;
}

const alberto = Person.call({}, 'male', 18);

console.log(alberto);
process.exit(0)

In a simple way, we call the Person function to which we first pass an empty object like this , and then the necessary arguments for the function .

const alberto = Person.call({}, 'male', 18);

Do you know what would have happened if instead of putting {} we had put this ? The following would have happened:

function Human(gender) {
  this.gender = gender;
  this.isAlive = true;
}

function Person(gender, age) {
  // this = global: { ... }
  Human.call(this, gender);
  // this = global: { ..., gender: 'male', isAlive: true }
  this.age = age;
  // this = global: { ..., gender: 'male', isAlive: true, age: 18 }
  return this;
}

const alberto = Person.call(this, 'male', 18);

console.log(alberto);
process.exit(0)

As you can see, Alberto now has a lot of new properties, and this is because this at the time of being executed with .call refers to the window object of the browser (or global if we are in Node as is the case), and for Therefore we would be passing an unwanted object as context to Person.

What if instead of using this and call (), I call the function directly?

function Human (gender) {
   this.gender = gender;
   this.isAlive = true;
}

function person (gender, age) {
   // You are getting an implicit this
   // this = global: {...}
   Human.call (this, gender);
   // this = global: {..., gender: 'male', isAlive: true}
   this.age = age;
   // this = global: {..., gender: 'male', isAlive: true, age: 18}
   return this;
}

const alberto = person ('male', 18); // Invoking the function without call ()
/ *
Is the same! The transformation is executed by the JS parser (internals)
   person ('male', 18) 'implicit' === 'explicit' person.call (this, 'male', 18)
* /

console.log (alberto);

It would be exactly the same. You will be able to observe several things:

  1. I have not used the sandbox because apparently it has a security mechanism to prevent this practice, since it represents a security risk. You can try it in your browser or in Node.

  2. I have renamed Person to person, and that's because of the same thing. A rule was created so that functions cannot be called directly if they start with a capital letter, since it may be the case that a developer calls the function directly and the this is referenced to the global / window.

You have to be careful when using this . And this is one of the main reasons why new mechanisms such as new and Class are being created, to avoid mistakes and provide a simpler option to developers who come from OOP languages ​​of classes.

Now it's the turn to explain what happens inside Person. We return to the first example. As you can see, we use call () ^ 2 again, but this time instead of {} we use the word this ^ 3. The esta always depends on the context from which has been executed, and in this case comes from the call ( esta ) ^ 1 alberto , which is {}.

(the symbol> I use it so that you can find what I say)

function Person(gender, age) {
  // this = {}
  Human.2>call(3>this, gender);
...
const alberto = Person.1>call(1>{}, 'male', 18)

So by continuing the call, Human continues to receive the empty object through the ^ 3 context that we are explicitly sending through call (this) ^ 2

I take this opportunity to mention that it is usual to say context and refer to this , since all this will depend on the context from which it is called.

All this will depend on the context from which it is called. It is therefore a contextual reference.

function Human(gender) {
  3> // this = {} ( proviene de Human.call(this, gender) )
  this.gender = gender;
  this.isAlive = true;
}

function Person(gender, age) {
  // this = {}
  Human.2>call(2>this, gender);
...
const alberto = Person.1>call(1>{}, 'male', 18)

Now comes the nice thing in JavaScript there is a technique called Augmentation. It is easier to read and find as "Augmentation", its English variant.

What Human is doing is increasing the context from which it is called. In other words, increase this , add more properties to it (they could also be methods).

And now the not so pretty, if we want to take advantage of the potential of JavaScript, we must know when to increase the context. I say this because in the end, it ends up becoming a composition that does not take advantage of prototypes. Let's say the call () would be like a super (). But this is for another topic.

Now the context will have two new properties, which are gender and isAlive . The this in person has increased. We increase the context again by adding the age ^ 1 property. And finally we return ^ 2 the augmented context.

function Person(gender, age) {
  ...
  // this = { gender: 'male', isAlive: true }
  this.age = age^1;
  // this = { gender: 'male', isAlive: true, age: 18 }
  return this^2;
}

Have you understood the differences between these two methods?

110,660 points
12 7 5