18 min read
The this keyword refers to the context where a piece of code, such as a function's body, is supposed to run. Usually it's used in object methods, where this refers to the object that the method is attached to, thus allowing the same method to be reused on different objects.
The value of this in JavaScript depends on how a function is invoked (runtime binding), not how it is defined. When a regular function is invoked as a method of an object, this points to that object. When invoked as a standalone function, this typically refers to the global object.
When is the Value of this Set?
Once a function object has been instantiated and the evalaution of code begins, when a function call expression is found foo() the function object's internal [[Call]] method will be called.
10.2.1 [[Call]] ( thisArgument, argumentsList )
The [[Call]] internal method of an ECMAScript function object F takes arguments thisArgument (an ECMAScript language value) and argumentsList (a List of ECMAScript language values) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:
- Let callerContext be the running execution context.
- Let calleeContext be PrepareForOrdinaryCall(F, undefined).
- Assert: calleeContext is now the running execution context.
- If F.[[IsClassConstructor]] is true, then a. Let error be a newly created TypeError object. b. NOTE: error is created in calleeContext with F's associated Realm Record. c. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. d. Return ThrowCompletion(error).
- Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
- Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)).
- Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
- If result is a return completion, return result.[[Value]].
- Assert: result is a throw completion.
- Return ? result.
10.2.1.1 PrepareForOrdinaryCall ( F, newTarget )
The abstract operation PrepareForOrdinaryCall takes arguments F (an ECMAScript function object) and newTarget (an Object or undefined) and returns an execution context. It performs the following steps when called:
- Let callerContext be the running execution context.
- Let calleeContext be a new ECMAScript code execution context.
- Set the Function of calleeContext to F.
- Let calleeRealm be F.[[Realm]].
- Set the Realm of calleeContext to calleeRealm.
- Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
- Let localEnv be NewFunctionEnvironment(F, newTarget).
- Set the LexicalEnvironment of calleeContext to localEnv.
- Set the VariableEnvironment of calleeContext to localEnv.
- Set the PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]].
- If callerContext is not already suspended, suspend callerContext.
- Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
- NOTE: Any exception objects produced after this point are associated with calleeRealm.
- Return calleeContext.
10.2.1.2 OrdinaryCallBindThis ( F, calleeContext, thisArgument )
The abstract operation OrdinaryCallBindThis takes arguments F (an ECMAScript function object), calleeContext (an execution context), and thisArgument (an ECMAScript language value) and returns unused. It performs the following steps when called:
- Let thisMode be F.[[ThisMode]].
- If thisMode is lexical, return unused.
- Let calleeRealm be F.[[Realm]].
- Let localEnv be the LexicalEnvironment of calleeContext.
- If thisMode is strict, then a. Let thisValue be thisArgument.
- Else, a. If thisArgument is either undefined or null, then i. Let globalEnv be calleeRealm.[[GlobalEnv]]. ii. Assert: globalEnv is a Global Environment Record. iii. Let thisValue be globalEnv.[[GlobalThisValue]]. b. Else, i. Let thisValue be ! ToObject(thisArgument). ii. NOTE: ToObject produces wrapper objects using calleeRealm.
- Assert: localEnv is a Function Environment Record.
- Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized.
- Perform ! BindThisValue(localEnv, thisValue).
- Return unused.
How this value is set at runtime
The value of this depends on how the function is invoked, not where it's declared. Here the greet function is declared in the global scope and it's also invoked in the global scope, but notice how the value of this is set to obj using the call method.
const obj = {
name: 'david'
}
function greet() {
console.log(`hi ${this.name}`)
}
greet.call(obj) // hi davidThe value of this can also be passed to another function.
const obj = {
name: 'david'
}
function greet() {
bar(this)
}
function bar(ctx) {
console.log(`hi ${ctx.name}`)
}
greet.call(obj) // hi davidBelow we will go over the different ways that the value of this is determined.
Default Binding
The default this binding is created whenever a function is invoked in global scope. Note that in strict mode you will get undefined.
function foo() {
console.log(this) // window
}
foo() // here the scope is globalImplicit Binding and Object Methods
This can be confusing, it's often explained wrongly that an object owns a method, then adding to the confusion it's said that you should only use normal functions instead of arrow functions because arrow functions do not create their own this.
The value of this inside of an object is not the object itself, rather it's the same value as this outside of the object.
// this === window
const obj = {
foo: this === window,
name: 'david',
// this === window
greet: function() {
// this === window
return `hi ${this.name}`
},
greetAgain: () => {
// this === window
return `hi ${this.name}`
}
}
console.log(obj)
/*
const obj = {
foo: true,
name: 'david',
greet: function() {
return `hi ${this.name}`
},
greetAgain: () => {
return `hi ${this.name}`
}
}
*/The only difference is how the function is invoked as we already mentioned. To call the greet() method we use dot notation. At the time of invocation, the value of this is determined by what is on the left hand side of the dot, which in more technical terms is called a context object.
Here when greet() is called, it's preceded by a reference to a context object called obj which will be used as the value of this.
obj.greet() // logs 'hi david'The above code could be rewritten as follows:
function greet() {
return `hi ${this.name}`
}
const obj = {
name: 'david',
greet
}Now we can see there is nothing special about an object method.
Below note that if there is a chain, only the last object property reference matters to the function call.
function greet() {
console.log(`hi ${this.name}`)
}
const obj = {
name: 'john',
obj2
}
const obj2 = {
name: 'david',
greet
}
obj.obj2.greet() // `hi david`Explicit Binding
Explicit binding happens when using call(), apply(), and bind().
This is when we state explicitly what the value of this will be. call() and apply() are basically the same, just the way you pass arguments to them differs. call() accepts a comma seperated list, whereas apply() accepts an array.
function greet() {
return `hi ${this.name}`
}
const obj = {
name: 'john',
greet
}
const obj2 = {
name: 'david',
greet
}
obj.greet() // hi john
obj.greet.call(obj2) // hi davidImplicitly Lost
This is a common source of confusion for developers. Let's say you have the following code and you want to save an object's method so you can call it later.
const obj = {
name: 'david',
greet() {
return `hi ${this.name}`
}
}
const greetMethodForLater = obj.greet
greetMethodForLater() // 'hi '
// 'this' is lostWe stored a reference to the obj.greet method in the greetMethodForLater variable and when we invoked it, it was a normal undecorated function invocation in global scope. When greetMethodForLater() is invoked, there is no context object and this does not refer to obj.
In this situation we need to use bind, call will invoke a function immediately with the specified this value, bind will return a new function with its this value set for later use.
const obj = {
name: 'david',
greet() {
return `hi ${this.name}`
}
}
const greetMethodForLater = obj.greet.bind(obj)
greetMethodForLater() // 'hi david'bind two times in a row.function greet() {
console.log(`hi ${this.name}`)
}
const obj1 = {
name: 'john'
}
const obj2 = {
name: 'david'
}
const boundFn = greet.bind(obj1)
boundFn() // 'hi john'
const boundFn2 = boundFn.bind(obj2)
boundFn2() // 'hi john'Callbacks
Callbacks are another way we can lose the value of this. The below example doesn't work as we already mentioned because there is no context object, it's just a normal function invocation.
const obj = {
name: 'david',
greet() {
return `hi ${this.name}`
}
}
const greetMethodForLater = obj.greet
greetMethodForLater() // `hi `This same thing happens with callbacks. Inside of foo, obj.greet is being assigned to callbackFn.
function foo(callbackFn) {
callbackFn()
}
const obj = {
name: 'david',
greet() {
console.log(`hi ${this.name}`)
}
}
foo(obj.greet)The reason this happens is because inside of the function foo what is happening is that obj.greet is passed in and assigned to callbackFn.
function foo(callbackFn) {
// callbackFn = obj.greet
callbackFn()
}
const obj = {
name: 'david',
greet() {
console.log(`hi ${this.name}`)
}
}
foo(obj.greet)Here is a more advanced example utilizing the same concept.
Let's see how the forEach would work. Pay attention to the highlighted line, we have to bind this
function forEach(array, callbackFn) {
for (let i = 0; i < array.length; i++) {
callbackFn(array[i], i, array)
}
}
const obj = {
name: 'david',
tasks: ['eat', 'clean'],
listTasks(task) {
console.log(`${this.name} must ${task}`)
}
}
forEach(obj.tasks, obj.listTasks)
// 'must eat'
// 'must clean'This example is a little bit harder but it is a great example to understand why arrow functions can be so useful. Now inside the forEach function let's see what it looks like at invocation time.
function forEach(array, callbackFn) {
array = ['eat', 'clean']
callbackFn = obj.listTasks
for (let i = 0; i < array.length; i++) {
callbackFn(array[i], i, array)
}
}For each iteration of the loop, the callback function is invoked but without a context object. We could use bind and pass in a callback that has had its this value bound like this obj.listTasks.bind(obj).
function forEach(array, callbackFn) {
for (let i = 0; i < array.length; i++) {
callbackFn(array[i], i, array)
}
}
const obj = {
name: 'david',
tasks: ['eat', 'clean'],
listTasks(task) {
console.log(`${this.name} must ${task}`)
}
}
forEach(obj.tasks, obj.listTasks.bind(obj))But what if we wanted to pass the callback as an inline function like usually is the case?
function forEach(array, callbackFn) {
for (let i = 0; i < array.length; i++) {
callbackFn(array[i], i, array)
}
}
const obj = {
name: 'john',
tasks: ['eat', 'clean'],
listTasks() {
forEach(
this.tasks,
function(task, index, array) {
console.log(`${this.name} must ${task}`)
}.bind(this)
)
}
}The code would look like this inside of the forEach function.
function forEach(array, callbackFn) {
array = ['eat', 'clean']
// callbackFn has been assigned to a function that
// has already been bound to obj
callbackFn = function(task, index, array) {
console.log(`${this.name} must ${task}`)
}
for (let i = 0; i < array.length; i++) {
callbackFn(array[i], i, array)
}
}Many developers find this confusing because they don't first learn the fundamentals, like execution context for example. So rather than trying to understand how this actually works, you will often see code being written trying to make use of lexical scope rather than using bind like we did above. The reason this happens is because anytime function() {} is invoked it creates a new value of this which normally is just the global value if a context object is not used or if call, apply, or bind are not being used.
The callback below is a normal function and not an arrow function so it creates its own this value which is the window if you run this in a console. For that reason we need to capture the value of this lexically with _this or use bind.
const obj = {
name: 'david',
tasks: ['eat', 'clean'],
listTasks() {
const _this = this
this.tasks.forEach(function(task) {
console.log(`${_this.name} must ${task}`)
})
}
}Here is an example using bind. This code is unnecessary because we can just use an arrow function which will inherit the this value of the surrounding scope.
const obj = {
name: 'david',
tasks: ['eat', 'clean'],
listTasks() {
this.tasks.forEach(function(task) {
console.log(`${this.name} must ${task}`)
}.bind(this))
}
}Arrow Functions
Arrow functions inherit their this value from the surrounding lexical scope, they do not create their own this value unlike regular functions. The value of this inside the object is window in the case of a browser. When obj.foo() is called, the value of this is set to the context object, obj, and when obj.bar() is called, because arrow functions do not create a new value of this, the value of this is the surrounding lexical scope which is the window object. Regular functions do define their own this value which is why code such as obj.foo() works.
// this === window
const obj = {
// this === window
x: 1,
foo() {
console.log(this.i, this)
},
bar: () => console.log(this.i, this)
}Here is another example.
const obj = {
name: 'david',
tasks: ['sleep', 'clean'],
listTasks() {
// obj is the context object at invocation time
// obj is the `this` value the arrow function inherits
this.tasks.forEach(task => {
console.log(`${this.name} must ${this}`)
})
}
}
obj.listTasks()Remember, arrow functions do not create their own this. A normal function() {} has a this value can that be changed implicitly with dot or bracket notation or explictly using call, apply, or bind.
We noted that arrow functions capture this at call time so lets take a look at another example.
function greet() {
return (name) => {
console.log(`hi ${this.name}`)
}
}
const obj = {
name: 'david'
}
const obj2 = {
name: 'john'
}
const foo = greet.call(obj)
foo() // 'hi david'The reason this happens is because const foo = greet.call(obj) saves the returned function with a captured this which is set to obj. When foo() is invoked, it is invoking the arrow function that was returned from greet().
foo.call(obj2)
// 'hi david'
// not 'hi john' as we might expectThis is due to the fact that the arrow function captured the value of this at call time. When we first called greet.call(obj) the value of this was captured as obj and the arrow function was returned. Any attempt to change the this value of the arrow function will fail.
Arrow functions inherit the lexical context from where they are defined which means this code will not work because of where the arrow function was defined, not where it's used.
Here the value of this the arrow function captures is the window.
const logTask = task => {
console.log(`${this.name} must ${task}`)
}
const obj = {
name: 'john',
tasks: ['clean', 'eat'],
listTasks() {
this.tasks.forEach(logTask)
}
}
obj.listTasks()
// must clean
// must eatHere the this value the arrow function captures is the context object that was used when obj.listTasks() is invoked.
const obj = {
name: 'david',
tasks: ['clean', 'eat'],
listTasks() {
this.tasks.forEach(task => {
console.log(`${this.name} must ${task}`)
})
}
}
obj.listTasks()
// david must eat
// david must cleanDo call, apply and bind have a higher priority than using a context object?
If there are multiple rules such as using an implicit binding and an explicit binding one must have a higher priority over the other. Below when invoking obj.greet() 'hi john is logged to the console as we would expect, and invoking obj.greet.call(obj2) logs 'hi david'. This means when we inspect a call site using the execution context stack in our dev tools, if using call, apply or bind, this has a higher priority than just using a context object, i.e. obj.greet().
const obj = {
name: 'john',
greet() {
return `hi ${this.name}`
}
}
const obj2 = {
name: 'david',
greet() {
return `hi ${this.name}`
}
}
obj.greet() // 'hi john'
obj.greet.call(obj2) // 'hi david'new Operator
What happens if we use the new operator with an object?
function setAge(age) {
this.age = age
}
const obj = {
name: 'david'
}
// bar is hard bound to obj
const foo = setAge.bind(obj)
foo(30)
console.log(obj.age) // 30
// here foo is called with the new keyword and baz.age === 40 not 30 as we might expect
const bar = new foo(40)
console.log(bar.age) // 40The new keyword has the highest precedence when determining the value of this. When you examine the call site, ask yourself, is the function using the new binding? if not, is the function using explicit binding? if not, is the function called with implicit binding? If the answer is still no, then we know that the default binding applies.
Review
thisis determined at function invocation time.- Regular functions create a new value of
thiswhen they are invoked, this iswindowin the case of the browser. - Arrow functions do not create a new
this, they inherit thethisof the surrounding lexical scope. - The
thisvalue is created for a regularfunction() {}in three ways:- Implicitly -
contextObj.foo() - Explictly -
foo.call(someObj) - With the
newoperator -new someConstructorFunction()
- Implicitly -
- The order of precendence in determing the the value of
thisoccurs in this order from highest to lowest.newoperatorcall,apply, andbind- Using a context obj, i.e.
contextObj.foo() - Default binding, ie.
foo()