Overview
this
is a reference to some object whose properties can be accessed inside the function call. This this is the execution context.
First, let's remember how we can basically execute an instruction in code.
There are 4 ways to execute something in JS:
- By calling a function;
- By calling an object method;
- By using a constructor function;
- By an indirect function call.
The first and easiest way to execute something is to call a function.
function hello(whom) {
console.log(`Hello, ${whom}!`)
}
hello('World') // Hello, World!
To execute the function, we use the hello
expression and parentheses with arguments.
When we call a function, the value of this
can only be a global object or undefined
when using 'use strict'
.
Global Object
A global object is, so to say, the root object in a programme.
If we run JS code in a browser, the global object will be window
. If we run code in Node-environment, then global
.
Strict Mode
You could say that a strict mode is a non-punitive way of dealing with legacy.
Strict mode is enabled by using the 'use strict'
directive at the beginning of a block that is to be executed in strict mode:
function nonStrict() {
// To be carried out in a non-strict mode.
}
function strict() {
'use strict'
// To be carried out in a strict mode.
}
You can also set strict mode for the entire file if you specify 'use strict'
at the beginning.
Meaning of this
Let's get back to this
. In the non-strict mode when executed in a browser, this
will be equal to window
when the function is called:
function whatsThis() {
console.log(this === window)
}
whatsThis() // true
The same - if the function is declared inside a function:
function whatsThis() {
function whatInside() {
console.log(this === window)
}
whatInside()
}
whatsThis() // true
And the same - if the function will be anonymous and, for example, called immediately:
;(function () {
console.log(this === window)
})() // true
In the above example, you may notice ;
before the anonymous function. The point is that the existing auto-separated semicolon insertion (ASI) mechanism works only in certain cases, while a string starting with (
is not included in the list of these cases. That's why experienced developers often add ;
in those cases when their code can be copied and added to the existing code.
In strict mode - the value will be undefined
:
'use strict'
function whatsThis() {
console.log(this === undefined)
}
whatsThis() // true
Object Method
If a function is stored in an object, it is a method of that object.
const user = {
name: 'Alice',
greet() {
console.log('Hello, my name is Alice')
},
}
user.greet() // Hello, my name is Alice
user.greet()
is a method of the user
object.
In this case, the value of this
is this object.
const user = {
name: 'Alice',
greet() {
console.log(`Hello, my name is ${this.name}`)
},
}
user.greet() // Hello, my name is Alice
Note that this
is defined at the time the function is called. If you write an object method to a variable and call it, the value of this
will change.
const user = {
name: 'Alice',
greet() {
console.log(`Hello, my name is ${this.name}`)
},
}
const greet = user.greet
greet() // Hello, my name is
When user.greet()
is called through a point, this
equals the object before the point (user
). Without this object, this
equals the global object (in normal mode). In strict mode we would get the error "Cannot read properties of undefined".
To prevent this from happening, you should use bind()
.
Constructor Call
A constructor is a function we use to create objects of the same type. Such functions are like a printing press that creates LEGO parts. Same-type objects are parts, and a constructor is a machine. It sort of constructs these objects, hence the name.
By convention, constructors are invoked with the keyword new
, and also named with a capital letter, and usually with a noun rather than a verb. The noun is the entity that the constructor creates.
For example, if the constructor is going to create user objects, we can call it User
, and use it like this:
function User() {
this.name = 'Alica'
}
const firstUser = new User()
firstUser.name === 'Alica' // true
When the constructor is called, this
equals the freshly created object.
In the User
example, the value of this
would be the object that the constructor creates:
function User() {
console.log(this instanceof User) // true
this.name = 'Alice'
}
const firstUser = new User()
firstUser instanceof User // true
In fact, there is a lot going on "behind the scenes":
- When called, a new empty object is first created and assigned to
this
. - The function code is executed. (Usually it modifies
this
, adds new properties to it). - The value of
this
is returned.
If you schedule all the implicit steps, then:
function User() {
// Happens implicitly:
// this = {};
this.name = 'Alice'
// Happens implicitly:
// return this;
}
The same thing happens in ES6 Classes:
class User {
constructor() {
this.name = 'Alice'
}
greet() {
// ...
}
}
const firstUser = new User()
How not to Forget about the new
When working with constructor functions, it's easy to forget about new
and call them incorrectly:
const firstUser = new User() // ✅
const secondUser = User() // ❌
Although at first glance there is no difference, and it works as if it is correct. But in reality, there is a difference:
console.log(firstUser) // User { name: 'Alice' }
console.log(secondUser) // undefined
To avoid falling into such a trap, you can write a check in the constructor that a new object has been created:
function User() {
if (!(this instanceof User)) {
throw Error('Error: Incorrect invocation!')
}
this.name = 'Alice'
}
// Or:
function User() {
if (!new.target) {
throw Error('Error: Incorrect invocation!')
}
this.name = 'Alice'
}
const secondUser = User() // Error: Incorrect invocation!
Indirect Call
An indirect call is a function call via call()
or apply()
.
Both take this
as their first argument. That is, they allow you to set the context externally, moreover - explicitly.
function greet() {
console.log(`Hello, ${this.name}`)
}
const user1 = { name: 'Alice' }
const user2 = { name: 'James' }
greet.call(user1) // Hello, Alice
greet.call(user2) // Hello, James
greet.apply(user1) // Hello, Alice
greet.apply(user2) // Hello, James
In both cases, in the first call this === user1
, in the second call user2
.
The difference between call()
and apply()
is how they take arguments for the function itself after this
.
call()
accepts arguments in a comma-separated list, apply()
accepts an array of arguments. Otherwise they are identical:
function greet(greetWord, emoticon) {
console.log(`${greetWord} ${this.name} ${emoticon}`)
}
const user1 = { name: 'Alice' }
const user2 = { name: 'James' }
greet.call(user1, 'Hello,', ':-)') // Hello, Alice :-)
greet.call(user2, 'Good morning,', ':-D') // Good morning, James :-D
greet.apply(user1, ['Hello,', ':-)']) // Hello, Alice :-)
greet.apply(user2, ['Good morning,', ':-D']) // Good morning, James :-D
Linking Functions
A standout is bind()
. This is a method that allows you to bind the execution context to a function to determine "in advance and precisely" what value this
will have.
function greet() {
console.log(`Hello, ${this.name}`)
}
const user1 = { name: 'Alice' }
const greetAlex = greet.bind(user1)
greetAlex() // Hello, Alice
Note that bind()
, unlike call()
and apply()
, does not call a function immediately. Instead, it returns another function - bound to the specified context forever. The context of this function cannot be changed.
function getAge() {
console.log(this.age)
}
const howOldAmI = getAge.bind({age: 20}).bind({age: 30})
howOldAmI() // 20
Arrow Functions
Arrow functions do not have their own execution context. They are linked to the closest hierarchical context in which they are defined.
This is useful when we need to pass in an arrow function, for example, a parent context without using bind()
.
function greetWaitAndAgain() {
console.log(`Hello, ${this.name}!`)
setTimeout(() => {
console.log(`Hello again, ${this.name}!`)
})
}
const user = { name: 'Alice' }
user.greetWaitAndAgain = greetWaitAndAgain;
user.greetWaitAndAgain()
// Hello, Alice!
// Hello again, Alice!
With a normal function inside, the context would be lost and we would have to use call()
, apply()
or bind()
to get the same result.
Support ❤️
It took a lot of time and effort to create this material. If you found this article useful or interesting, please support my work with a small donation. It will help me to continue sharing my knowledge and ideas.
Make a contribution or Subscription to the author's content: Buy me a Coffee, Patreon, PayPal.