JavaScript from a Java developer's viewpoint
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.
A function in JavaScript is quite different from a method in Java because it is actually an object created by theFunction
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 useinstanceof
, 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 theFunction
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 aboutprototype
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 usenew Function()
to create a function. However, this is not recommended due to its poor performance as well as its readability. TheUser
function in the preceding code snippet is a function declaration. Andworkout
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.
In Java, you create a class to represent a concept, for example, a User
class. TheUser
class has a constructor, some fields, and methods. And you use its constructor to instantiate aUser
object. And every object in Java is an instance of the associated class that provides code sharing among its instances. You can extend theUser
class to create, for example, aTeamMember
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 - Thecreator function method
- The ES6 class method
Let's look at each method one at a time.
TheObject
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.
TheObject
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 overnew 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 torole
, 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.
TheObject.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 reasongreeting()
is not a function of the user
object here is that theObject.create()
method creates a new object with the constructor's prototype object. And the greeting
function is not defined inUser.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 theUser
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 aboutclass
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 thatObject
is a language type in JavaScript, like other language types that JavaScript has, which includesUndefined
, Null
, Boolean
, String
, Symbol
, and Number
. Any value in JavaScript is a value of those types.
Note
The undefined type has a single value,undefined
. The null type has a single value,null
. A Boolean has two values:true
andfalse
.
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 typewritable
, which defines whether a data property can be changed or notenumerable
, which defines whether a property can be enumerated by using a for-in
statementconfigurable
, which defines whether a property can be deleted, changed to be an access property, changed to be not writable, or whether itsenumerable
attribute can be modified
An access property also has four attributes:
get accessor
, which can be a Function
object or undefinedset accessor
, which can be a Function
object or undefinedenumerable
, 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 itstoString()
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:
Figure 1.1: Object, properties, and property attributes
In JavaScript,you can useObject.defineProperty
orObject.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 thenew
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, calledTeamMember
, and it will inherit properties fromUser
and also override thegreeting()
method and provide a new method calledwork()
. Later, we will addeat()
method toUser
andmove()
toObject
.
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, thename
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 theTeamMember
constructor function. Line 21
shows that the member
object can greet in a different way to objects created by theUser
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 theeat()
method to theUser
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 themove()
method to theObject
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:
Figure 1.2: Prototype-based inheritance
On the left-hand side are the constructor functions, and on the right-hand side are their corresponding prototypes. The bottom is themember
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 useObject.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 themove()
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'shasOwnProperty()
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 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. Theinterested
variable, defined in line 12
, is in method-level scope, and it is only accessible within that method. Thei
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 thelet
andconst
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);
Thehotels
variabledeclared in line 24
,is in global scope and is accessible anywhere, such as inside thebookHotel()
function, even though the variable is defined after the function.
TheavailableHotel
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 nestedplaceOrder()
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. ThetotalAmount
variable, defined in line 15
, is a local variable of theplaceOrder()
function.
And in lines 3
and 4
, we defined thei
andhotel
variables with thevar
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.
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 invar 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 theeval()
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();
Note
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.TheUser
function declaration, and lines 19
to 21
, are global code.Lines2
to 17
are the function code of theUser
function. Well, not exactly. Lines 5
to 10
, except line 8
, are the function code of thespeak()
method. Line 8
is the function code of thedrink()
function. Lines 13
and 14
are the function code of theask()
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 afunction
object that is visible in the scope in which the function is declared. For example, line 1
declares theUser
function, which is visible in global scope. Line 12
declares theask()
function, which is visible inside the scope of theUser
function. And line 4
is a function expression that creates thespeak()
method. On the other hand, in line 7
, we use a function expression to create adrink
variable. It is different from the speak()
method created in line 4
. Even though it is also a function expression, thedrink
variable is not a property of an object. It is simply visible inside thespeak()
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. Thespeak()
method and theask()
function are in the same scope, but they have different execution contexts. When theask()
function is executed, as you can see from the output, it has global context and thename
property resolves to the valueUnknown
, which is declared in global scope. And when thespeak()
method is executed, it has theuser
context. As you can see from the output, its access to thename
property resolves toTed
. 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 auser
object by calling theUser
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 theuser
object. And that's why you seeI'm in "User" context.
in the output. And during the instantiation, JavaScript engine will not execute the code inside thespeak()
method because there is no invoking yet. It executes theask()
function when it reaches line 17
. At that time, the control flows from the function code of theUser
constructor function to theask()
function. Because theask()
function isn't a property of an object, nor it is invoked by theFunction.call()
method, which we will talk about later, the global context becomes the execution context. And that's why you seeAsking from "Window" context.
andWhere am I? "Unknown"
in the output. After the instantiation of the user
object, the JavaScript engine goes back to execute line 21
and invokes thespeak()
method on theuser
object. Now, the control flows into thespeak()
method and theuser
object becomes the execution context. And that's why you seeTed 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 seeDrinking 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
fromask()
toask.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 inuser.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
toask.bind(this)()
, you can see the answer to the question"Who am I?"
is also"Ted"
now.
So, what are thesecall()
,apply()
, andbind()
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 theFunction
object. After typing in the following code into the console, you can see that thespeak()
function inherits the properties fromFunction
prototype, including thecall()
,apply()
, andbind()
methods:
console.log(Function.prototype.isPrototypeOf(user.speak)); // true
user.speak.hasOwnProperty('apply');// false
user.speak.__proto__.hasOwnProperty('apply');// true
Thecall()
method and theapply()
method are similar. The difference between these two methods is that thecall()
method accepts a list of arguments, while theapply()
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 thecall()
andapply()
methods as a way of switching execution context.
And thebind()
method acts differently from the other two. What thebind()
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 usecall()
orapply()
to switch execution context. So, whatask.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)
orask.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 thecall()
method.
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 notundefined
, and notTraveling
. 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 whengoToGym()
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.