Now, I’m not saying that “determining the object bound to this
” makes for the lightest possible reading, but there is some small comfort to be found here: now that we understand when that determination is made, we’re well equipped to understand the what—and that fortunately, that breaks down to only a handful of distinct use cases, all of which share one big caveat in the form of arrow functions.
Global Binding (formerly “default binding”)permalink
Any code outside of a defined function lives in the global execution context. Within the scope of the global execution context, this
references globalThis
—a property that itself references a JavaScript environment’s global object. In a document in your browser, the global object is the window
object:
- Code language
- js
this; // result: Window {...} this === window; // result: true globalThis; // result: Window {...} this === globalThis; // result: true
Simple enough, if not especially useful outside the context of a function. Inside the context of a function declaration, you’ll see a similar result:
- Code language
- js
function theFunction() { return this; } theFunction(); // result: Window {...}
Now, remember that the value of this
is a reference to “the object bound to a function call.” this
referencing the global object—window
, for our web-browsing purposes—makes sense in terms of how JavaScript has historically handled global scope: when you create a function using the function
constructor, that function is created in the global scope and made available as a method on the window
object:
- Code language
- js
function theFunction() { } theFunction; // result: theFunction() window.theFunction; // result: function theFunction()
So it makes a kind of sense—a JavaScript kind of sense, anyway—that the value of this
within the execution context created for myFunction
would be a reference to the window
object: absent a more specific object to reference, well, the window
object more or less owns everything, so that tracks as the default—hence the original name for this case: “default binding.”
- Code language
- js
function theFunction() { return this; } theFunction() === window // result: true
I know; a little weird. For what it’s worth, you’re not likely to run into this situation very often. You’re even less likely to have occasion to use it—we don’t tend to need a dedicated reference to window
when we can access window
directly. While “default binding” might make sense in terms of the anatomy of JavaScript, there isn’t much call for a use case as dangerous as “act on the object bound to this function, even if that means messing with window
.” For that reason, later versions of JavaScript refined this behavior—but altering the rules of JavaScript as-it-is-played would potentially break untold numbers of websites out in the wild, so the changes had to be opt-in via strict mode.
In the context of strict mode, this behavior is much more predictable: when we call a function by identifier within the global scope, this
gets the value undefined
:
- Code language
- js
function theFunction() { "use strict"; return this; } theFunction(); // result: undefined
“this
references the object bound to this function, if there is one, but undefined
otherwise” makes for much more predictable conditional logic around acting on the object bound to a function.
Now, that function is still available as a method on window
, naturally—the nature of JavaScript and all. So, what happens if we invoke it as one? In that case, we make window
the object implicitly bound to this function:
- Code language
- js
function theFunction() { "use strict"; return this; } theFunction(); // result: undefined window.theFunction(); // result: Window { … }
We’re calling the function as a method of window
, so window
is the object bound to our function at the time the function is executed, meaning this
is a reference to window
. Through this
, we then get access to all the parent properties and methods of the object it references:
- Code language
- js
function theFunction() { "use strict"; if ( this !== undefined ) { console.log( this.getComputedStyle ); } else { return this; } } theFunction(); // result: undefined window.theFunction(); // result: function getComputedStyle()
Is this useful, as seen here? Well, still no, not especially; we don’t need to stop for this
when we already have window
at home. In fact, I’d caution against deliberately using this
to refer to globalThis
—better to use window
, self
, or even globalThis
itself, which will give you exactly what you need, independent of surrounding context.
For our purposes, this snippet of code is illustrative, even if not copy-and-paste useful: this is a perfect example of “implicit binding,” and that’s something you’ll definitely end up using.
Implicit bindingpermalink
As you just saw, when a function is called as an object’s method, the value of this
within the context of that function’s execution is a reference to to the object that contains the method. That gives you access to all the methods and properties that sit alongside the method.
- Code language
- js
const theObject = { theString: "This is a string!", theMethod() { console.log( this.theString ); } }; theObject.theMethod(); // result: This is the string!
The theObject
object is calling the theMethod
method, so within the function execution context that gets created, the value for this
is a reference to theObject
(and I bet you thought “this
is a keyword that refers to the value of the object bound to the function where this
is invoked at the time when the function is called” was the worst sentence you would read in this post).
At this point, I have some good news and some bad news. The good news is that, with what you now know of implicit binding, you’re covered for the vast majority of use cases for this
. The bad news, of course, is that there’s an exception: arrow functions.
this
in arrow functionspermalink
Arrow functions don’t have this
bindings of their own. Instead, the value of this
in an arrow function resolves to a binding in a “lexically enclosing environment.” That’s right: lexical environment—as in, where it sits in the code we’ve written—not the execution context. Inside an arrow function, this
works very differently: it refers to the value of this
in that function’s closest enclosing context as written:
- Code language
- js
const theObject = { theMethod() { console.log( this ); }, theArrowFunction: () => console.log( this ) }; theObject.theMethod(); // result: Object { myMethod: myMethod(), myArrowFunction: myArrowFunction() } theObject.theArrowFunction(); // result: Window {...}
Here, myObject.theMethod()
is classic implicit binding. Just like the earlier examples: when theMethod
is called and a function execution context is created for it, the value of this
is set to a reference to the object containing the method.
That isn’t the case when we call myObject.theArrowFunction()
, however: this
inherits the value of this
from the lexically enclosing environment—the value of globalThis
. Because we’re outside of strict mode, this
ends up being a reference to window
—if we were in strict mode, it would be undefined
.
Arrow functions are exceptionally useful in a lot of ways, and not having their own this
bindings can be one of them if used very carefully. For this reason (among others), it’s generally considered a good practice to avoid using arrow functions as a method—a function defined as a property of an object—unless you can make a strong case for it.
Explicit bindingpermalink
Implicit binding handles most use cases for working with this
, by a huge margin. However, in the rare event you need this
reference a specific object, invoking a function using the call()
or apply()
methods will allow you to specify that the value referenced by this
should be the object you’ve provided as an argument.
- Code language
- js
function theFunction() { "use strict"; console.log( this ); } const theObject = { theValue: "This is a string!" }; theFunction(); // result: undefined theFunction.call( theObject ); // result: Object { theValue: "This is a string!" }
Just like it says on the tin, the bind()
method can net you the same end result when it comes to this
, though the usage is a little different. When you invoke bind()
on a function, a new, wrapped version of that function is created. The function created using bind()
is usually referred to as a “bound function,” predictably enough.
Calling that bound function then executes the inner function with a value for this
that references the object specified as an argument.
- Code language
- js
function theFunction() { "use strict"; console.log( this ); } const theObject = { theValue: "This is a string!" }; const boundFunction = theFunction.bind( theObject ); theFunction(); // result: undefined boundFunction(); // result: Object { theValue: "This is a string!" }
When we call theFunction
, this
is a reference to globalThis
—because we’re in strict mode, resulting in this
being undefined
. However, when we invoke that function using call()
(or use bind()
to create a bound function) with theObject
as an argument, this
contains a reference to that object instead. The explicit binding overrides the implicit binding:
- Code language
- js
const theObject = { theValue : "This string sits alongside myMethod.", theMethod() { console.log( this.theValue ); } }; const someOtherObject = { theValue : "This is a string in another object entirely!", }; theObject.theMethod(); // result: This string sits alongside myMethod. theObject.theMethod.call( someOtherObject ); // This is a string in another object entirely!
We’re veering a little academic, but explicit binding does come with one interesting complication: like so many dogs playing so much basketball, there’s no rule that says you can’t bind something other than an object—a primitive, for example.
- Code language
- js
function theFunction() { "use strict"; console.log( this ); } theFunction.call( "How'd this string get here?" ); // result: How'd this string get here?
Now, needing to use explicit binding in the first place is rare, and I can’t imagine a case where you’d do so with a non-object on purpose, but you just never know with JavaScript—so, just in case, there are some unique behaviors worth knowing.
A passed this
value isn’t coerced in strict mode—this
just takes on that value:
- Code language
- js
"use strict"; function theFunction(){ "use strict"; console.log( typeof this, this ); } theFunction.call( 7 ); // result: number 7 theFunction.call( "A string." ); // result: string A string. theFunction.call( undefined ); // result: undefined undefined theFunction.call( null ); // result: object null // Believe it or not, `typeof null` is `object`! Check out <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null>
As is often the case, things get a little weirder outside of strict mode. If a function is called in a way that would provide this
with a value of either undefined
or null
, that value is replaced by globalThis
.
- Code language
- js
function theFunction() { console.log( this ); } theFunction.call( null ); // result: Window {...}
If a function is called in a way that would provide this
with a primitive value, that value is substituted with the primitive value’s wrapper object outside strict mode:
- Code language
- js
function theFunction() { console.log( this ); } theFunction.call( 10 ); // result: Number { 10 }
You may very well be asking “in what way would that be useful?” You would be right to wonder this! I tell you, I have for years now.
The thing is, the nature of the web is such that we can’t know whether someone out there had some wildly obscure use case (or made some mistake) where the JavaScript running on their website relies on this obscure behavior, and so the nature of the standards that govern JavaScript is that they can’t be changed without being opt-in. So outside of strict mode, this behavior persists to this day. If over the course of your travels in JavaScript you’ve ever run into this behavior buried in a legacy codebase somewhere, I absolutely want to hear about it in the JavaScript for Everyone Discord which will be available when the course goes live.
All told, explicit binding is part of the language, and you shouldn’t hesitate use it when and where you need it. That said, when writing something brand new, I try to avoid depending on explicit binding for anything load-bearing; it breaks with the contextual usefulness of this
, and turns it into an argument that wants desperately to be a little too clever for its own good.
new
Bindingpermalink
When a class is used as a constructor by way of the new
keyword, this
refers to the newly-created object:
- Code language
- js
class TheClass { theString; constructor() { this.theString = "A string."; } logThis() { console.log( this ); } } const thisClass = new TheClass(); thisClass.logThis(); // result: Object { theString: "A string.", ... }
But remember, the value of this
is set at the time the logThis
method is executed, and that means that value can be overridden by the way a method is invoked within a class:
- Code language
- js
class TheClass { successMsg = "You clicked the button!"; constructor( theElement ) { theElement.addEventListener('click', this.logThis ); // result: <button> } logThis() { console.log( this ); }; } const theButton = document.querySelector( "button" ) const theConstructedObject = new TheClass( theButton );
Imagine we wanted to do something with the instance property containing our You clicked the button!
string in response to a button press. Well, spoilers for a little later on in the course, but this
takes on a different value in the context of an event listener—because of the execution scope of the logThis
method, this
doesn’t have the value we’re looking for.
Well, there’s an argument to be made that this is the exact use case for arrow functions inheriting their this
value from lexical scope rather than execution scope. If we write the logThis
method as an arrow function, the event handler execution scope doesn’t matter anymore—and the lexical scope for our method is the class instance:
- Code language
- js
class TheClass { successMsg = "You clicked the button!"; constructor( theElement ) { theElement.addEventListener('click', this.logThis ); // result: Object { successMsg: "You clicked the button!", logThis: logThis() } } logThis = () => { console.log( this ); }; } const theButton = document.querySelector( "button" ) const theConstructedObject = new TheClass( theButton );
As with classes, when a function is called with new
, this
within that function is an instance of that constructor function:
- Code language
- js
function TheConstructorFunction() { this.theString = "A string."; this.logThis = function() { console.log( this ); } } const theConstructedObject = new TheConstructorFunction(); theConstructedObject.logThis(); // result: Object { theString: "A string.", ... }
Nothing terribly surprising there, either—there’s a similarly clear relationship between those objects and functions. There’s one little thing to keep in mind when you’re working with this
and constructor functions, however: in a well-written constructor function like the example above, this
will always reference the instance of that constructor function:
- Code language
- js
function TheConstructorFunction() { this.logThis = function() { console.log( this.constructor.name ); } } const theConstructedObject = new TheConstructorFunction(); theConstructedObject.logThis(); // result: TheConstructorFunction
If that constructor function were written using a return
—not a good practice, but I’ve certainly seen it done—the object that is explicitly returned might not be an instance of the constructor function:
- Code language
- js
function TheConstructorFunction() { return { logThis: function() { console.log( this.constructor.name ); } }; } const theConstructedObject = new TheConstructorFunction(); theConstructedObject.logThis(); // result: Object
We’re veering a little academic again, but that doesn’t mean you couldn’t make use of this
in the example above, using our terse but surprisingly complicated friend the arrow function.
- Code language
- js
function TheConstructorFunction() { return { logThis: function() { console.log( this.constructor.name ); }, logThat: () => console.log( this.constructor.name ), }; } const thing = new TheConstructorFunction(); thing.logThis(); // result: Object thing.logThat(); // result: TheConstructorFunction
In this example, logThat
inherits a this
value from the lexical scope of TheConstructorFunction
.
For those of you keeping score, though: that’s us using an arrow function as a method, which should usually be avoided, to address the failing of a constructor function that returns an object, which should definitely be avoided—we’re two layers deep in “questionable practice” territory.
Still, I know better than anyone that we don’t always have full control over the codebases we inherit; it’s worth knowing the edge cases, just in case you find yourself faced with one someday.
Event handler bindingpermalink
Ah, you never forget your first this
. An old favorite. A classic. Inside an event handler’s callback function, this
references the element associated with the handler. That’s it!
- Code language
- js
document.querySelector( "button" ).addEventListener( "click", function( event ) { console.log( this ); // result: <button class="btn"> });
When a user fires this click event on the button
element, the resulting value of this
is a reference to the element object for the <button>
itself.
Okay, technically, this isn’t a distinct case; behind the scenes, JavaScript is effectively using call()
to set the value of this
. You can do the same by using bind()
to create a bound function for use as a callback, at which point this
will explicitly reference the object you specify:
- Code language
- js
const button = document.querySelector( "button" ); const theObject = { "theValue" : true }; function handleClick() { console.log( this ); } button.addEventListener( "click", handleClick.bind( theObject ) ); // result: Object { theValue: true }
Just like every other context, when an arrow function is used as an event listener’s callback, the value of this
is provided by the closest enclosing lexical environment. For example, at the top level, this
inside an event handler callback arrow function will reference globalThis
:
- Code language
- js
let button = document.querySelector( "button" ); button.addEventListener( "click", ( event ) => { console.log( this ); } ); // result: Window { … }
Now, again: this is not to say you can’t use an arrow function as an event handler’s callback, but you should only reach for one in a situation where you explicitly want to rely on lexical scope to determine the value of this
—for example, an event handler inside a method attached to an object that you’ll need to reference from your event handler’s callback function. That might sound like a pretty specific use case written out that way, but it is something I’ve encountered in my day-to-day work, and odds are you will too sooner or later.
That’s That this
permalink
Being so dependent on context makes this
especially tricky to understand through trial-and-error alone. Using it wrong gives you some wholly unexpected object rather than throwing a clear error, and a search for “why is this window now” is more likely to give you results for heated philosophical debates than Stack Overflow answers. Even the most seasoned developer occasionally runs afoul of it, present company included.
If you’ve made it this far, listen, credit is wholly due: you’ve wound your way through one of the most famously confusing topics JavaScript has to offer. If you’ve skipped ahead to see how this
story ends, there’s no shame in that either. Nobody should expect to have memorized all of this
after one, two, or ten read-throughs of a couple articles, no matter how clever or handsome their author may be—but this
is as common in JavaScript development as it is opaque, and odds are you’re going to spend a lot of quality time together.
My hope is that the next time it comes back as unexpectedly undefined
, sure, it might still take a little tinkering to figure out exactly why, but you’ll be starting on the right foot by recognizing it as “strict mode global binding.” Now that you’re well on your way toward understanding the when and the what of this
, you’re two steps closer to understanding the why in your daily work.
With thanks to Jake Archibald for checking all of the specifics were in order.