JavaScript Function Invocation and this (With Examples)

John John (304)
0

You could write JavaScript for years without ever understanding scope or how the this keyword is set (it probably won't be great code). You'll run into some confusing situations and find a quick solution on StackOverflow involving the apply or bind functions and move on. Understanding function invocation is a great way to take your JavaScript knowledge to the next level. Learning how functions are invoked and how the this keyword is set will allow you to write much more expressive code. In this guide, I'll provide some examples about how functions are invoked and how the this keyword is determined for each invocation pattern.

Posted in these interests:
h/code69 guides
h/javascript27 guides

This pattern refers to the invocation of a function that is not defined as a member of an object. This is the most basic way to define a function, and it includes both function declarations:

function hello() {
    alert('hello');
}

and function expressions:

var hello = function() {
    alert('hello');
}

Functions invoked in this way have access to the this keyword, and its value is the global object. This is probably not what you would expect to see. You might anticipate this to refer to the parent function's scope, but it does not. Here is an example.

var obj = {};
obj.add = function(val1, val2) {
    var inner = function() {
        this.sum = val1 + val2;
    }
    inner();
}
obj.add(2, 4);

You might expect obj.sum to equal 6, but it does not!

console.log(obj.sum);
> undefined

Rather the global object now has a sum attribute. Take a look:

console.log(sum);
> 6

This is not good. Whenever possible we want to avoid clouding the global namespace. There is a conventional way around this problem, and the solution involves a new variable called that (as a Python guy, I also like self). Check out this example:

var obj = {};
obj.add = function(val1, val2) {
    var that = this;
    var inner = function() {
        that.sum = val1 + val2;
    }
    inner();
}
obj.add(2, 4);

You should notice a small change. The first line of the add method now shows var that = this;. In this case, we are setting the value of this (which refers to the obj object) to the variable that.

Now within the inner function, since the this keyword refers to the global object, we want to use that to refer to the obj object. Now you can log obj.sum and get what you might expect:

console.log(obj.sum);
> 6

You should remember that when invoking a function on its own (not as a member of an object), this will refer to the global object.

The method invocation pattern does, in my opinion, what is most natural and expected - it assigns this to the object within which it is invoked. First, let's look at an example of method invocation:

var sentence = {
    words: [],
    add: function(word) {
        this.words.push(word);
    },
    read: function() {
        var text = this.words.join(' ') + '.';
        console.log(text);
    }
};
sentence.add('The');
sentence.add('end');
sentence.read();

In this example, we can add words to the sentence using the add method. This method is invoked using the method invocation pattern as it is a member of the sentence object. As you can see, the add method makes use of this which refers to the sentence. So within the add and read methods, using this.words is the same thing as using sentence.words. The above code will log:

> The end.

This pattern is especially great because it employs late binding. This means the value of this is determined when the method is invoked. This makes the method highly reusable. Take a look at the following function:

var set = function(key, val) {
    this[key] = val;
};

If you call this function directly using basic function invocation, you will be adding your key and value to the global object.

set('name', 'Tyler');
console.log(name);
> Tyler

However, since the value of this is determined at invocation time, you can assign this function to an object in order to set attributes on the object.

var person = {};
person.set = set;
person.set('name', 'Joe');

This same set function is added as an attribute of the person object. And as you can see in the above example, invoking the set method as a member of the person object ensures that this refers to person rather than the global object.

console.log(person.name);
> Joe

Before talking about constructor invocation, you should understand that JavaScript is a prototypal inheritance language. Everything is an object, and every object is created using some other object as a prototype. This means that there is no such thing as a JavaScript class. However, since many programmers are familiar and happy with classical languages, JavaScript provides a way to create objects that looks very much like a classical language. Here's a brief example of JavaScript's class-like object creation:

function base() {
}
var child = new base();

This example is boring because the base function does nothing, but I wanted to demonstrate how a new object is created from an existing function. There are some important details about this process that are outside the scope of this guide, but for this purpose you can see that the child object is created using new base().

This creates a new object called child whose constructor method is base. I'll show you what I mean:

console.log(child.constructor)
> [Function: base]

If a function is invoked with the new prefix, a new object will be created with a hidden link to the value of the original function’s prototype member, and this will be bound to the newly created object. To put this into practice let's pass in a value upon "instantiation" of our fake class.

function base(number) {
    this.number = number;
}
var child = new base(100);

Hopefully by now you can guess what this refers to in the base function.

console.log(child.number);
> 100

Since base is used as the constructor, this refers to whichever object is created from it. The function that is intended to be used with the new prefix is called a constructor. This is not recommended as JavaScript wasn’t designed as a classical language but rather as a prototypal language. But you will certainly run into this pattern as you read more code.

In JavaScript, functions are really just objects.

var chicken = function() {};
console.log(chicken.prototype);
> {}

The function object comes with a method called apply that helps establish its context at invocation time. Apply allows you to specify the value of this as well as the arguments when you call the method (arguments will be discussed in a later guide).

So check out this example:

var set = function(key, val) {
    this[key] = val;
}

This is the same set method we used in a previous example. If you recall, we set this function as an attribute of an object in order to give this the context of the object. There's actually another way to accomplish this using the apply invocation pattern.

var person = {};
set.apply(person, ['name', 'Tyler']);

Making use of the apply method on the function object, we are able to pass in the object to be used as this. The second parameter of the apply function is an array to be used as the arguments keyword. The arguments array matches up with the parameters in the target function. As you might expect from the above code, this used within the set method refers to the person object.

console.log(person.name);
> Tyler

Of course, if we didn't use apply to call the set function, person.name would be undefined. This pattern gives us another great way to write reusable functions allowing us the ability to set the context for any function we create.

JavaScript invocation patterns are nothing new, but I find that important concepts like this are often buried deep within a mass of documentation. It's not reasonable to retain an entire 500 pages of documentation about anything, so it's nice to read and learn the important stuff first. Feel free to comment below if there are any issues with this guide or if you have any great examples demonstrating one of these patterns.

John John (304)
0

In short, hoisting is when JavaScript moves variable and function declarations to the top of their scope before any code is executed.