Author: Dmitri Pavlutin
Reference: https://dmitripavlutin.com/gentle-explanation-of-this-in-javascript/
Concept
- Invocation of a function is executing the code that makes the body of a function, or simply calling the function. For example
parseInt
function invocation is parseInt('15')
. - Context of an invocation is the value of this within function body. For example the invocation of
map.set('key', 'value')
has the context map
. - Scope of a function is the set of variables, objects, functions accessible within a function body.
Invocations
- function invocation: alert(‘Hello World!’)
- method invocation: console.log(‘Hello World!’)
- constructor invocation: new RegExp(’\d')
- indirect invocation: alert.call(undefined, ‘Hello World!’)
this in different invocations
this
is base on the context of calling this function
Function invocation
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
| const numbers = {
numberA: 5,
numberB: 10,
sum: function () {
console.log(this === numbers); // => true
const calculate = () => {
console.log(this === numbers); // => true
return this.numberA + this.numberB;
};
return calculate();
},
};
numbers.sum(); // => 15
|
console.log(this === window);
returns true
only when your running in a browser otherwise it will give an ERR from Node.js
this in a function invocation
this
means windows
1
2
3
4
5
6
7
8
9
| 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; // => 20
|
this in a function invocation, strict mode
this is undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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); // => 20
|
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 its invocation type, but not on the outer function’s context.
this
is windows
for inner function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| const 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 mode
|
⚠️ numbers.sum()
is a method invocation. But calculate()
is a function invocation
There are two ways to fix this:
Use calculate.call(this)
that an indirect invocation of a function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const 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(); // => 15
|
of use arrow function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const numbers = {
numberA: 5,
numberB: 10,
sum: function () {
console.log(this === numbers); // => true
const calculate = () => {
console.log(this === numbers); // => true
return this.numberA + this.numberB;
};
return calculate();
},
};
numbers.sum(); // => 15
|
Method invocation
Example:
1
2
3
4
5
6
7
8
| const myObject = {
// helloFunction is a method
helloFunction: function () {
return 'Hello World!';
},
};
// method invocation
const message = myObject.helloFunction();
|
this in a method invocation
this
is object
who call the function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| // Object
const calc = {
num: 0,
increment() {
console.log(this === calc); // => true
this.num += 1;
return this.num;
},
};
// method invocation. this is calc
calc.increment(); // => 1
calc.increment(); // => 2
// Object inherits a method from its prototype
const myDog = Object.create({
sayName() {
console.log(this === myDog); // => true
return this.name;
},
});
myDog.name = 'Milo';
// method invocation. this is myDog
myDog.sayName(); // => 'Milo'
// class syntax
class Planet {
constructor(name) {
this.name = name;
}
getName() {
console.log(this === earth); // => true
return this.name;
}
}
const earth = new Planet('Earth');
// method invocation. the context is earth
earth.getName(); // => 'Earth'
|
Pitfall: Separating method from its object
⚠️ A method can be extracted from an object into a separated variable like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| function Pet(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`);
};
}
const myCat = new Pet('Cat', 4);
console.log(setTimeout(myCat.logInfo, 1000));
// logs "The undefined has undefined legs"
// or throws a TypeError in strict mode
|
In this code myCat.logInfo
is extracted from object myCat
as a function. Therefore is equal to:
1
2
3
4
| setTimeout(function () {
console.log(this === myCat); // => false
console.log(`The ${this.type} has ${this.legs} legs`);
}, 1000);
|
Now you can see that this
is invocated by function invocation rather than method invocation.
To fix this, Use bind setTimeout(myCat.logInfo.bind(myCat))
or arrow function for logInfo
.
Constructor invocation
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| function Country(name, traveled) {
this.name = name ? name : 'United Kingdom';
this.traveled = Boolean(traveled); // transform to a boolean
}
Country.prototype.travel = function () {
this.traveled = true;
};
// Constructor invocation
const france = new Country('France', false);
// Constructor invocation
const unitedKingdom = new Country();
france.travel(); // Travel to France
// class since ECMAScript 2015
class City {
constructor(name, traveled) {
this.name = name;
this.traveled = false;
}
travel() {
this.traveled = true;
}
}
// Constructor invocation
const paris = new City('Paris', false);
paris.travel();
|
this in a constructor invocation
this
is the newly created object
1
2
3
4
5
6
7
8
| function Foo() {
// this is fooInstance
this.property = 'Default Value';
}
// Constructor invocation
const fooInstance = new Foo();
console.log(fooInstance.property); // => 'Default Value'
|
Pitfall: Forgetting about new
1
2
3
4
5
6
7
8
9
10
| function Vehicle(type, wheelsCount) {
this.type = type;
this.wheelsCount = wheelsCount;
return this;
}
// Function invocation
const car = Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount; // => 4
car === window; // => true PROBLEM!
|
Although it looks good in this code, But actually this is a function invocation not constructor invocation. Since it is not return any error, losing new
may cause potential problem.
The correct way is to prevent the function invocation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| function Vehicle(type, wheelsCount) {
if (!(this instanceof Vehicle)) {
throw Error('Error: Incorrect invocation');
}
this.type = type;
this.wheelsCount = wheelsCount;
return this;
}
// Constructor invocation
const car = new Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount; // => 4
car instanceof Vehicle; // => true
// Function invocation. Throws an error.
const brokenCar = Vehicle('Broken Car', 3);
|
Indirect invocation
Indirect invocation is performed when a function is called using myFun.call()
or myFun.apply()
methods.
myFun.call()
means function be called with given context.
The main difference between the two is that .call()
accepts a list of arguments, for example myFun.call(thisValue, 'val1', 'val2')
. But .apply()
accepts a list of values in an array-like object, e.g. myFunc.apply(thisValue, ['val1', 'val2'])
.
1
2
3
4
5
| function increment(number) {
return ++number;
}
increment.call(undefined, 10); // => 11
increment.apply(undefined, [10]); // => 11
|
this in an indirect invocation
this
is the first argument of .call()
or .apply()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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;
}
const myRabbit = new Rabbit('White Rabbit', 4);
myRabbit; // { name: 'White Rabbit', countLegs: 4 }
|
Bound function
A bound function is a function whose context and/or arguments are bound to specific values.
The different between .call()
and .bind()
is that .bind()
bind context and function to a new function but call()
calling function directly with a given context.
1
2
3
4
5
6
7
8
9
| function multiply(number) {
'use strict';
return this * number;
}
// create a bound function with context
const double = multiply.bind(2);
// invoke the bound function
double(3); // => 6
double(10); // => 20
|
this inside a bound function
this
is the first argument of .bind()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| const numbers = {
array: [3, 5, 10],
getNumbers() {
return this.array;
},
};
let another = {
array: 'Array from another scope',
};
// method invocation
const getNumbersResult = numbers.getNumbers();
console.log(getNumbersResult); // => [3, 5, 10]
// Extract method from object
const simpleGetNumbers = numbers.getNumbers;
// This is a function invocation
console.log(simpleGetNumbers()); // => undefined or throws an error in strict mode
// Create a bound function
const boundGetNumbers = numbers.getNumbers.bind(numbers);
console.log(boundGetNumbers()); // => [3, 5, 10]
// bind to another scope
const anotherBindFunction = numbers.getNumbers.bind(another);
console.log(anotherBindFunction()); // => "Array from another scope"
|
Tight context binding
.bind()
makes a permanent context link
1
2
3
4
5
6
7
8
9
10
11
12
| function getThis() {
'use strict';
return this;
}
const one = getThis.bind(1);
console.log(one.call(2)); // => 1
console.log(one.apply(2)); // => 1
console.log(one.bind(2)()); // => 1
// construct invocation
console.log(new getThis()); // => Object
|
Arrow function
Arrow function is designed to declare the function in a shorter form and lexically bind the context.
Example of arrow function
1
2
3
4
5
6
| const sumArguments = (...args) => {
return args.reduce((result, item) => result + item);
};
console.log(sumArguments.name); // => 'sumArguments'
console.log(sumArguments(5, 5, 6)); // => 16
|
this in arrow function
arrow function is already bind this is the enclosing context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 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);
}
}
const myPoint = new Point(95, 165);
myPoint.log();
|
If use a regular function for setTimeout
will be setTimeout(function(){...}.bind{this})
Another example shows how allow function bind to context.
1
2
3
4
5
6
7
8
9
10
11
12
13
| function test() {
console.log('1.', this);
function inner1() {
console.log('2.', this);
}
let inner2 = () => {
console.log('3.', this);
};
inner1();
inner2();
}
test(); // 1.window 2.window 3.window
new test(); // 1.test 2.window(function invocation) 3.test(function invocation but bind this)
|
If the arrow function is defined in the topmost scope (outside any function), the context is always the global object (window in a browser):
1
2
3
4
5
6
| const getContext = () => {
console.log(this === window); // => true
return this;
};
console.log(getContext() === window); // => true
|
An arrow function is bound with the lexical context once and forever.
1
2
3
4
5
6
7
8
9
10
11
12
13
| const numbers = [1, 2];
(function () {
const get = () => {
console.log(this === numbers); // => true
return this;
};
console.log(this === numbers); // => true
get(); // => [1, 2]
// Try to change arrow function context manually
get.call([0]); // => [1, 2]
get.apply([0]); // => [1, 2]
get.bind([0])(); // => [1, 2]
}.call(numbers));
|
Pitfall: defining method with an arrow function
1
2
3
4
5
6
7
8
9
10
11
12
| 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';
};
const walkPeriod = new Period(2, 30);
walkPeriod.format(); // => 'undefined hours and undefined minutes'
|
In this particular situation, better use regular function.
Conclusion
- Function invocation –> windows
- Method invocation –> Scope where method belongs to
- Constructor invocation –> Object scope
- Indirection invocation –> Depends on your given context
Because the function invocation has the biggest impact 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 is defined?