1. The mystery of this
A lot of time this
keyword was a mystery for me and many starting JavaScript developers. It is a powerful feature, but requires some efforts to be understood.
From a background like Java, PHP or other standard language, this
is seen as an instance of current object in the class method: no more and no less. Mostly, it cannot be used outside the method and this simple approach does not create confusion.
In JavaScript, this
is the current execution context of a function. Because the language has 4 function invocation types:
- function invocation:
alert('Hello World! ')
- method invocation:
console.log('Hello World! ')
- constructor invocation:
new RegExp('\\d')
- indirect invocation:
alert.call(undefined, 'Hello World! ')
and each one defines its own context, this
behaves slight different than developer expects.
Moreover strict mode also affects the execution context.
The key to understanding this keyword is having a clear view over functions invocation and how this impacts the context. This article is focused on the invocation explanation, how the function call influences this and demonstrates the common pitfalls of identifying the context.
Before starting, let’s familiarize with a couple of terms:
- Invocation is executing the code that makes the body of a function (simply calling the function). For example
parseInt
function invocation isparseInt('15')
. - Context of an invocation is the value of
this
within function body. - Scope of a function is a set of variables, objects, functions accessible within a function body.
Table of contents:
- The mystery of
this
- This in Function Invocation 2.2. This in Function Invocation, strict mode 2.3. Pitfall: this in an inner function
- Method invocation
3.1.this
in method invocation
3.2. Pitfall: separating method from its object - Summarization 4.1. Pitfall: Forgetting about new
- Indirect invocation
5.1.this
in indirect invocation - This is Bound function
- This in Arrow function 7.2. Pitfall: Defining method with Arrow function
- Conclusion
2. Function invocation
Function invocation is performed when an expression that evaluates to a function object is followed by an open parenthesis (, a comma separated list of arguments expressions and a close parenthesis ). For example parseInt(’18’). The expression cannot be a property accessor myObject.myFunction, Which creates a method invocation. For example [1,5]. Join (‘,’) is not a function invocation, but a method call.
A simple example of function invocation:
function hello(name) { return 'Hello ' + name + '! '; } // Function invocation var message = hello('World'); console.log(message); // => 'Hello World! 'Copy the code
hello('World')
is the function invocation: hello
expression evaluates to a function object, followed by a pair of parenthesis with 'World'
argument.
A more advanced example is the IIFE (immediately-invoked function expression):
var message = (function(name) { return 'Hello ' + name + '! '; })('World'); console.log(message) // => 'Hello World! 'Copy the code
IIFE is a function invocation too: first pair of parenthesis (function(name) {… }) is an expression that evaluates to a function object, followed by a pair of parenthesis with ‘World’ argument: (‘World’).
2.1. this
in function invocation
this
is the global object in a function invocation
The global object is determined by the execution environment. It is the window
object in a web browser and the process
object in a NodeJS script.
In a function invocation the execution context is the global object.
Let’s check the context in the following function:
function sum(a, b) { console.log(this === window); // => true this.myNumber = 20; // add 'myNumber' property to global object return a + b; } // sum() is invoked as a function // this in sum() is a global object (window) sum(15, 16); // => 31 window.myNumber; / / = > 20Copy the code
At the time sum(15, 16)
is called, JavaScript automatically sets this
as the global object, which in a browser is window
.
When this
is used outside any function scope (the top most scope: global execution context), it also refers to the global object:
console.log(this === window); // => true this.myString = 'Hello World! '; console.log(window.myString); // => 'Hello World! 'Copy the code
Copy the code
2.2. this
in function invocation, strict mode
this
isundefined
in a function invocation in strict mode
The strict mode introduced in ECMAScript 5.1, which is a restricted variant of JavaScript and provides better security and stronger error checking. To enable it, place the directive ‘use strict’ at the top of a function body. This mode affects the execution context making this to Be undefined. The execution context is not The global object anymore, contrary to above case 2.1.
An example of running a function in strict mode:
function multiply(a, b) { 'use strict'; // enable the strict mode console.log(this === undefined); // => true return a * b; } // multiply() function invocation with strict mode enabled // this in multiply() is undefined multiply(2, 5); / / = > 10Copy the code
When multiply(2, 5)
is invoked as a function this
is undefined
.
The strict mode is active not only in the current scope, but also in the inner scopes (for all functions declared inside):
function execute() { 'use strict'; // activate the strict mode function concat(str1, str2) { // the strict mode is enabled too console.log(this === undefined); // => true return str1 + str2; } // concat() is invoked as a function in strict mode // this in concat() is undefined concat('Hello', ' World! '); // => "Hello World!" } execute();Copy the code
‘use strict’ is inserted at the top of execute body, which enables the strict mode within its scope. Because concat is declared within the execute scope, it inherits the strict mode. And the invocation concat(‘Hello’, ‘ World! ‘) makes this to be undefined.
A single JavaScript file may contain both strict and non-strict modes. So it is possible to have different context behavior in a single script for the same invocation type:
function nonStrictSum(a, b) { // non-strict mode console.log(this === window); // => true return a + b; } function strictSum(a, b) { 'use strict'; // strict mode is enabled console.log(this === undefined); // => true return a + b; } // nonStrictSum() is invoked as a function in non-strict mode // this in nonStrictSum() is the window object nonStrictSum(5, 6); // => 11 // strictSum() is invoked as a function in strict mode // this in strictSum() is undefined strictSum(8, 12); / / = > 20Copy the code
2.3. Pitfall: this
in an inner function
A common trap with the function invocation is thinking that this
is the same in an inner function as in the outer function.
Correctly the context of the inner function depends only on invocation, but not on the outer function’s context.
To have the expected this
, modify the inner function’s context with indirect invocation (using .call()
or .apply()
, see 5.) or create a bound function (using .bind()
, see 6.).
The following example is calculating a sum of two numbers:
var numbers = { numberA: 5, numberB: 10, sum: function() { console.log(this === numbers); // => true function calculate() { // this is window or undefined in strict mode console.log(this === numbers); // => false return this.numberA + this.numberB; } return calculate(); }}; numbers.sum(); // => NaN or throws TypeError in strict modeCopy the code
numbers.sum() is a method invocation on an object (see 3.), so the context in sum is numbers object. calculate function is defined inside sum, so you might expect to have this as numbers object in calculate() too. However calculate() is a function invocation (but Not method Invocation) and it has this as the global object window (case 2.1.) or undefined in strict mode (case 2.2.). Even if the outer function sum has the context as numbers object, it doesn’t have influence here. The invocation result of numbers.sum() is NaN or an error is thrown TypeError: Cannot read property ‘numberA’ of undefined in strict mode. Definitely not the expected result 5 + 10 = 15, all because calculate is not invoked correctly.
To solve the problem, calculate
function should be executed with the same context as the sum
method, in order to access numberA
and numberB
properties. One solution is to use .call()
method (see section 5.):
var numbers = { numberA: 5, numberB: 10, sum: function() { console.log(this === numbers); // => true function calculate() { console.log(this === numbers); // => true return this.numberA + this.numberB; } // use .call() method to modify the context return calculate.call(this); }}; numbers.sum(); / / = > 15Copy the code
calculate.call(this)
executes calculate
function as usual, but additionally modifies the context to a value specified as the first parameter. Now this.numberA + this.numberB
is equivalent to numbers.numberA + numbers.numberB
and the function returns the expected result 5 + 10 = 15
.
3. Method invocation
A method is a function stored in a property of an object. For example:
var myObject = {
// helloFunction is a method
helloFunction: function() {
return 'Hello World!';
}
};
var message = myObject.helloFunction();
Copy the code
helloFunction
is a method in myObject
. To get the method, use a property accessor: myObject.helloFunction
.
Method invocation is performed when an expression in a form of property accessor that evaluates to a function object is followed by an open parenthesis (
, a comma separated list of arguments expressions and a close parenthesis )
.
Using the previous example, myObject.helloFunction()
is a method invocation of helloFunction
on the object myObject
. Also method calls are: [1, 2].join(',')
or /\s/.test('beautiful world')
.
It is important to distinguish function invocation (see section 2.) from method invocation, because they are different types. The main difference is that method invocation requires a property accessor form to call the function (.functionProperty()
or ['functionProperty']()
), while function invocation does not (()
).
['Hello', 'World'].join(', '); // method invocation ({ ten: function() { return 10; } }).ten(); // method invocation var obj = {}; obj.myFunction = function() { return new Date().toString(); }; obj.myFunction(); // method invocation var otherFunction = obj.myFunction; otherFunction(); / / function invocation parseFloat (' 16.60 '); // function invocation isNaN(0); // function invocationCopy the code
3.1. this
in method invocation
this
is the object that owns the method in a method invocation
When invoking a method on an object, this
becomes the object itself.
Let’s create an object with a method that increments a number:
var calc = { num: 0, increment: function() { console.log(this === calc); // => true this.num += 1; return this.num; }}; // method invocation. this is calc calc.increment(); // => 1 calc.increment(); / / = > 2Copy the code
Calling calc.increment()
will make the context of the increment
function to be calc
object. So using this.num
to increment the number property is working well.
A JavaScript object inherits a method from its prototype
. When the inherited method is invoked on the object, the context of the invocation is still the object itself:
var myDog = Object.create({ sayName: function() { console.log(this === myDog); // => true return this.name; }}); myDog.name = 'Milo'; // method invocation. this is myDog myDog.sayName(); // => 'Milo'Copy the code
Object.create()
creates a new object myDog
and sets the prototype. myDog
object inherits sayName
method. When myDog.sayName()
is executed, myDog
is the context of invocation.
In ECMAScript 6 class
syntax, the method invocation context is also the instance itself:
class Planet {
constructor(name) {
this.name = name;
}
getName() {
console.log(this === earth); // => true
return this.name;
}
}
var earth = new Planet('Earth');
// method invocation. the context is earth
earth.getName(); // => 'Earth'
Copy the code
3.2. Pitfall: Separating method from its objects
A method from an object can be extracted into a separated variable. When calling the method using this variable, you might think that this
is the object on which the method was defined.
Correctly if the method is called without an object, then a function invocation happens: where this
is the global object window
or undefined
in strict mode (see 2.1 and 2.2).
Creating a bound function (using .bind()
, see 6.) fixes the context, making it the object that owns the method.
The following example creates Animal
constructor and makes an instance of it – myCat
. Then setTimout()
after 1 second logs myCat
object information:
function Animal(type, legs) {
this.type = type;
this.legs = legs;
this.logInfo = function() {
console.log(this === myCat); // => false
console.log('The ' + this.type + ' has ' + this.legs + ' legs');
}
}
var myCat = new Animal('Cat', 4);
// logs "The undefined has undefined legs"
// or throws a TypeError in strict mode
setTimeout(myCat.logInfo, 1000);
Copy the code
You might think that setTimout
will call the myCat.logInfo()
, which will log the information about myCat
object. But the method is separated from its object when passed as a parameter: setTimout(myCat.logInfo)
, and after 1 second a function invocation happens. When logInfo
is invoked as a function, this
is global object or undefined
in strict mode (but not myCat
object), so the object information does not log correctly.
A function can be bound with an object using .bind()
method (see 6.). If the separated method is bound with myCat
object, the context problem is solved:
function Animal(type, legs) {
this.type = type;
this.legs = legs;
this.logInfo = function() {
console.log(this === myCat); // => true
console.log('The ' + this.type + ' has ' + this.legs + ' legs');
};
}
var myCat = new Animal('Cat', 4);
// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000);
Copy the code
myCat.logInfo.bind(myCat)
returns a new function that executes exactly like logInfo
, but has this
as myCat
even in a function invocation.
4. Constructor invocation
Constructor invocation is performed when new
keyword is followed by an expression that evaluates to a function object, an open parenthesis (
, a comma separated list of arguments expressions and a close parenthesis )
. For example: new RegExp('\\d')
.
This example declares a function Country
, then invokes it as a constructor:
function Country(name, traveled) {
this.name = this.name ? this.name : 'United Kingdom';
this.traveled = Boolean(traveled); // transform to a boolean
}
Country.prototype.travel = function() {
this.traveled = true;
};
// Constructor invocation
var france = new Country('France', false);
// Constructor invocation
var unitedKingdom = new Country;
france.travel(); // Travel to France
Copy the code
new Country('France', false)
is a constructor invocation of the Country
function. The result of execution is a new object, which name
property is 'France'
.
If the constructor is called without arguments, then the parenthesis pair can be omitted: new Country
.
Starting ECMAScript 6, JavaScript allows to define constructors using class
keyword:
class City {
constructor(name, traveled) {
this.name = name;
this.traveled = false;
}
travel() {
this.traveled = true;
}
}
// Constructor invocation
var paris = new City('Paris', false);
paris.travel();
Copy the code
new City('Paris')
is a constructor invocation. The object initialization is handled by a special method in the class: constructor
, which has this
as the newly created object.
A constructor call creates an empty new object, which inherits properties from constructor’s prototype. The role of constructor function is to initialize the object. As you might know already, the context in this type of call is the created instance. This is next chapter subject.
When a property accessor myObject.myFunction
is preceded by new
keyword, JavaScript will execute a constructor invocation, but not a method invocation.
For example new myObject.myFunction()
: first the function is extracted using a property accessor extractedFunction = myObject.myFunction
, then invoked as a constructor to create a new object: new extractedFunction()
.
4.1. this
in constructor invocation
this
is the newly created object in a constructor invocation
The context of a constructor invocation is the newly created object. It is used to initialize the object with data that comes from constructor function arguments, setup initial value for properties, attach event handlers, etc.
Let’s check the context in the following example:
function Foo () {
console.log(this instanceof Foo); // => true
this.property = 'Default Value';
}
// Constructor invocation
var fooInstance = new Foo();
fooInstance.property; // => 'Default Value'
Copy the code
new Foo()
is making a constructor call where the context is fooInstance
. Inside Foo
the object is initialized: this.property
is assigned with a default value.
The same scenario happens when using class
syntax (available in ES6), only the initialization happens in the constructor
method:
class Bar {
constructor() {
console.log(this instanceof Bar); // => true
this.property = 'Default Value';
}
}
// Constructor invocation
var barInstance = new Bar();
barInstance.property; // => 'Default Value'
Copy the code
At the time when new Bar()
is executed, JavaScript creates an empty object and makes it the context of the constructor
method. Now you can add properties to object using this
keyword: this.property = 'Default Value'
.
4.2. Pitfall: forgetting about new
Some JavaScript functions create instances not only when invoked as constructors, but also when invoked as functions. For example RegExp
:
var reg1 = new RegExp('\\w+');
var reg2 = RegExp('\\w+');
reg1 instanceof RegExp; // => true
reg2 instanceof RegExp; // => true
reg1.source === reg2.source; // => true
Copy the code
When executing new RegExp('\\w+')
and RegExp('\\w+')
JavaScript creates equivalent regular expression objects.
Using a function invocation to create objects is a potential problem (excluding factory pattern), because some constructors may omit the logic to initialize the object when new keyword is missing. The following example illustrates the problem:
function Vehicle(type, wheelsCount) {
this.type = type;
this.wheelsCount = wheelsCount;
return this;
}
// Function invocation
var car = Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount // => 4
car === window // => true
Copy the code
Vehicle is a function that sets type and wheelsCount properties on the context object. When executing Vehicle(‘Car’, 4) an object car is returned, which has the correct properties: car.type is ‘Car’ and car.wheelsCount is 4. You might think it works well for creating and initializing new objects. However this is a window object in a function Invocation (see 2.1.) and Vehicle(‘Car’, 4) is setting properties on the window object – faulty scenario. A new object is not created.
Make sure to use new
operator in cases when a constructor call is expected:
function Vehicle(type, wheelsCount) { if (! (this instanceof Vehicle)) { throw Error('Error: Incorrect invocation'); } this.type = type; this.wheelsCount = wheelsCount; return this; } // Constructor invocation var car = new Vehicle('Car', 4); car.type // => 'Car' car.wheelsCount // => 4 car instanceof Vehicle // => true // Function invocation. Generates an error. var brokenCat = Vehicle('Broken Car', 3);Copy the code
new Vehicle('Car', 4)
works well: a new object is created and initialized, because new
keyword is present in the constructor invocation.
A verification is added in the constructor function: this instanceof Vehicle
, to make sure that execution context is a correct object type. If this
is not a Vehicle
, then an error is generated. This way if Vehicle('Broken Car', 3)
is executed (without new
) an exception is thrown: Error: Incorrect invocation
.
5. Indirect invocation
Indirect invocation is performed when a function is called using .call()
or .apply()
methods.
Functions in JavaScript are first-class objects, which means that a function is an object. The type of this object is Function
.
From the list of methods that a function object has, .call()
and .apply()
are used to invoke the function with a configurable context.
The method .call(thisArg[, arg1[, arg2[, ...]]])
accepts the first argument thisArg
as the context of the invocation and a list of arguments arg1, arg2, ...
that are passed as arguments to the called function.
And the method .apply(thisArg, [args])
accepts the first argument thisArg
as the context of the invocation and an array-like object of values [args]
that are passed as arguments to the called function.
The following example shows the indirect invocation:
function increment(number) { return ++number; } increment.call(undefined, 10); // => 11 increment.apply(undefined, [10]); / / = > 11Copy the code
increment.call()
and increment.apply()
both invoke the increment function with 10
argument.
The main difference between the two is that .call()
accepts a list of arguments, for example myFunction.call(thisValue, 'value1', 'value2')
. But .apply()
accepts a list of values in an array-like object, e.g. myFunction.apply(thisValue, ['value1', 'value2'])
.
5.1. this
in indirect invocation
this
is the first argument of.call()
or.apply()
in an indirect invocation
It’s obvious that this
in indirect invocation is the value passed as first argument to .call()
or .apply()
. The following example shows that:
var rabbit = { name: 'White Rabbit' };
function concatName(string) {
console.log(this === rabbit); // => true
return string + this.name;
}
// Indirect invocations
concatName.call(rabbit, 'Hello '); // => 'Hello White Rabbit'
concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'
Copy the code
The indirect invocation is useful when a function should be executed with a specific context. For example to solve the context problems with function invocation, where this
is always window
or undefined
in strict mode (see 2.3.). It can be used to simulate a method call on an object (see the previous code sample).
Another practical example is creating hierarchies of classes in ES5 to call the parent constructor:
function Runner(name) {
console.log(this instanceof Rabbit); // => true
this.name = name;
}
function Rabbit(name, countLegs) {
console.log(this instanceof Rabbit); // => true
// Indirect invocation. Call parent constructor.
Runner.call(this, name);
this.countLegs = countLegs;
}
var myRabbit = new Rabbit('White Rabbit', 4);
myRabbit; // { name: 'White Rabbit', countLegs: 4 }
Copy the code
Runner.call(this, name)
inside Rabbit
makes an indirect call of the parent function to initialize the object.
6. Bound function
A bound function is a function bind with an object. Usually it is created from the original function using .bind()
method. The original and bound functions share the same code and scope, but different contexts on execution.
The method .bind(thisArg[, arg1[, arg2[, ...]]])
accepts the first argument thisArg
as the context of the bound function on invocation and an optional list of arguments arg1, arg2, ...
that are passed as arguments to the called function. It returns a new function bound with thisArg
.
The following code creates a bound function and later invokes it:
function multiply(number) { 'use strict'; return this * number; } // create a bound function with context var double = multiply.bind(2); // invoke the bound function double(3); // => 6 double(10); / / = > 20Copy the code
multiply.bind(2)
returns a new function object double
, which is bound with number 2
. multiply
and double
have the same code and scope.
Contrary to .apply()
and .call()
methods (see 5.), which invokes the function right away, the .bound()
method returns a new function that it supposed to be invoked later with a pre-configured this
.
6.1. this
in bound function
this
is the first argument of.bind()
when invoking a bound function
The role of .bind()
is to create a new function, which invocation will have the context as the first argument passed to .bind()
. It is a powerful technique that allows to create functions with a predefined this
value.
Let’s see how to configure this
of a bound function:
var numbers = { array: [3, 5, 10], getNumbers: function() { return this.array; }}; // Create a bound function var boundGetNumbers = numbers.getNumbers.bind(numbers); boundGetNumbers(); // => [3, 5, 10] // Extract method from object var simpleGetNumbers = numbers.getNumbers; simpleGetNumbers(); // => undefined or throws an error in strict modeCopy the code
numbers.countNumbers.bind(numbers)
returns a function boundGetNumbers
that is bound with numbers
object. Then boundGetNumbers()
is invoked with this
as numbers
and returns the correct array object.
The function numbers.getNumbers
can be extracted into a variable simpleGetNumbers
without binding. On later function invocation simpleGetNumbers()
has this
as window
or undefined
in strict mode, but not numbers
object (see 3.2. Pitfall). In this case simpleGetNumbers()
will not return correctly the array.
.bind()
makes a permanent context link and will always keep it. A bound function cannot change its linked context when using .call()
or .apply()
with a different context, or even a rebound doesn’t have any effect.
Only the constructor invocation of a bound function can change that, however this is not a recommended approach (for constructor invocation use normal, not bound functions).
The following example declares a bound function, then tries to change its pre-defined context:
function getThis() {
'use strict';
return this;
}
var one = getThis.bind(1);
// Bound function invocation
one(); // => 1
// Use bound function with .apply() and .call()
one.call(2); // => 1
one.apply(2); // => 1
// Bind again
one.bind(2)(); // => 1
// Call the bound function as a constructor
new one(); // => Object
Copy the code
Only new one()
changes the context of the bound function, other types of invocation always have this
equal to 1
.
7. Arrow function
Arrow function is designed to declare the function in a shorter form and lexically bind the context.
It can used the following way:
var hello = (name) => { return 'Hello ' + name; }; hello('World'); // => 'Hello World' // Keep only even numbers [1, 2, 5, 6].filter(item => item % 2 === 0); / / = > [2, 6]Copy the code
Arrow functions bring a lighter syntax, excluding the verbose keyword function
. You could even omit the return
, when the function has only 1 statement.
An arrow function is anonymous, which means that name
property is an empty string ''
. This way it doesn’t have a lexical function name (which would be useful for recursion, detaching event handlers).
Also it doesn’t provide the arguments
object, opposed to a regular function. However this is fixed using ES6 rest parameters:
var sumArguments = (... args) => { console.log(typeof arguments); // => 'undefined' return args.reduce((result, item) => result + item); }; sumArguments.name // => '' sumArguments(5, 5, 6); / / = > 16Copy the code
7.1. this
in arrow function
this
is the enclosing context where the arrow function is defined
The arrow function doesn’t create its own execution context, but takes this
from the outer function where it is defined.
The following example shows the context transparency property:
class Point { constructor(x, y) { this.x = x; this.y = y; } log() { console.log(this === myPoint); // => true setTimeout(()=> { console.log(this === myPoint); // => true console.log(this.x + ':' + this.y); // => '95:165'}, 1000); } } var myPoint = new Point(95, 165); myPoint.log();Copy the code
setTimeout is calling the arrow function with the same context (myPoint object) as the log() method. As seen, the arrow function “inherits” the context from the function where it is defined. If trying to use a regular function in this example, it would create its own context (window or undefined in strict mode). So to make the same code work correctly with a function expression it’s necessary to manually bind the context: setTimeout(function() {… }.bind(this)). This is verbose, and using an arrow function is a cleaner and shorter solution.
If the arrow function is defined in the top most scope (outside any function), the context is always the global object (window
in a browser and process
object in NodeJS):
var getContext = () => {
console.log(this === window); // => true
return this;
};
console.log(getContext() === window); // => true
Copy the code
An arrow function is bound with the lexical context once and forever. this
cannot be modified even if using the context modification methods:
var numbers = [1, 2];
(function() {
var get = () => {
console.log(this === numbers); // => true
return this;
};
console.log(this === numbers); // => true
get(); // => [1, 2]
// Use arrow function with .apply() and .call()
get.call([0]); // => [1, 2]
get.apply([0]); // => [1, 2]
// Bind
get.bind([0])(); // => [1, 2]
}).call(numbers);
Copy the code
A function expression is called indirectly using .call(numbers)
, which makes this
of the invocation as numbers
. The get
arrow function has this
as numbers
too, because it takes the context lexically.
No matter how get
is called, it always keeps the initial context numbers
. Indirect call with other context (using .call()
or .apply()
), rebinding (using .bind()
) have no effect.
Arrow function cannot be used as a constructor. If invoking it as a constructor new get()
, JavaScript throws an error: TypeError: get is not a constructor
.
7.2. Pitfall: defining method with arrow function
You might want to use arrow functions to declare methods on an object. Fair enough: their declaration is quite short comparing to a function expression: (param) => {… } instead of function(param) {.. }.
This example defines a method format()
on a class Period
using an arrow function:
function Period (hours, minutes) {
this.hours = hours;
this.minutes = minutes;
}
Period.prototype.format = () => {
console.log(this === window); // => true
return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);
walkPeriod.format(); // => 'undefined hours and undefined minutes'
Copy the code
Since format is an arrow function and is defined in the global context (top most scope), it has this as window object. Even if format is executed as a method on an object walkPeriod.format(), window is kept as the context of invocation. It happens because arrow function have a static context that doesn’t change on different invocation types. this is window, so this.hours and this.minutes are undefined. The method returns the string: ‘undefined hours and undefined minutes’, which is not the expected result.
The function expression solves the problem, because a regular function does change its context depending on invocation:
function Period (hours, minutes) {
this.hours = hours;
this.minutes = minutes;
}
Period.prototype.format = function() {
console.log(this === walkPeriod); // => true
return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);
walkPeriod.format(); // => '2 hours and 30 minutes'
Copy the code
Walkperiod. format() is a method Invocation on an object (see 3.1.) with the context walkPeriod object.this.hours evaluates to 2 and this.minutes to 30, so the method returns the correct result: ‘2 hours and 30 minutes’.
8. Conclusion
Because the function invocation has the biggest impart on this
, from now on do not ask yourself:
Where is
this
taken from?
but do ask yourself:
How is the
function invoked
?
For an arrow function ask yourself:
What is
this
where the arrow function isdefined
?
This mindset is correct when dealing with this
and will save you from headache.
If you have an interesting example of context pitfall or just experience difficulties with a case, write a comment bellow and let’s discuss!
Spread the knowledge about JavaScript and share the post, your colleagues will appreciate it.
Don’t lose your context 😉
See also the recent popular posts:
When ‘not’ to use arrow functions
How three dots changed JavaScript
JavaScript variables hoisting in details