For readers who are new to JavaScript but who are familiar with the Java language, here are some differences between the two languages that may confuse you. And even though this section is written from a Java developer's perspective, if you're new to JavaScript, you will also find it informative.
JavaScript from a Java developer's viewpoint
Functions and methods
A function in JavaScript is quite different from a method in Java because it is actually an object created by the Function constructor, which is a built-in object of the language. Yes, that's right. Function itself is an object too. What is a method in JavaScript, then? When a function is a property of an object, it is a method. So, in JavaScript, a method is a function, but not all functions are methods.
Since a function is an object, it can also have properties and methods. To establish whether an object is a function or not, you can use instanceof, as follows:
var workout = function () {};
console.log(workout instanceof Function); // true
What is the difference between a function and other objects in JavaScript, apart from the fact that it is created by the Function constructor? First of all, a function is callable, while other objects are not. Another difference is that a function has a prototype property while other objects don't. We will talk about prototype later.
In JavaScript, you can use a function to create objects with new. In a case such as this, that function serves as a constructor. As a convention, when a function serves as a constructor, it should be capitalized. The following is a simple example of using a function as a User constructor. We will build the User constructor containing more details later:
function User () {
}
var user = new User();
Before we move on, let's see the different ways to create a function in JavaScript. Function declarations and function expressions are the most common ways to create functions. Other than that, you can use new Function() to create a function. However, this is not recommended due to its poor performance as well as its readability. The User function in the preceding code snippet is a function declaration. And workout is a function expression. The way that a function is created and invoked will affect the execution context that its function body will point to. We will talk about it later.
Objects and classes
In Java, you create a class to represent a concept, for example, a User class. The User class has a constructor, some fields, and methods. And you use its constructor to instantiate a User object. And every object in Java is an instance of the associated class that provides code sharing among its instances. You can extend the User class to create, for example, a TeamMember class.
In JavaScript, there are several ways to create an object:
- The Object() constructor method
- The object literal method
- The constructor function method
- The Object.create() method
- The creator function method
- The ES6 class method
Let's look at each method one at a time.
The Object constructor method looks like this:
// Call the Object constructor with new
var user = new Object();
user.name = 'Sunny';
user.interests = ['Traveling', 'Swimming'];
user.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
};
user.greeting(); // Hi, I'm Sunny.
The Object constructor creates an object wrapper. This is not a recommended approach, even though it is valid in JavaScript. In practice, it is better to use an object literal instead, which makes the code compact.
The object literal method looks like this:
// Create a user with object literal
var user = {
name: 'Sunny',
interests: ['Traveling', 'Swimming'],
greeting: function () {
console.log('Hi, I\'m ' + this.name + '.');
}
}
user.greeting(); // Hi, I'm Sunny.
The object literal is a compact syntax to create an object in JavaScript and it is the recommended way of creating an object over new Object(). Starting from ES5, object literals also support getter and setter accessors, as can be seen here:
var user = {
get role() {
return 'Engineer';
}
}
user.role; // Engineer
And if you try to assign a value to role, it will stay unchanged because there is no setter accessor defined for the role property.
The constructor function method looks like this:
// Create a constructor function
function User (name, interests) {
this.name = name;
this.interests = interests;
this.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
}
}
// Call the constructor with new to create a user object
var user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.
This syntax is very close to the one in Java. JavaScript is very tolerant, and you can omit the parenthesis when calling the constructor. However, this will not pass any arguments to the constructor, as can be seen here:
var user = new User;
console.log(user.name); // undefined
And again, even though this is valid in JavaScript, it is not recommended to omit the parenthesis.
The Object.create() method looks like this:
// Use Object.create() method with the prototype of
// User constructor function created above
var user = Object.create(User.prototype, {
name: { value: 'Sunny' },
interests: { value: ['Traveling', 'Swimming']}
});
user.greeting(); // Uncaught TypeError: user.greeting() is not a //function
The reason greeting() is not a function of the user object here is that the Object.create() method creates a new object with the constructor's prototype object. And the greeting function is not defined in User.prototype, or passed in the second argument of Object.create(). To make the user be able to greet, we can either pass the greeting function in the second argument, or we can add it to the User constructor's prototype object. The difference is that the first approach only adds the greeting function to the current user object. If you created another user without passing in the greeting function, that user won't have greeting function. On the other hand, adding the function to the prototype object will add the greeting function to all the objects created by that constructor. Let's add it to the User prototype object:
// Add greeting to prototype object
User.prototype.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
}
user.greeting(); // Hi, I'm Sunny.
Actually, using a prototype is how a superclass provides methods for subclasses to inherit in JavaScript. We will talk about that in detail later.
The creator function method looks like this:
// Use a creator function with an object as its return value
function createUser (name, interests) {
var user = {};
user.name = name;
user.interests = interests;
user.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
};
return user;
}
// Call the creator function with parameters
var user = createUser('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.
The creator function here is a factory method, similar to the static factory method that used to instantiate an object in Java. And it is merely a pattern because underneath it wraps the object creation details inside of the creator function.
The ES6 class method looks like this:
// Create User class
class User {
// Equivalent to User constructor function
constructor (name, interests) {
this.name = name;
this.interests = interests;
}
// Equivalent to User.prototype.greeting
greeting () {
console.log('Hi, I\'m ' + this.name + '.')
}
}
let user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.
This is very close to the syntax in Java. Instead of using the class declaration, you can also use the class expression to create the class, as follows:
// Use class expression
let User = class {
constructor (name, interests) {
this.name = name;
this.interests = interests;
}
greeting () {
console.log('Hi, I\'m ' + this.name + '.')
}
}
Even though it uses the same keyword, class, class in JavaScript is quite different from the class in Java. For example, there is no static class and no private class in JavaScript. We will talk more about class in the ES6 section.
Objects, properties, and property attributes
In Java, once an object is created, there is (almost) no way to modify its methods during runtime. Java is not a dynamic language. In JavaScript, things are quite different. You can create an object and modify it easily during runtime, such as adding new properties and replacing a method. That's what a dynamic language can do. Actually, that is not the special part. The special part is that Object is a language type in JavaScript, like other language types that JavaScript has, which includes Undefined, Null, Boolean, String, Symbol, and Number. Any value in JavaScript is a value of those types.
In Java, an object has fields and methods. In JavaScript, an object is logically a collection of properties. A property has a name of the String type and a list of attributes. Attributes, in JavaScript, are used to define and explain the state of a property. There are two types of properties—data properties and access properties.
A data property has four attributes:
- value, which can be of any JavaScript language type
- writable, which defines whether a data property can be changed or not
- enumerable, which defines whether a property can be enumerated by using a for-in statement
- configurable, which defines whether a property can be deleted, changed to be an access property, changed to be not writable, or whether its enumerable attribute can be modified
An access property also has four attributes:
- get accessor, which can be a Function object or undefined
- set accessor, which can be a Function object or undefined
- enumerable, which defines whether a property can be enumerated by using a for-in statement
- configurable, which defines whether a property can be deleted, be changed to be a data property, or whether its other attributes can be modified.
To access a property of an object, you can use dot notation or bracket notation. The dot notation acts the same as how it does in Java. The bracket notation, on the other hand, is quite interesting. In JavaScript, property names must be strings. If you try to use a non-string object as a property name with bracket notation, the object will be casted into a string via its toString() method, as we can see here:
var obj = {};
obj['100'] = 'one hundred';
// Number 100 will be casted to '100'
console.log(obj[100]); // 'one hundred'
// Both foo and bar will be casted to string '[object Object]'
var foo = {prop: 'f'}, bar = {prop: 'b'};
obj[foo] = 'Foo'
console.log(obj[bar]) // 'Foo'
In a nutshell, here is how an object appears, logically:
In JavaScript, you can use Object.defineProperty or Object.defineProperties to modify the properties of an object. Here is how it works:
1. function User (name, department) {
2. var _department = department;
3. var _name = name;
4. Object.defineProperty(this, 'name', {
5. value: _name,
6. writable: true,
7. enumerable: true,
8. configurable: false
9. });
10. Object.defineProperty(this, 'department', {
11. get: function () {
12. console.log('Retrieving department');
13. return _department;
14. },
15. set: function (newValue) {
16. console.log('Updating department value to "' + newValue + '"');
17. _department = newValue;
18. },
19. enumerable: true,
20. configurable: true
21. });
24. Object.defineProperty(this, 'greeting', {
25. value: function () {
26. console.log('Hi, I\'m ' + _name + '.');
27. },
28. enumerable: false,
29. configurable: false
30. });
31. }
As you can see from lines 4 to 9, we use Object.defineProperty to define name as a data property, and its actual data is stored in the internal property _name. In lines 10 to 21, we define department as an access property that has a get accessor and set accessor, and the actual value is kept in _department. In lines 24 to 30, we define greeting property as a data property and its value is a Function object:
32. var user = new User('Sunny', 'Engineering');
33. console.log(user.department);
34. user.department = 'Marketing';
35. user.greeting();
36. Object.defineProperty(user, 'name', {
37. enumerable: false
38. });
39. delete user.name;
40. delete user.department;
41. for (var prop in user) {
42. console.log(prop);
43. }
In line 32, we create a user object using the User constructor function. In line 33, we access the department property. Since it is a get accessor, the getter function will be invoked and the message Retrieving department will show up in the console before the actual department value. In line 34, we assign a new value to the department property. Since we have defined the set accessor, the setter function will be invoked. In line 35, since the greeting property of user object is defined as a function, we will need to invoke it. In lines 36 to 38, we try to redefine the name property. However, since it is not configurable, JavaScript will throw an error with this. The same is true with regard to line 39, where we try to delete this property. The deletion of department property in line 40 will work since it is configurable. In lines 41 to 43, the only property that will show up in the console is the name property, because the department has been deleted and the greeting property is not enumerable.
Prototype and inheritance
As has been briefly mentioned previously, inheritance in JavaScript is archived by using prototypes of constructor functions. In JavaScript, a prototype is an object that provides shared properties for other objects. And only a function object has a prototype because only a function object is callable and can create other objects. In ES6, arrow functions don't have prototypes. We will discuss that later.
You can think of a function as a factory and its prototype is the specification of the products that the factory manufactures. Every time you call a function with the new keyword, you place an order for its product. And the factory will produce it according to how it is specified in the prototype.
Now, let's see how inheritance works in code. We will create another constructor function, called TeamMember, and it will inherit properties from User and also override the greeting() method and provide a new method called work(). Later, we will add eat() method to User and move() to Object.
Here is how it is implemented in ES5:
1. function User (name, interests) {
2. this.name = name;
3. this.interests = interests;
4. }
5. User.prototype.greeting = function () {
6. console.log('Hi, I\'m ' + this.name + '.');
7. }
In lines 1 to 4, we create a User constructor function. And what it really does is create a function object using the Function constructor. In JavaScript, you can check who created an object by using its constructor property, which references back to its creator, as follows:
console.log(User.constructor === Function); // true
And once the User constructor function is created, it has a prototype object. And a User prototype object itself is created by the User constructor function, as you can see in the following:
console.log(User.prototype.constructor === User); // true
And in JavaScript, after you create a user object using the User constructor function, that object will have a __proto__ property that references the User prototype object. You can see the link like this:
var user = new User();
console.log(user.__proto__ === User.prototype); // true
This __proto__ reference serves as a link in the prototype chain. You will see what that means visually later.
Now back to the code. In lines 5 to 7, we create a greeting property on the User prototype. This will create a method that can be inherited by User subclasses. And, as we mentioned earlier, if you define the greeting method inside the User constructor function, subclasses won't see this greeting method. We will see the reason for this shortly:
8. function TeamMember (name, interests, tasks) {
9. User.call(this, name, interests);
10. this.tasks = tasks;
11. }
12. TeamMember.prototype = Object.create(User.prototype);
13. TeamMember.prototype.greeting = function () {
14. console.log('I\'m ' + this.name + '. Welcome to the team!');
15. };
16. TeamMember.prototype.work = function () {
17. console.log('I\'m working on ' + this.tasks.length + ' tasks');
18. };
In lines 8 to 13, we create a TeamMember constructor function, and inside it, we invoke the User constructor function's call() method, which is inherited from the Function object to chain constructors, which is similar to invoking super() in a constructor of a Java class. One difference is that the call() method's first argument must be an object, which serves as the execution context. In our example, we use this as the execution context. Inside the call() method, the name and interests properties are initialized. And then, we add an additional property, tasks, to TeamMember.
In line 12, we use Object.create() to create a TeamMember prototype object using the User prototype object. In this way, objects created by the TeamMember constructor function will have the properties of the User prototype object and each team member object will have a __proto__ property that links to this TeamMember prototype.
In lines 13 to 15, we override the original greeting() method of the User prototype so that objects created by the TeamMember constructor function will have different behavior. This will not affect the User prototype object since they are essentially two different objects, even though these two prototype objects have the same constructor, as you can see in the following:
console.log(User.prototype === TeamMember.prototype); // false
console.log(User.prototype.constructor === TeamMember.prototype.constructor); // true
In lines 16 to 18, we add a new method, work(), to the TeamMember prototype object. In this way, objects created by the TeamMember constructor function will have this additional behavior:
19. var member = new TeamMember('Sunny', ['Traveling'],
20. ['Buy three tickets','Book a hotel']);
21. member.greeting(); // I'm Sunny. Welcome to the team!
22. member.work(); // I'm working on 2 tasks
23
24. console.log(member instanceof TeamMember); // true
25. console.log(member instanceof User); // true
26. console.log(member instanceof Object); // true
27
28. User.prototype.eat = function () {
29. console.log('What will I have for lunch?');
30. };
31. member.eat(); // What will I have for lunch?
32
33. // Add a method to the top
34. Object.prototype.move = function () {
35. console.log('Every object can move now');
36. };
37. member.move(); // Every object can move now
38. var alien = {};
39. alien.move(); // Every object can move now
40. User.move(); // Even the constructor function
In line 19, we create a member object using the TeamMember constructor function. Line 21 shows that the member object can greet in a different way to objects created by the User constructor function. And line 22 shows that the member object can work.
Lines 24 to 26 show that the member object is an instance of all its superclasses.
In lines 28 to 30, we add the eat() method to the User prototype, and even though the member object is created before this, as you can see from line 31, it also inherits that method.
In line 34, we add the move() method to the Object prototype, which might turn out to be a really bad idea since, as you can see in lines 37 to 40, every object can move now, even those constructor function objects.
We just create an inheritance chain starting from Object | User | TeamMember. The prototype link is the key to this chain. Here is how it appears:
On the left-hand side are the constructor functions, and on the right-hand side are their corresponding prototypes. The bottom is the member object. As you can see, the member object's __proto__ property references the prototype object of TeamMember. And the __proto__ property of the TeamMember prototype object itself references the prototype object of User. And the __proto__ property of the User prototype object references the top level, which is the prototype object of Object. To verify the link, you can do something like this:
console.log(member.__proto__ === TeamMember.prototype); // true
console.log(TeamMember.prototype.__proto__ === User.prototype); // true
console.log(User.prototype.__proto__ === Object.prototype); // true
So, be really careful with the __proto__ property. If you, let's say, accidentally change this property to something else, the inheritance will break:
User.prototype.__proto__ = null;
member.move(); // Uncaught TypeError: member.move is not a function
console.log(member instanceof Object); // false (Oops!)
It is recommended to use Object.prototype.isPrototypeof() to check the prototype chain:
TeamMember.prototype.isPrototypeOf(member); // true
With the inheritance relationship map showing in the preceding diagram, you can easily see how JavaScript resolves a property through the prototype chain. For example, when you access a member object's name property, JavaScript finds that it is on the object itself and will not go up the chain. And when you access the move() method, JavaScript will go up the chain and check whether the TeamMember prototype has it and, since it doesn't, JavaScript will keep going up until it finds the method in the Object prototype. You can use an object's hasOwnProperty() method to check whether that object has a property as its own instead of inherited through the prototype chain:
member.hasOwnProperty('name'); // true
member.hasOwnProperty('move'); // false
Scope and closure
Scope is about the accessibility of variables. In Java, basically, a set of curly brackets {} defines a scope, including class-level scope, method-level scope, and block-level scope.
Let's take a look at the following example in Java:
1. public class User {
2. private String name;
3. private List<String> interests;
4.
5. public User (String name, List<String> interests) {
6. this.name = name;
7. this.interests = interests;
8. }
9.
10. // Check if a user is interested in something
11. public boolean isInterestedIn(String something) {
12. boolean interested = false;
13. for (int i = 0; i < interests.size(); i++) {
14. if (interests.get(i).equals(something)) {
15. interested = true;
16. break;
17. }
18. }
19. return interested;
20. }
21. }
The name and interests properties are in the class-level scope and they are accessible anywhere within the class. The interested variable, defined in line 12, is in method-level scope, and it is only accessible within that method. The i variable, in line 13, is defined within the for loop and it is block-level scope only accessible within the for loop block. In Java, the scope of the variables is static and can be determined by the compiler.
In JavaScript, the scope of the variables is much more flexible. There is global scope and function scope, and block scope with the let and const keywords, which were introduced in ES6, which we will talk about later.
Let's look at the following JavaScript example:
1. function bookHotel (city) {
2. var availableHotel = 'None';
3. for (var i=0; i<hotels.length; i++) {
4. var hotel = hotels[i];
5. if (hotel.city === city && hotel.hasRoom) {
6. availableHotel = hotel.name;
7. break;
8. }
9. }
10. // i and hotel still accessible here
11. console.log('Checked ' + (i+1) + ' hotels');// Checked 2 hotels
12. console.log('Last checked ' + hotel.name); // Last checked Hotel B
13. {
14. function placeOrder() {
15. var totalAmount = 200;
16. console.log('Order placed to ' + availableHotel);
17. }
18. }
19. placeOrder();
20. // Not accessible
21. // console.log(totalAmount);
22. return availableHotel;
23. }
24. var hotels = [{name: 'Hotel A', hasRoom: false, city: 'Sanya'},
{name: 'Hotel B', hasRoom: true, city: 'Sanya'}];
25. console.log(bookHotel('Sanya')); // Hotel B
26. // Not accessible
27. // console.log(availableHotel);
The hotels variable declared in line 24, is in global scope and is accessible anywhere, such as inside the bookHotel() function, even though the variable is defined after the function.
The availableHotel variable declared in line 2 is in the scope of the bookHotel() function. It is a local variable and is not accessible outside of the function, as you can see from line 27. Inside its enclosing function, the availableHotel variable is accessible anywhere, even the nested placeOrder() function, as you can see in line 16. This is called closure. A closure is formed when a function is nested inside another function. And no matter how deeply you have nested a function, it will still have access to its parent function's scope, and all the way to the top scope, which is global scope. The totalAmount variable, defined in line 15, is a local variable of the placeOrder() function.
And in lines 3 and 4, we defined the i and hotel variables with the var keyword. Even though it is in a for loop block, it is still accessible outside the block, as shown in lines 11 and 12. In ES6, you can use the let keyword to define i and hotel, which will put these two variables in for loop block scope. We will talk more about this later.
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();
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.
Hoisting
This is another thing that Java developers usually easily get confused. Hoisting is a metaphor for the way that JavaScript interpreters will lift function declarations and variable declarations to the top of their containing scope. So, In JavaScript, you can see something that is obviously wrong and will definitely break the compilation if you write that in Java, but it is totally valid in JavaScript.
Let’s see an example:
1. travel = 'No plan';
2. var travel;
3. console.log(travel); // Is the output: undefined?
4.
5. function travel() {
6. console.log('Traveling');
7. }
8. travel(); // Is the output: Traveling?
What will the output be when the JavaScript engine executes line 3 and 8? It is not undefined, and not Traveling. Line 3 is "No plan" and line 8 is "Uncaught TypeError".
Here is what the JavaScript interpreter sees when it processes the preceding code:
1. // Function declaration moved to the top of the scope
2. function travel() {
3. console.log('Traveling');
4. }
5. // Variable declaration moved under function declaration
6. var travel;
7. travel = 'No plan';
8.
9. console.log(travel); // No plan
10. travel(); // Uncaught TypeError: travel is not a function
JavaScript interpreter moves the function declarations up to the top, followed by variables declarations. Function expressions, for example, var travel = function(){}, are not lifted to the top as function declarations because they are also variable declarations.
Let's see another example:
1. function workout() {
2. goToGym(); // What will the output be?
3. var goToGym = function () {
4. console.log('Workout in Gym A');
5. }
6. return;
7. function goToGym() {
8. console.log('Workout in Gym B');
9. }
10. }
11. workout();
What will the output be when line 2 is executed? It is "Workout in Gym B.". And here is what the interpreter sees when it processes the code:
1. function workout() {
2. function goToGym() {
3. console.log('Workout in Gym B');
4. }
5. var goToGym;
6. goToGym();
7. goToGym = function () {
8. console.log('Workout in Gym A');
9. }
10. return;
11. }
12. workout();
The interpreter moves the function declaration to the top of the scope and then the variable declaration, but not the assignment. So when goToGym() is executed, the assignment expression to the new function hasn't happened yet.
To wrap up, before executing, JavaScript interpreters will move the function declarations, and then variable declarations, without assignment expressions, up to the top of the containing scope. And it is valid to put function declarations after the return statement.