The this keyword

In Java, this always refers to the current object. It is solid. In JavaScript, this behaves differently. In short, this refers to the current execution context, which is an object. And the way that JavaScript runtime determines the current execution context is much more complex than in Java.

In JavaScript, there is an execution context stack, logically formed from active execution contexts. When control is transferred from one executable code to another, control enters the new executable code's execution context, which becomes the current execution context, or what is referred to as the running execution context. At the bottom of the stack is the global context, where everything begins, just like the main method in Java. The current execution context is always at the top of the stack.

What is the executable code? There are three types in JavaScript:

  • Global code, which is the code that runs from the place where a JavaScript program starts. In a browser, it is where window lives. And when you open a browser console and type in var user = new User(), you are writing global code.
  • Eval code, which is a string value passed in as the argument of the built-in eval() function (do not use the eval() function unless you really know what you're doing).
  • Function code, which is the code parsed as the body of a function. However, it doesn't mean all the code written inside a function is function code.

Now, to understand this better, let's look at the following example:

1.  function User (name) {
2. console.log('I'm in "' + this.constructor.name + '" context.');
3. this.name = name;
4. this.speak = function () {
5. console.log(this.name + ' is speaking from "' +
6. this.constructor.name + '" context.');
7. var drink = function () {
8. console.log('Drinking in "' + this.constructor.name + '"');
9. }
10. drink();
11. };
12. function ask() {
13. console.log('Asking from "' +
14. this.constructor.name + '" context.');
15. console.log('Who am I? "' + this.name + '"');
16. }
17. ask();
18. }
19. var name = 'Unknown';
20. var user = new User('Ted');
21. user.speak();
Since an execution context is, in fact, an object, here we use its .constructor.name to see what the context is. And if you run the preceding code in the node command line, it will be Object instead of Window.

If you run the code from Chrome console, the output will be the following:

// I'm in "User" context.
// Asking from "Window" context.
// Who am I? "Unknown"
// Ted is speaking from "User" context.
// Drinking in "Window"

First, let's see which part is global code and which part is function code. The User function declaration, and lines 19 to 21, are global code. Lines 2 to 17 are the function code of the User function. Well, not exactly. Lines 5 to 10, except line 8, are the function code of the speak() method. Line 8 is the function code of the drink() function. Lines 13 and 14 are the function code of the ask() function.

Before we review the output, let's revisit the two commonly used ways of creating a function—function declarations and function expressions. When the JavaScript engine sees a function declaration, it will create a function object that is visible in the scope in which the function is declared. For example, line 1 declares the User function, which is visible in global scope. Line 12 declares the ask() function, which is visible inside the scope of the User function. And line 4 is a function expression that creates the speak() method. On the other hand, in line 7, we use a function expression to create a drink variable. It is different from the speak() method created in line 4. Even though it is also a function expression, the drink variable is not a property of an object. It is simply visible inside the speak() method.

In JavaScript, scope and execution context are two different concepts. Scope is about accessibility, while the execution context is about the ownership of running an executable code. The speak() method and the ask() function are in the same scope, but they have different execution contexts. When the ask() function is executed, as you can see from the output, it has global context and the name property resolves to the value Unknown, which is declared in global scope. And when the speak() method is executed, it has the user context. As you can see from the output, its access to the name property resolves to Ted. This can be quite confusing to Java developers. So what happened behind the scenes?

Let's review the preceding example from the JavaScript engine's view. When the JavaScript engine executes line 20, it creates a user object by calling the User constructor function. And it will go into the function body to instantiate the object. When the control flows from the global code to the function code, the execution context is changed to the user object. And that's why you see I'm in "User" context. in the output. And during the instantiation, JavaScript engine will not execute the code inside the speak() method because there is no invoking yet. It executes the ask() function when it reaches line 17. At that time, the control flows from the function code of the User constructor function to the ask() function. Because the ask() function isn't a property of an object, nor it is invoked by the Function.call() method, which we will talk about later, the global context becomes the execution context. And that's why you see Asking from "Window" context. and Where am I? "Unknown" in the output. After the instantiation of the user object, the JavaScript engine goes back to execute line 21 and invokes the speak() method on the user object. Now, the control flows into the speak() method and the user object becomes the execution context. And that's why you see Ted is speaking from "User" context. in the output. When the engine executes the drink() function, it resolves back to the global context as the execution context. And that is why you see Drinking in "Window" context. in the output.

As mentioned earlier, the execution context is affected by the way a function is created as well as by how it is invoked. What does that mean? Let's change line 16 from ask() to ask.call(this). And if you run the preceding example again from Chrome's console, you can see the following output:

...
Asking from "User" context.
Who am I? "Ted"
...

And if you type in user.speak.apply({name: 'Jack'}) into the console, you will see something interesting, like this:

Jack is speaking from "Object" context.
Drinking in "Window" context.

Or, if you change line 17 to ask.bind(this)(), you can see the answer to the question "Who am I?" is also "Ted" now.

So, what are these call(), apply(), and bind() methods? It seems that there are no definitions of them in the preceding example. As you might remember, every function is an object created by the Function object. After typing in the following code into the console, you can see that the speak() function inherits the properties from Function prototype, including the call(), apply(), and bind() methods:

console.log(Function.prototype.isPrototypeOf(user.speak)); // true
user.speak.hasOwnProperty('apply'); // false
user.speak.__proto__.hasOwnProperty('apply'); // true

The call() method and the apply() method are similar. The difference between these two methods is that the call() method accepts a list of arguments, while the apply() method accepts an array of arguments. Both methods take the first argument as the execution context of the function code. For example, in user.speak.apply({name: 'Jack'}), the {name: 'Jack'} object will be the execution context of the speak() method of user. You can think of the call() and apply() methods as a way of switching execution context.

And the bind() method acts differently from the other two. What the bind() method does is create a new function that will be bound to the first argument that is passed in as the new function’s execution context. The new function will never change its execution context even if you use call() or apply() to switch execution context. So, what ask.bind(this)() does is create a function and then execute it immediately. Besides executing it immediately, you can assign the new function to a variable or as a method of an object.

To wrap up, there are four ways to invoke a function:

  • Constructor function invoking: new User()
  • Direct function invoking: ask()
  • Method invoking: user.speak()
  • Switching context invoking: ask.call(this) or ask.apply(this)

When we are talking about constructor function invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that the constructor creates.

When we are talking about direct function invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the global context.

When we are talking about method invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that the method belongs to.

When we are talking about switching context invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that passed in as the first argument of the call() method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.190.175