In a function definition, this
refers to the “owner” of the function. Another way to say it would be: the this
keyword references the calling object which provides access to the calling object's properties. When there isn't a calling object, this refers to the global object (in strict mode, referencing the global object with this
will throw an error, because strict mode does not allow default binding).
- Example
- The Calling Object
- Explicit and Hard Binding
- this Context vs Explicit Context
- Common Usage
- Function Invocation Patterns
- Comparisons
- Avoid using Arrow Function Syntax
- this Summary
Consider the example:
let plant = {
name: 'spider plant',
repotted: '09-15-2018',
water() {
console.log(`Time to water ${name}.`);
}
};
plant.water(); // ReferenceError: name is not defined
The above doesn't work because methods do not automatically have access to other internal properties of the calling object. Here's where the this
keyword comes into play.
let plant = {
name: 'spider plant',
repotted: '09-15-2018',
water() {
console.log(`Time to water ${this.name}.`);
}
};
plant.water(); // Time to water spider plant.
If a function has a this
reference inside it, that this
usually points to an object. But which object it points to depends on how the function was called. It's important to remember that this
doesn't refer to the function itself (a common misconception). For example:
function foo() {
console.log(this.bar);
}
var bar = 'global bar';
var obj1 = {
bar: 'obj1 bar',
foo: foo
};
var obj2 = {
bar: 'obj2 bar'
};
foo(); // undefined (global bar) or TypeError
obj1.foo(); // obj1 bar
foo.call(obj2); // obj2 bar
new foo(); // undefined
In the example above, call()
is a special method that most functions have access to via their prototype. The purpose of this function is to allow you to explicitly state which object you want to be bound to this
. The functions first parameter is the object to use for this
.
Note: When chained, only the top/last level of the object property reference chain matters. For example:
function foo() {
console.log(this.a);
}
var obj1 = {
a: 2,
obj2: obj2
};
var obj2 = {
a: 50,
foo: foo
};
obj1.obj2.foo() // 50
Note: In the browser, the default object in the global context is the window object. In the example below, the values returned will be from the window itself:
function windowSize() {
var width = this.innerWidth;
var height = this.innerHeight;
console.log(width, height);
return [width, height];
}
windowSize();
// 863 673
In the examples above, we are seeing the this
value resulting from an implicit binding (with the exception of the call()
example). When you start passing functions around as parameters (callbacks), the implicit this
binding can become a problem. For example, let's start with something fairly straightforward:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
The result is what we expect. But what happens if we want to pass obj.foo
as a parameter?
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
setTimeout(obj.foo, 100); // undefined
The reason for the loss of the this
binding is that the call-site has changed (see: call-stack.md) and therefor the calling object has changed. To see this more clearly, we can pass obj.foo
to our own function:
function foo() {
console.log(this.a);
}
function doFoo(func) { // <-- calling object
func(); // <-- call-site
}
var obj = {
a: 2,
foo: foo
};
doFoo(obj.foo); // TypeError: Cannot read property 'a' of undefined
Instead of relying on this implicit binding through the use of obj.foo
, we can use call()
to create an explicit binding.
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
};
foo.call(obj); // 2
If we want to pass that explicit binding as a parameter, we can assign it to another function expression and pass that around instead. In the example below, no matter where we pass bar
it will always invoke foo
with obj
. This binding is both explicit and strong so we call it hard binding.
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
};
var bar = function () {
foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); // 2
Since hard binding is such a common pattern, it's provided as a built-in utility (as of ES5) Function.prototype.bind()
. Basically, bind()
returns a new function that is hardcoded to call the original function with the specified this
context.
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
};
var bar = foo.bind(obj);
bar(); // 2
setTimeout(bar, 100); // 2
This example uses this
to allow functions to be used against multiple objects:
function format() {
return this.name.toUpperCase();
}
function greet() {
let greeting = 'Hello ' + format.call(this);
console.log(greeting);
}
let bob = {
name: 'bob'
};
let jane = {
name: 'jane'
};
greet.call(bob); // Hello BOB
greet.call(jane); // Hello JANE
In the example above, the call()
method calls a function with a given this
value and arguments provided individually. Instead of using this
, you could explicitly pass in a context object, like so:
function format(object) {
return object.name.toUpperCase();
}
function greet(object) {
let greeting = 'Hello ' + format(object);
console.log(greeting);
}
let bob = {
name: 'bob'
};
let jane = {
name: 'jane'
};
greet(bob); // Hello BOB
greet(jane); // Hello JANE
This method seems way simpler to me coming from Python, but apparently the this
mechanism provides a more elegant way of implicitly passing along an object reference which can make a cleaner API design that's easier to reuse. The more complex the pattern, the messier it is to pass context around as an explicit parameter. More to come to validate this point.
Generally speaking, this
is used most commonly in objects that contain methods (as seen in the very first example above), object constructor functions, and classes.
// objects with methods:
let user = {
name: 'bob',
greet() {
console.log(`Hello ${this.name}`);
}
};
// constructor function
function Person(first, age, admin) {
this.firstname = first;
this.age = age;
this.admin = admin;
}
// class
class Human {
constructor(first, age, admin) {
this.firstname = first;
this.age = age;
this.admin = admin;
}
}
So we know that in addition to declared parameters, every function receives a this
parameter. Its value is determined by its invocation pattern. There are four invocation patterns in JavaScript and they differ in how the this
parameter is initialized.
When a function is stored as a property of an object (or in a class), it's a method. When a method is invoked it's bound to that object. A method can use this
to retrieve values from the object or modify the object. Methods that get their object context from this
are called public methods.
// Creates myObject with a value and a method.
// The increment method takes 1 optional parameter.
// If the argument is not a number, 1 is used as a default.
const myObject = {
value: 0,
increment(num) {
this.value += (typeof num === 'number' ? num : 1);
}
};
myObject.increment();
console.log(myObject.value); // 1
myObject.increment(2);
console.log(myObject.value); // 3
When a function is not a direct property of an object, then it is invoked as a function. As noted above, this
will be bound to the global object and will throw and error in strict mode. A consequence of this behaviour is that a method cannot employ an inner helper function because that inner function doesn't share the method's access to the object because it's own this
is bound to a different value. For example:
const myObject = {
value: 3
};
myObject.double = function () {
const helper = function () {
this.value *= 2; // TypeError: Cannot read property 'value' of undefined
};
helper();
};
myObject.double();
console.log(myObject.value);
There's a simple workaround for this situation though: assign the value of this to a new variable. The common naming convention is to use the word that
:
const myObject = {
value: 3
};
myObject.double = function () {
const that = this;
const helper = function () {
that.value *= 2;
};
helper();
};
myObject.double();
console.log(myObject.value); // 6
Constructor functions are one of the many ways to create an object. See objects.md. Constructor functions are invoked with the new
prefix. The new object that's created has a hidden link to the functions prototype and this
will be bound to that new object.
const Thing = function (string) {
this.string = string;
this.logString = function () {
console.log(this.string);
};
};
const thingOne = new Thing('confused');
const thingTwo = new Thing('clear');
thingOne.logString(); // confused
thingTwo.logString(); // clear
As a side note, if I wanted to assign a new method to a constructor function and have it be available to each instance, I have to use the prototype
property (every function has this property). For example:
Thing.logStringUpper = function () {
console.log(this.string.toUpperCase());
};
thingOne.logStringUpper();
// TypeError: thingOne.logStringUpper is not a function:
Thing.prototype.logStringUpper = function () {
console.log(this.string.toUpperCase());
};
thingOne.logStringUpper(); // CONFUSED
thingTwo.logStringUpper(); // CLEAR
The apply()
method is very similar to the call()
method described briefly above in The Calling Object. The syntax is almost identical. The fundamental difference is that following the first parameter, call() accepts an argument list, while apply() accepts a single array of arguments.
func.apply(thisObj, [...])
func.call(thisObj, arg1, arg2, arg3,...)
The apply()
method lets us call a function with a given object to be used for the this
value along with an array of other arguments. The syntax is function.apply(thisArg, [additionalArgs])
For example:
console.log(Math.max(1, 10, 12, 5)); // 12
const array = [1, 10, 12, 5];
console.log(Math.max.apply(null, array)); // 12
As a side note, you can use apply()
in some interesting ways. For example, we could merge two arrays together as if we were using concat()
:
const myArray = ['a', 'b'];
const myThis = [0, 1, 2];
myThis.push.apply(myThis, myArray);
console.log(myThis); // [ 0, 1, 2, 'a', 'b' ]
And for the big finale, we can use apply()
to invoke a method on an object where there is no existing prototype link.
const Thing = function (string) {
this.string = string;
};
Thing.prototype.logStringUpper = function () {
console.log(this.string.toUpperCase());
};
const notThing = {
string: 'bumblebee'
};
Thing.prototype.logStringUpper.apply(notThing); // BUMBLEBEE
Here are some examples of different ways to approach creating a function that tracks how many times it was called:
Using a global variable:
function foo(num) {
console.log('foo: ' + num);
// keep track of how many times foo is called in a GLOBAL variable
count++;
}
let count = 0;
for (let i = 0; i < 3; i++) {
foo(i);
}
console.log(count);
// foo: 0
// foo: 1
// foo: 2
// 3
Using the lexical scope of an object:
function foo(num) {
console.log('foo: ' + num);
// keep track of how many times foo is called in an OBJECT
data.count++;
}
let data = {
count: 0
};
for (let i = 0; i < 3; i++) {
foo(i);
}
console.log(data.count);
// foo: 0
// foo: 1
// foo: 2
// 3
Using the lexical scope of the function itself:
function foo(num) {
console.log('foo: ' + num);
// keep track of how many times foo is called as a PROPERTY
foo.count++;
}
foo.count = 0;
for (let i = 0; i < 3; i++) {
foo(i);
}
console.log(foo.count);
// foo: 0
// foo: 1
// foo: 2
// 3
First (incorrect) attempt to use this:
function foo(num) {
console.log('foo: ' + num);
// keep track of how many times foo is called with THIS
this.count++;
}
foo.count = 0;
for (let i = 0; i < 3; i++) {
foo(i);
}
console.log(foo.count);
// strict mode = TypeError: Cannot read property 'count' of undefined
// foo: 0
// foo: 1
// foo: 2
// 0
This first (incorrect) attempt to use this
, fails because this.count
is not the same as foo.count
because once again, this.count
refers to the global (calling) object (and in strict mode, will be undefined). A correct solution for using this would be:
function foo(num) {
console.log('foo: ' + num);
this.count++;
}
foo.count = 0;
for (let i = 0; i < 3; i++) {
// we use call() to make foo the calling function of itself
foo.call(foo, i);
}
console.log(foo.count);
// foo: 0
// foo: 1
// foo: 2
// 3
In this example, we use .call()
to make foo()
the calling function, so this.count
now refers to foo.count
. I'm still not sure why we'd do it this way but... moving on for now.
In many of the examples above, we're using concise syntax which allows us to omit the colon :
and the function
keyword). Note that if you use Arrow Functions, the this
keyword will not work in the same way. Arrow functions save the binding of this
in the closure that's created when the function is created. So it doesn't set this
to the context of the call.
let plant = {
name: 'spider plant',
repotted: '09-15-2018',
water: () => {
console.log(`Time to water ${this.name}.`);
}
};
plant.water(); // TypeError: Cannot read property 'name' of undefined
The key takeaway from the example above is to avoid using arrow functions when using this
in a method!
To clarify, this
is not an author-time binding but a runtime binding. It is contextual, based on the conditions of the functions invocation. It has nothing to do with where a function is declared, but everything to do with the manner in which the function is called.
When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how it was invoked, what parameters were passed an so on. One of the properties of this record is the this
reference, which will be used for the duration of that function's execution.
To summarize, we can ask these questions in order of precedence to determine a this
binding:
- Is the function called with the
new
keyword? If so,this
is the newly constructed object. (see: objects.md)
var bar = new foo(); // this is bar
- Is the function called with
call()
orapply()
(explicit binding)? If so,this
is the explicitly specified object.
var bar = foo.call(obj); // this is obj
- Is the function called with a context (implicit binding)? If so,
this
is that context object.
var bar = obj1.foo(); // this is obj1
- Otherwise,
this
is a default binding (in strict mode undefined, otherwise the global object).
var bar = foo(); // this is undefined