One could see this as an argument that has a default value. Consider the
apply
and call
functions.
function theThis () {
return this
}
theThis.call('hello') // => "hello"
theThis.apply('world') // => "world"
This example is not an explanation of the behavior of this
, but more
a demonstration of the use of this
as an argument that isn't part of the
arguments array. You could say it is an argument that has its own special
keyword.
The exact value that this
becomes; depends on a (long) list of rules. The
this
keyword can therefore be confusing and often unnecessarily so. In most
cases, it is better to write your code in a way that makes the use of this
redundant. Let's look at a fullName
example.
function fullName({firstName, lastName}) {
return `${firstName} ${lastName}`
}
const person = {
firstName: 'John',
lastName: 'Doe',
}
fullName(person) // => "John Doe"
We implemented the complete fullName
feature without this
, which is great
because we don't have to worry about the rules of this
. Therefore, we don't
need to use call
, bind
, and apply
to force the function to be called with
the correct this
. Not to mention, the () => {}
syntax for defining
functions which adds to the list of how the value of this
is determined.
Some people might want an object that also contains methods that can be
instantiated, the prototypal inheritance approach. This common approach is not
OK as it will cause confusion in many cases. Assigning a function on the object
to another object is one of those examples. Just because you know this little
trick, does not mean a developer with a different programming background knows
about this (confusing) behavior.
> a = {id: 0, thiz() { return this; } }
{ id: 0, thiz: [Function: thiz] }
> b = {id: 1}
{ id: 1 }
> b.thiz = a.thiz
[Function: thiz]
> b.thiz()
{ id: 1, thiz: [Function: thiz] }
By doing this you have also conflated computed properties and behaviours with
the state.
I remember running into these bugs where I would assign a method to the view
model of some SPA component. Poor me back in the early days of Angular.
I often read that the use of this
enables code re-use and concise
implementations. I find this statement to be untrue and misleading. Let's
compare implementations of the fullName
example.
With this
class Person {
constructor(details) {
Object.assign(this, details)
},
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
export default Person
//
import Person from './person.mjs'
new Person({
firstName: 'John',
lastName: 'Doe',
}).fullName()
Without this
export function fullName({firstName, lastName}) {
return `${firstName} ${lastName}`
}
//
import { fullName } from './person.mjs'
fullName({
firstName: 'John',
lastName: 'Doe',
})
The implementation without this
- has fewer objects
- is written with less code
- requires less knowledge of the language
- is just as expressive.
However, there are use-cases for using this
that are very specific.
Keeping backwards compatibility while passing more state to a function.
Previously, I showed that you can write functions without this
. But what if
code ends up being used, and you want to re-use that code while extending
functionality? Here you need an "escape-hatch." The this
can be exactly that.
You are most likely still better off doing a major version bump and updating
the consuming code to adopt those changes. If, however, you need or want to
keep the API, the this
can be your way out to pass some extra state.
The library I'm using already uses this
.
Don't be too strict about not using this
and adopt the API of the library you
end up picking. The designer of that library should assume their API is being
used the way they prescribe it. If you don't like it, consider using
a different library or write your own.
Conclusion: Using this
incorrectly can lead to confusion in JavaScript code. It is
recommended to become familiar with module and lexical scoping to write more
readable code. Avoiding the dynamic behavior of this
can also help in this
regard. Additionally, most features of JavaScript, such as prototypal
inheritance, can be used without relying on the this
keyword.
Links: