this
isn't really a keyword, it is the natural "main" function argument in JavaScript.
This proposal extends the function
declaration syntax to allow for explicit naming of what is normally called this
.
Object.defineProperty(User.prototype, {
get: function fullName() { // original version
return `${this.firstName} ${this.lastName}`
},
configurable: true,
})
// versions use the feature of this proposal
function fullName(this) { // explicit this parameter
return `${this.firstName} ${this.lastName}`
}
function fullName(this user) { // explicit naming `this` to `user`
return `${user.firstName} ${user.lastName}`
}
function fullName(this {firstName, lastName}) { // destructuring
return `${firstName} ${lastName}`
}
Its primary use case is to play nicely with the function bind proposal, making such functions more readable. For example:
function zip (this array, otherArray) {
return array.map( (a, i) => [a, otherArray[i]] )
}
function flatten (this subject) {
return subject.reduce( (a,b) => a.concat(b) )
}
[10,20]
::zip( [1,2] )
::flatten()
//=> [10, 1, 20, 2]
The "keyword" this
has been one of the greatest sources of confusion for programmers learning and using JavaScript. Fundamentally, it boils down to two primary reasons:
this
is an implicit parameter, andthis
implicitly performs variable shadowing in your functions.
Variable shadowing is true source of why this
is so confusing to learn. In fact, variable shadowing is a bad practice in general. For example:
function process (obj, name) {
obj.taskName = name;
doAsync(function (obj, amount) {
obj.x += amount;
});
};
In the above code, obj.x
is referring to a different obj
than obj.name
. An experienced programmer would likely think this code is silly. Why name the inner parameter obj
when there is already another variable in the same scope with the same name?
Yet, this behavior is exactly what this
proceeds to do. If we were to translate the above example to use method-style functions:
function process (name) {
this.taskName = name;
doAsync(function (amount) {
this.x += amount;
});
};
...we would end up with code just as bad as the original example. The second this
is referring to a different object than the first this
, even though they have the same name.
However, if we could explicitly name the object within the function parameters, we could disambiguate the two to avoid such variable shadowing, and make the above example look something more like this:
function process (this obj, name) {
obj.taskName = name;
doAsync(function callback (this result, amount) {
result.amount += 2;
});
};
Explicit this
parameter also allow type annotation or parameter decorators be added just like normal parameter.
// Type annotation (TypeScript, already possible today)
Number.prototype.toHexString = function (this: number) {
return this.toString(16)
}
// Parameter decorators (future proposal)
Number.prototype.toHexString = function (@toNumber this num) {
return num.toString(16) // same as Number(num).toString(16)
}
If a function explicitly names this
, attempting to use this
inside the body will throw an error:
function method (this elem, e) {
console.log("Elem:", elem) // OK
console.log("this:", this) // <-- Syntax error!
}
This behavior fits well with arrow functions, since they don't contain their own this
in the first place.
function callback (this elem, e) {
alert(`You entered: ${elem.value}`);
setTimeout( () => elem.value = '', 1000 ) // OK
setTimeout( () => this.value = '', 1000 ) // <-- Syntax error!
}
As normal, an inner function
get its own this
, which you can still choose whether or not to rename:
runTask(function cb (this elem, e) {
elem.runAsyncTask(function () {
console.log("Outer elem:", elem) // OK
console.log("Inner this:", this) // OK
});
})
Class constructor can't use explicit this
syntax because class constructor can only be used via new
and this
in the constructor is never a parameter or argument passed by caller, the this
value in constructor is generated by the constructor or its base class.
class C {
constructor(this) { // <-- Syntax error!
// ...
}
}
If a function has explicitly named its this
parameter, it could be useful to throw an error when that function gets called without one or used via new
. For example:
function zip (this array, otherArray) {
return array.map( (a, i) => [a, otherArray[i]] )
}
zip() //=> Throws a TypeError on invocation,
// before `array.map(...)` is run.
new zip() //=> Also throws a TypeError
Matching the function bind proposal:
function array::zip (otherArray) {
return array.map( (a, i) => [a, otherArray[i]] )
}
function subject::flatten () {
return subject.reduce( (a,b) => a.concat(b) )
}
[10,20]
::zip( [1,2] )
::flatten()
//=> [10, 1, 20, 2]