Determining the value of `this` in JS (Flowchart included)
May 13, 2021
JavaScriptFew weeks before, while I was sharing my React
knowledge with one of my friend, we stumbled upon an issue, in which I found my mental model on this
was wrong 🙈😬. Thereafter, I went through different blog posts, tutorials and, it took me some time to understand the principles behind different this
bindings. I think the knowledge worth sharing. So here we go.
Table of Contents
Different ways to invoke a function (different
this
bindings)Function invocation using the
new
keyword- Value of
this
inside ES6 class constructor - Value of
this
inside derived class constructor - Value of
this
inside instance fields - Value of
this
inside instance methods - Value of
this
inside base instance method when accessing through derived instance - Value of
this
when accessing through thesuper
keyword - Value of
this
inside static fields - Value of
this
inside derived static fields - Value of
this
inside static methods - Value of
this
inside base static methods
- Value of
Flowchart illustrating the steps in determining the value of this
Introduction
this
in JavaScript can be very confusing if we don't understand the concepts behind it. Firstly, it does not work like the familiar "lexical scope". Secondly, it behaves very differently compared to other Object Oriented Programming Languages like C# and Java. However, it is not yet another corner case of the language. Vote of thanks
I am thankful to Kyle Simpson and the FrontEndMasters team for the awesome Deep JavaScript Foundations - v3 course. While, this post discusses this
deeply, the principles I’ve learnt from the course are very valuable, helpful and laid me a strong foundation. Thank you sir!
I shared this post in r/JavaScript
subreddit, and a generous person from the community gave some valuable feedback. I’ve updated the post accordingly. Thank you so much senocular 🙏🤗😘.
What is this
If the question is ‘What is this
in C#
or Java
?’, the answer is simple. this
is a keyword that points to the current instance of the class. Using this
we can access the instance properties, methods, and accessible properties and methods from the inheritance chain. That’s it, deterministic, sweet, and simple.
However, we can’t approach this
in JavaScript with the same mental model. Unlike in Object Oriented Programming languages, this
in JS is not deterministic but flexible. It can take different values depending on how the function is invoked.
Ok, back to the question. What is this
in JS?
this
is only a part of the execution context. I think saying, "normally this
points to the current Environment Record
would be more appropriate". I'm not sure though. Please feel free to share your thoughts in the comments.Anyway, let’s forget about giving a special name to this
and let’s try to understand it by the different values it can take, and the logic behinds it.
Demonstrating the dynamic nature of this
Before diving into the nitty-gritty details about this
, let’s see some contrived examples where this
in a function takes different values depending on how the function is invoked.
In the below example, we invoke the same function logThis
in different ways (only the ‘how’ part changes). I’ve commented out the logged this
values right next to each statement. See if your understanding aligns with them. Also, feel free to copy the code into the browser developer console and play around with it. If anything doesn’t make sense to you, don’t worry, by the end of this post, you’ll be able to determine the value of this with a smile 😀, well that’s the goal of this post.
function logThis(description) {// debuggerconsole.log(description, this)}let obj = {name: "obj",logThis,}let anotherObj = {name: "anotherObj",logThis,}logThis("normal function invocation in non-strict mode") //globalThis (window in browser)obj.logThis("invoke as a method of `obj`") //objanotherObj.logThis("invoke as a method of `anotherObj`") //anotherObjlet logThisVar = obj.logThislogThisVar("assign method of obj to a variable and invoke it") //globalThis (window in browser)logThis.call(obj, "invoke using .call") //objnew logThis("invoke the function using new") //{}
What values does this
take
In the global scope(outside any function), `this` points to the global object.
globalThis
is a special property that we can use to get the global this
value(the global object) regardless of the environment(browser, node, worker).var doTopLevelModuleVariableNotPolluteGlobalScope = true
, well that variable is only available in that module scope unless exported. That's the whole point of modules, right? Encapsulation!this
points to an object. It can be the global object, an object we created, a DOM element or a new object created on the fly for us by the JS engine -- some object.this
can take primitive values too. Later we'll see how we can pass any value to this
using explicit binding(Though we can set primitive values to this
, I don't see a good reason why one might want to do that 🤔).this
inside arrow functions
Arrow functions do not define their own this
. So the concept of binding a value to this
does not apply. In other words, inside arrow functions, this
is just a variable that does not exist in the function’s scope.
What do we do if we come across a variable that is not in the current scope? Aha, we check if the variable exists in the parent scope. And, if the parent scope also does not have that variable? No problem, we look at its grand parent’s scope. Similarly, we follow the scope chain, until we hit the root of the scope chain which is the global scope where this
is set to the global object. (Strict mode/non-strict mode doesn’t matter).
this
can take different values, we should be aware that this
inside an arrow function can also refer to different objects.this
hardbound to the parent's this
. It's a wrong mental model!this
binding through which we can, well, explicitly set any value to this
. This is not applicable to arrow functions. Do note that, any concept around binding a value to this
is not applicable to arrow functions because they don't define their own this
to bind some value to it!this
in the enclosing scope point to(lexically resolved).If the term “lexical” is not clear to you, we can think of it as “static”. So we can translate “lexical scope” as “static scope”. Ok, I think I’m not making any sense.
Let me approach differently.
Look through the below example. What do you think will be logged when we call sayHelloWr
?
var name = 'panda';function sayHello() {console.log(`Hi, my name is ${name}`)}function sayHelloWr() {var name = 'karady'sayHello()}sayHello() // Hi, my name is pandasayHelloWr() // Hi, my name is ???
If you guessed “Hi, my name is panda”, 🥂, you can skip to the next section.
JavaScript is a two-pass system, in the first pass, a plan for which variable go into which scope is fixed(static). In the second pass, whenever we refer a variable, which variable to take is decided based on the plan created in the first pass. In other words, a variable-scope mapping is created based on where things are defined.
So since, arrow functions do not define their own this
, since it’s going to be resolved from the scope chain lexically, where the arrow function is defined matters!
this
, I found some people use the term "lexical this". I think they say so since, this
inside arrow functions are lexically resolved. Do note that such term is only applicable for arrow functions.this
inside non-arrow functions
Inside non-arrow functions, which object this
refers to entirely depends on how we invoke the function/method. We can’t just look at the function definition(where the function is defined) and say what value this
will take. We need to look at how the function is invoked.
this
is determined in based on the call site.Different ways to invoke a function (different this
bindings)
So, we’ve seen, the value of this
in a function depends on the call site, “how we/something else invokes the function”
But, you might ask, how else can we invoke a function other than how we normally do? Well, it turns out there are four main ways to invoke a function.
- Invoke a function with default this binding
- Invoke a function with implicit this binding
- Invoke a function with explicit this binding
- Invoke a function using the
new
keyword
Function invocation with default this
binding
In this category, we directly invoke the function using its name.
In this scenario, the this
value is set to the global
object in sloppy(non-strict) mode.
In strict mode, this
is undefined
.
Here is a contrived example.
var name = 'global'; // var outside any function will attach to the global object, this is same as saying `this.name= 'global'`function sayHello() {console.log(`Hi, my name is ${this.name}`);}function sayHelloStrict() {'use strict';console.log(`Hi, my name is ${this.name}`);}const panda = {name: 'panda',sayHello,}sayHello(); // Hi, my name is globalconst pandaSayHello = panda.sayHello;pandaSayHello(); // Hi, my name is globalsayHelloStrict(); // TypeError (this is undefined)
Note that this
in pandaSayHello
also resolves to the global object instead of the panda
object. This is because pandaSayHello()
invocation falls into the default this
binding category and not into the implicit this
binding category.
Function invocation with implicit this
binding
In this approach, we access the method using the object dot notation or using the square brackets.
function sayHello() {console.log(`Hi, my name is ${this.name}`);}let panda = {name: 'panda',sayHello}let karady = {name: 'karady',sayHello}panda.sayHello(); // Hi, my name is pandakarady.sayHello(); // Hi, my name is karadypanda['sayHello'](); // Hi, my name is panda
In this scenario, this
inside the method is implicitly set(or bound) to the object on which the method is invoked.
So when we say panda.sayHello()
, we’re asking the JS engine to invoke the function sayHello
with its this
bound to the panda
object. Thus, this.name = panda.name = ‘panda’
.
Function invocation with explicit this
binding
JavaScript offers APIs that allow us to explicitly set the value for this
. This is useful when passing a this-aware function as a callback or when authoring libraries. Remember, when we pass a function as an argument, the passed function gets assigned to a new variable. So, now this
will rely on how that variable is invoked, right?
We can use call
, apply
methods to invoke a function with an explicit this
value.
If we want to explicitly set the this
value but defer the execution(i.e execute the function later), then we can use the bind
method.
Let’s see some examples for each category.
Explicit this
binding using ‘call’ or ‘apply’
function sayHello() {console.log(`Hi, my name is ${this.name}`);}let panda = {name: 'panda',sayHello,}let karady = {name: 'karady',sayHello,}sayHello.call(panda); // Hi, my name is pandasayHello.apply(karady); // Hi, my name is karadykarady.sayHello.call(panda); // Hi, my name is panda
Note that we invoke call
on the this
aware function using the dot notation as if it’s a method on it.
In this scenario, this
is initialized to the first argument passed to the call
, apply
methods.
Also, explicit binding takes precedence over implicit binding. Thus karady.sayHello.call(panda)
will log “Hi, my name is panda”.
Explicit this
binding using bind
This is also called hard binding. In contrast to call
, apply
methods, bind
does not immediately invoke the function. It returns a new function instead.
The bind
method internally uses apply
to achieve hard binding.
function sayHello() {console.log(`Hi, my name is ${this.name}`);}let panda = {name: 'panda',}const sayHelloPanda = sayHello.bind(panda);sayHelloPanda(); // Hi, my name is panda
Overriding this
of a hardbound function
Say we have a hardbound function* and for some reason we want to set a new this
value.
this
value is explicitly set using the bind
method 👍None of the above approaches(i.e. invoking the hardbound function with implicit/explicit/default bindings) will work (yes, calling bind
again also won’t work)
Later, we’ll see about invoking a function using the new
keyword. Doing so will override the this
value to a new object who is linked to the object pointed by function’s prototype
property. This is like resetting the this
value to the original one. More on this later.
Explicitly binding primitive values to this
In strict mode, `this` takes what we pass to `.call/.apply or .bind`. There is no special rule in strict mode.
However, in sloppy mode, explicit binding of primitive values involves boxing.
Primitive value means, a value that is not an object. So, there are no members(properties, methods, getters, .etc).
For example,
var age = 20 // variable age is assigned a primitive value
In sloppy mode, when we try to explicitly bind a primitive value to this
, the value is automatically converted to an object using the corresponding constructor *. Also, when explicitly binding null
/undefined
, this
will point to the global object.
.call/.apply or .bind
then it is converted to an object using new Number(1)
. If we pass false
, it is converted to object using new Boolean(false)
. This process is also called boxing (coercion of primitive value to object).Function invocation using the new
keyword
We can use the new
keyword to invoke a function. The new
keyword hijacks the function call and does the following things.
- creates a new empty object
- links* that empty object to another object pointed by the (constructor)function's
prototype
property** - calls the function with
this
set to the new object (usually the function modifiesthis
by adding new properties) - returns
this
if the constructor function does not return any object (if the constructor function returns a primitive value, that will be ignored and, the newly createdthis
will be returned instead)
Each function object, automatically gets an external accessible
prototype
property. It's an important property. It plays a mojor role when it comes to inheritance in JavaScript.[[Prototype]]
. The new
operator sets this property to point to the object pointed by function's prototype
property.new
as invoking the function using call
with the first argument being an empty object. i.e. functionName.call({})
Due to the linking in step 2, by the time we access this
, all the properties and functions added to the prototype chain can be accessed through it.
Below is a contrived example.
function sayHello() {console.log(`Hi, my name is ${this.name}, ${this.sing()}`);}// if this part is confusing, hold on plz// in the next section// we'll discuss about adding methods to prototypesayHello.prototype.sing = function() {return 'Jingle bells, jingle bells'}new sayHello(); // Hi, my name is undefined, Jingle bells, jingle bells
It might seem rather unusual to invoke a function using new
. It’s common with constructor functions and ES6 classes. I’ve put a contrived example, just to show, it’s the new
keyword that is doing the magic.
In the following section, we’ll take a close look at a typical constructor function. Though, it’s the new
keyword, that is doing the this binding
and, there is no special this binding
rule within a constructor function, understanding constructor functions, will help us to better understand the new
operator and why it exists.
A typical constructor function
Constructor functions are just normal functions. They’re called so since they’re used to constructing new “objects”. Constructor functions are usually this-aware and intended to be called using the new
operator.
Well, in JavaScript we already have various ways to create new objects, so why do we need a constructor function and another operator to invoke it.
The challenge here is not just to define a blueprint to construct new objects in a reusable way but, how do we mimic an Object-Oriented Programming language? Mainly how are we going to allow inheritance. Constructor functions, the new
operator, the dynamic nature of this
, the [[Prototype]]
prototype
, constructor
properties etc. altogether, solve this problem.
Back to constructor functions.
When naming a constructor function, to signal others that it’s a “constructor” function, we use PascalCasing. So that they know, ok I’ve to invoke it using new
.
Typically, we don’t explicitly return anything from a constructor function. The new
operator will implicitly return the this
object in that case.
Below is a contrived example of a typical constructor function and, some explanation about it.
function Person(firstName, lastName) {// where does `this` come from?this.firstName = firstNamethis.lastName = lastName// we don't return anything?}// what is `Person.prototype`?Person.prototype.sayHello = function() {// hmmm, what value would `this` takeconsole.log(`Hi, my name is ${this.firstName} ${this.lastName}`)}let panda = new Person('Panda', 'Karady')panda.sayHello() // Hi, my name is Panda Karady// ***************************************// below is the same code snippet// with some explanation// ***************************************function Person(firstName, lastName) {// constructor functions are intended to be invoked using `new`// behind the screen// before executing the function// the `new` operator does some extra stuffs// it creates an empty object// links it to another object// then invokes the function with its `this` set to the new object// usually, inside the function,// we modify `this` by adding properties to it// for examplethis.firstName = firstNamethis.lastName = lastName// however,// normally, we don't add methods to the instance directly though// directly as in `this.sayHello = function(){...}`// why?// because we have a better way// to not to redefine methods on every instance// to share a method across all instances}Person.prototype.sayHello = function() {// so we add methods to another shared object// it's not just another random object// it's pointed by the function's prototype property// in our example - `Person.prototype`// let's call it "prototype object"// any instance created by invoking a function using `new`// will have a hidden linkage([[Prototype]]) to the "prototype object"console.log(`Hi, my name is ${this.firstName} ${this.lastName}`)// but, wait,// if all instances share the same method,// what value would `this` take?// `this` in JavaScript is dynamic!// to determine the value of `this`// we need to look at the call site}let panda = new Person('Panda', 'Karady')// the panda variable points to// the (implicitly)returned `this` valuepanda.sayHello() // Hi, my name is Panda Karady// remember,// `sayHello` is not a direct member of the returned instance(`this`)// it's resolved from the prototype chain (the hidden [[Prototype]] linkage)// and, if it matters,// lexical scope and prototype chain are different things
this
inside ES6 classes
ES6 class syntax offers a declarative approach to define constructor functions. People with OOP language background will find the syntax familiar and easier to get started with. However, under the hood, an ES6 class is a function and, the concepts of prototypical inheritance, dynamic this
binding etc. still holds true.
There’re some differences though. For example classes are not hoisted, they default to strict mode, and there is no strict equivalent to super
keyword in pre ES6 etc. Albeit these differences, under the hood, ES6 class is still a function. So, when creating a new instance from a class, ultimately we’re calling the function with the new
operator.
In the previous sections, we’ve discussed how the new
operator sets this
to a newly created(linked) object. The same principle applies to ES6 classes as well. However, there are some inconsistencies on what value this
would take inside different elements of a class. In the following sections, we’ll look about it and, the reason behind it.
Value of this
inside ES6 class constructor
Similar to other OOP languages, ES6 class can have optional constructor. It’s a function. It’s name should be constructor (in OOP languages, constructor functions take the class name instead, also unlike in OOP languages, in JavaScript, constructors can explicitly return some value).
If we don’t specify a constructor
, JS engine automatically creates one for us. If we explicitly specify a constructor
, JS engine will use that instead. constructor
is useful to perform initialization logic.
Below is a contrived ES6 class example with constructor
.
class Base {constructor(name) {this.name = name// doMoreStuff}}
The following is the equivalent constructor function representation.
function Base(name) {this.name = name// doMoreStuff}
So, when asking “what is the value of this
inside an ES6 class constructor
”, essentially, we’re asking, what is this
inside the equivalent mapping constructor function body.
Well, we know the answer. The function is supposed to be invoked using the new
operator. So, this
will point to an empty object, that is linked to the prototype chain.
Value of this
inside derived class constructor
The new
operator behaves differently when it comes to derived classes.
Usually, the new
operator creates an empty object and sets it to this
, so, by the time we do this.someKey = someValue
, this
is already initialized to an empty object. However, inside derived constructors, the new
operator expects the parent constructor to set a value to this
. So, until the parent constructor is executed, the value of this
inside the derived constructor will be undefined
. Thus, any attempt to set a property on this
(which is undefined
) will result in ReferenceError
.
To avoid this problem, we are asked to do super()
, before doing anything with this
inside the derived constructor.
What is this super()
thing? What is it doing behind the screen? How does it affect the this
value?
We can think super()
as invoking the parent constructor using the new operator and, setting the returned value to this
. i.e. this = new Base()
Such translation is not exactly correct, but will help us to understand the value of this
inside the derived constructor. Also, let’s imagine Base
as a normal constructor function instead of a class. With that loose assumptions, let’s break down the steps involved in this = new Base()
- The
new
operator creates an empty object - The
new
operator links it to the prototype chain - The
new
operator invokes theBase
constructor function with itsthis
set to the newly created linked object - The function usually adds some properties to
this
- If the function returns a reference type(i.e. non-primitive value), the
new
operator returns that value, otherwise, thenew
operator returnsthis
- Returned value is assigned to
this
variable insideDerived
constructor
So, usually, this
inside derived constructor will be an object linked to the prototype chain and will have all the properties defined in the parent constructor.
But, the catch here is, what happens if the parent constructor explicitly returns a reference type value other than its this
? What would be the value of this
inside the derived constructor in such case? You get the point right, this
will point to the returned object which might be linked to some different prototype chain.
this
in the derived constructor.Below is a contrived example that illustrates how base constructor affects the value of this
inside the derived constructor.
class Base {instanceFieldInBase = 'base'constructor() {return [1, 2, 3]}}class Derived extends Base {constructor() {super()// though, not strictly correct// we can think of super() as this = new Base()console.log(this instanceof Base) // falseconsole.log(this instanceof Array) // true}}let derived = new Derived()console.log(derived instanceof Base) // falseconsole.log(derived instanceof Derived) // falseconsole.log(derived instanceof Array) // true
Value of this
inside instance fields
Instance fields become properties of `this`.
Suppose we’ve defined an instance field like below.
class Base {instanceField = thisanotherInstanceField = 'hello'}
It’s really a shortcut to adding a property to this
inside the constructor
.
class Base {constructor() {this.instanceField = thisthis.anotherInstanceField = 'hello'}}
The equivalent constructor function would be.
function Base() {this.instanceField = thisthis.anotherInstanceField = 'hello'}
We know the value of this
inside the constructor function body when invoking it using the new
operator. this
will point to an empty object that is linked to the prototype chain.
If it’s a derived class, the this
object will also have the instance fields from the base class as its property.
If the base constructor explicitly returns a reference type for some reason, then this
will point to that returned value.
this
binding is applicable only inside functions. If we assign this
to a variable or to an object property, then, they will continue to point to that same this
object no matter what.constructor
modify the same object that is returned as a result of the new Base()
right?Value of this
inside instance methods
Instance method of an ES6 class, becomes member of the object pointed by the prototype property of the mapping constructor function.
Below is an example of ES6 class instance method, followed by the equivalent constructor function representation.
class Base() {instanceMethod() {console.log(this)}}
function Base() {}// instance methods become property of the prototype object// that way the same method can be shared with all instances// since all instances are linked to the prototype chain// they'll be able to access the methodBase.prototype.instanceMethod = function() {console.log(this)}
We usually invoke the method as if it’s a direct member of the instance. This falls into the implicit this binding
category and, in such case, this
will point to the instance on which the dot notation is used.
Value of this
inside base instance method when accessing through derived instance
Due to the prototypical inheritance we can access the base instance methods through the instance of the Derived class. The base instance methods will be part of the Base class’ prototype
object. However, as already discussed, where the function/method is defined doesn’t matter. Pay attention to the call site instead. Usually, as discussed in the previous section, this will fall into the implicit this binding
category.
Value of this
when accessing through the super
keyword
We can access a method defined in the base class using super
. It’s helpful if we’re overriding the method in the derived class. To access the base method we can do super.methodName()
inside the derived method. Due to the dot notation, this falls into the implicit this binding
category. So, this
will point to super
, but, what is super
?
super
keyword addresses 👍.We’ve already used super()
to invoke the parent constructor. Since it’s invoked, you might think super
is a function, not really! If invoked as a function it’ll call the base constructor. In other places, super
will point to this
but, with a special look-up behavior. If we access a method on super
, it’ll get it from the base class’ prototype object. If you need to visualize the value of super
, just visualize the value of this
in that context where super is used (remember the special lookup behavior though).
Below is a contrived example that illustrates the discussed concept.
class Base {instanceFieldDefinedInBase = 'base'instanceMethod() {console.log('inside base method')console.log(this.instanceFieldDefinedInBase, this.instanceFieldDefinedInDerived)}}class Derived extends Base {instanceFieldDefinedInDerived = 'derived'// we override the base methodinstanceMethod() {// this.instanceMethod() will cause infinite loopsuper.instanceMethod()console.log('inside derived method')}}const derived = new Derived()derived.instanceMethod()// inside base method// base derived// inside derived method
Value of this
inside static fields
Static fields become properties of the mapping constructor function. To access the static property, we need to use the dot notation on the function. Remember we discussed how JavaScript treats functions as first-class objects?`this` in a static field will always point to the class where it's defined.
Below is an ES6 class syntax with a static field followed by its equivalent constructor function representation.
class Base {static staticField = 'static-field'static anotherStaticField = this.staticField}
function Base {}Base.staticField = 'static-field'Base.anotherStaticField = Base.staticField// note that, how `this` is replaced by the function name// it's fixed, and will continue to point to the function object
Value of this
inside derived static fields
Static fields are inherited. So, we can access a static field defined in the Base class, using the Derived class.
class Base {static staticFieldDefinedInBase = 'base'}class Derived extends Base {}console.log(Derived.staticFieldDefinedInBase) // base
Now, what if the static field from Base class refers to
this
? What would this
refer when accessing through the Derived class?In the previous section, we saw, how
this
was replaced by the class(function) name. So, even if we access through the `Derived` class, `this` will continue to point the `Base` class.Take a look at the below example, if you want to understand how we can map ES6 subclass to a constructor function and how derived class inherits base class static fields.
class Base {static staticFieldDefinedInBase = this}class Derived extends Base {static staticFieldDefinedInDerived = this}// ***************console.log(Derived.staticFieldDefinedInBase) // Baseconsole.log(Derived.staticFieldDefinedInDerived) // Derived
function Base() {}Base.staticFieldDefinedInBase = Base// see, `this` is replaced with the function name// so, even if we access this property using `Derived`// it will still point to `Base`function Derived() {// `_super` points to the `Base` function// we'll set up it laterthis._super.call(this)}Derived.staticFieldDefinedInDerived = Derived// `Object.create` will create an empty object// we can pass an argument// which will be set to the internal [[Prototype]] property of the new objectDerived.prototype = Object.create(Base.prototype)Derived.prototype.constructor = DerivedDerived.prototype._super = Base// this line sets `[[Prototype]]` of `Derived` to `Base`// so, if a property/method is missing in `Derived`// the JS engine will then look at `Derived`Object.setPrototypeOf(Derived, Base)// ***************console.log(Derived.staticFieldDefinedInBase) // Baseconsole.log(Derived.staticFieldDefinedInDerived) // Derived
Value of this
inside static methods
Static methods become methods of the mapping constructor function(object).
Below is an ES6 class syntax with a static method followed by equal constructor function representation.
class Base {static staticMethod = function() { console.log(this) }}
function Base() {}Base.staticMethod = function() { console.log(this) }
There is no special this binding
rule here. As usual, this
can take different value depending on how the method is invoked. Typically, it will fall into the implicit this binding
category and, in such case, this
will point to the constructor function.
Value of this
inside base static methods
Similar to static fields, static methods are also inherited. So, we can access a static method defined in the Base
class using Derived
class.
There is no special this binding
rule here. To determine the value of this
, look at the call site. Generally, it will fall into the implicit this binding
category.
Value of this
when invoking a method on a primitive value
Primitive values are non-object types. So they don’t have any property or method. However, if we try to invoke a method or access a property on a primitive value, the JS engine will implicitly convert the value to the corresponding wrapper object (aka boxing).
Say we add a this
aware method to the wrapper function’s prototype object like below.
const getFirstChar = Symbol()// Symbol() gives us a guaranteed unique value// this will help us to prevent naming collisionsString.prototype[getFirstChar] = function() {// validation logic goes herereturn this[0]}
Usually, we’ll invoke the function as below.
"panda"[getFirstChar]()
This falls into the implicit this binding
category. So, you might guess, the value of this
will be the primitive string “panda”
. That’s only if we’re in strict mode (usually we are and we should).
this
aware function.String.prototype.someFunction = function() {console.log(typeof this == 'string')// true in strict mode, false in sloppy mode 🤷♂️}
Flowchart illustrating the steps in determining the value of this
Ok, we’ve seen a lot of things. this
might still seem a bit confusing. Hope this flowchart will help.
Please find below the updated flowchart with some more scenarios covered.
Summary
- Arrow functions do not define their own
this
, it’s just a variable that doesn’t exist in the function’s scope and will be resolved lexically. - To determine the value of
this
inside arrow functions, we need to look where the arrow function is defined. - If the arrow function is defined inside a non-arrow function, then depending on how that parent function is invoked,
this
inside arrow function also might point to different values. - To determine the value of
this
inside non-arrow functions, we need to look at the call site. - If the function is invoked using the
new
keyword, then a brand-new object is set tothis
. - We can explicitly set a value to
this
usingcall
,apply
andbind
methods. - When the function is invoked as a method(using object dot notation),
this
points to the context object. This is also called as “implicit binding”. - When we invoke a function just using its name(
functionName()
),this
isundefined
in strict mode and points to the global object(window
variable in Browser) in sloppy mode. This is called as “default binding”.
TODO
Initially, I thought to include these topics too. But, I’m already exhausted thinking about this
😂. For now, I’ll just leave some notes here.
- talk about
setTimeout
,addEventListener
- arrow function vs hardbound function using
.bind
this
inside accessor properties and proxieswhy not just makethis
behave as in other languages — talk about prototype chaininclude ES6 class example to the invoke-using-new section- does
bind
has performance impact? var that = this
and other hacks
Changelog
17/05/2021
Address feedback Reddit comment #1, Reddit comment #2
this
inside top level of an ESMthis
inside different elements of a (ES6)class- explicit
this
binding of primitive values in sloppy, strict modes - overriding
this
of a hardbound function - indicate why explicit
this
binding and other binding concepts do not directly apply to arrow functions - add todo to talk about accessor properties and proxies
27/05/2021
Address feedback Reddit comment #3
- provide more context about the prototype object and internal
[[Prototype]]
property - explain about typical constructor function
- revamp the
this inside ES6 class
section, remove Babel tranformation screenshots and explain in words, align subsections to have a logical connectivity - add a section to discuss different
implicit this binding
behavior when trying to invoke a method on a primitive value - update flowchart