Recently I stumbled about this post about The complete elimination and eradication of JavaScript’s this.
To give you a TL;DR: The author states that almost no JavaScript developer understands this
, that it is a terrible concept, and introduces a library that allows developers to avoid using this
in many circumstances. So, we had a twitter discussion with pretty opposing standpoints: I think this
in JavaScript is quite simple, versatile and really useful.
But in the next days, it got me thinking …
Yeah, I’ve met my share of developers that I had to explain to how this
works. But I don’t believe that introducing a new concept to avoid the use of this
altogether is a good solution. It’s like avoiding olives all your life because you didn’t like them at the age of 13. When you are 55, you will try some by accident, they will be delicious and you will mourn the last 30 years of your life that could have been so much better with olives (or this
, for that matter).
So, let’s actually talk about this
Before we go deeper, I’ll give you a simple rule of thumb:
this
in a function is, in this order
- what the function was explicitly bound to (using
bind
or a similar construct) - what the function was explicitly called with (using
call
orapply
) - what was left of the dot at the moment of execution
- something else like a global context that you should not rely on
„Left of the dot“? You’re trolling me right now!
Yeah, of course, that one rule seems the weirdest, but I think it’s the best way to put it and make it easy to understand. Let us start with two little test objects:
let a = { about: "This is object a", testFunction: function() { console.log(this); } }; let b = { about: "This is object b" };
So, let’s run something on it:
a.testFunction(); // Object { about: "This is object a", testFunction: testFunction() }
Okay, we expected that. Now, what if we assign a.testFunction
to b and call b.testFunction
?
b.testFunction = a.testFunction; b.testFunction(); // Object { about: "This is object b", testFunction: testFunction() }
Although we initially defined testFunction
on our object a
, this
when calling b.testFunction
now is b
.
So now you might get what I was meaning by ‚left of the dot‘. You could also say ‚called on‘, but I like to think about this as the 0th argument to a function. That way, it is very consistent with the signature of Function.prototype.call
, which we’ll talk about later.
„Something else“? So it is all hogwash!
Yeah, no. Let’s continue with our example above and see what it does in different contexts, when there is nothing left of the dot:
let a = { about: "This is object a", testFunction: function() { console.log(this); } }; let c = a.testFunction; c();
When we execute this code in a browser, this
points to the window
object. Executed in node, we get the global
object. In strict mode
, it behaves a little differently:
let a = { about: "This is object a", testFunction: function() { 'use strict'; console.log(this); } }; let c = a.testFunction; c();
Running it now in the browser or in node, we get the undefined
value.
So yeah, it is predictable and defined. But my point is: if you want to access window
or global
respectively, just access window
or global
. Doing that access with this
will help no-one understand your code. So, don’t do it and treat this
in that context just as „something else“ and never think about it again.
But when I’m writing this.foo as a property in React/JSX, it does not set this to the value left of the point. What’s going on there?
Notice that above, I said ‚left of the dot at the moment of execution‘? What you are doing here is passing that function for it to be executed later. It will be stuffed into a variable, and just like in the last chapter where we assigned c = a.testFunction
, the ‚left of the dot‘ context will be lost before it is finally executed.
This is why you need to bind
functions you pass in jsx, or use the fat arrow notation.
Explicitly specifiying this at runtime: call and apply
You can also specify at runtime what this
will be on method execution. Look at this example:
console.log(b.testFunction.call(a)); // Object { about: "This is object a", testFunction: testFunction() }
So while the function is stored
in our object b
, it is called on a
. Alternatively, you cann use the apply
method.
call
and apply
are pretty similar, but differ when you want to pass additional arguments to the function. The following two method calls would be identical:
c.call(newThis, firstArgument, secondArgument, thirdArgument); c.apply(newThis, [firstArgument, secondArgument, thirdArgument]);
Seeing the signature of call, you might also understand why I like to think of this
as the „0th argument“ of a function.
Explicitly binding this: bind and fat arrow functions
So what if you have no control over how and when a method is called, but want complete control over what this
is when it is executed? Then you can bind a method. Look at this example:
b.testFunction = c.bind(a); b.testFunction(); // Object { about: "This is object a", testFunction: testFunction() }
So even though you are calling testFunction
on b
, it was bound to a
and so, this will always point to a
.
Simplified, bind
wraps your function in another function that always calls your function with the this
that it was bound to.
While this is great, keep in mind that you cannot change what a function is bound to once it was bound once because you would only change what the wrapping function is bound to, not what the wrapped function is bound to. Because of the same reason, you cannot use apply
or call
to change this
any more.
Fat Arrow Function
Now, let’s talk about a fat arrow function in that context. The following two statements are roughly equivalent:
let y = (function(){ console.log(this); }).bind(this); let z = () => { console.log(this) };
So an arrow function automatically binds to the value of this
at the exact moment the method is defined. This is called lexical binding or static binding.
That property makes it so useful in every situation where a function is passed as a value (in JSX for example), because this
will be exactly what it is while you write it.
Phew. That was a lot of information. And you call that simple?
Yup. While there was a lot of information, just try to remember this even shorter version of my my rule of thumb from the top:
This
is what was explicitly bound to, or what was specified at runtime, either by using call
/apply
or just by calling a function with something left of the dot.
Forget about that global
/window
stuff and stay clear of that usage. It makes your code less readable and my rule of thumb too complicated.
Schreibe einen Kommentar