When you pass 'this' as an argument? [duplicate]
I'm trying to learn about this
, and it's confusing me a bit here:
var randomFunction = function(callback) {
var data = 10;
callback(data);
};
var obj = {
initialData: 20,
sumData: function(data) {
var sum = this.initialData + data;
console.log(sum);
},
prepareRandomFunction: function() {
randomFunction(this.sumData.bind(this));
}
};
obj.prepareRandomFunction();
Is this
designed to set itself where it is first rendered in code?
For instance, in my example I'm successfully using it to refer to obj
and also binding the function to obj
, but since this
is being passed as a callback function, what is stopping it from being set as randomFunction
(i.e. what's stopping it from literally passing "this.sumData.bind(this)" so that this
is set to randomFunction
when it gets called from there)?
Updated
I'm not exactly asking how this works generally (I don't think). I'm mainly curious to know why this
gets set where I define it as the argument of my randomFunction
call, and not where callback
gets called within randomFunction
.
I could be wrong, but if I were to swap this.sumData.bind(this)
with the callback(data)
that I currently have I think I would get a different result. Is that because callback
is a reference to this.sumData.bind(this)
when it was first defined (and where this
is obj
)?
I think I've learned through this scenario that this
is set when it's executed. It's not passed as a argument to be set later when the argument is called down the line.
Solution 1:
this
inside a function call gets set according to how a function is called. There are six main ways that this
gets set.
-
Normal Function Call: In a normal function call such as
foo()
,this
is set to either the global object (which iswindow
in a browser orglobal
in nodejs) or toundefined
(in JavaScript's strict mode). -
Method Call: If a method is called such as
obj.foo()
where the methodfoo
is a normal method declaration using thefunction
keyword or using the regular method declaration syntax for aclass
, thenthis
is set toobj
inside the function. -
.apply() or .call(): If
.apply()
or.call()
is used, thenthis
is set according to what is passed to.apply()
or.call()
. For example, you could dofoo.call(myObj)
and causethis
to be set tomyObj
inside offoo()
for that particular function call. -
Using new: If you call a function with
new
such asnew foo()
, then a new object is created and the constructor functionfoo
is called withthis
set to the newly created object. -
Using .bind(): When using
.bind()
a new stub function is returned from that call that internally uses.apply()
to set thethis
pointer as was passed to.bind()
. FYI, this isn't really a different case because.bind()
can be implemented with.apply()
. -
Using ES6 Fat Arrow Function Defining a function via the arrow syntax in ES6+ will bind the current lexical value of
this
to it. So, no matter how the function is called elsewhere (with any of the previous ways to call it), thethis
value will be set by the interpreter to the value thatthis
has when the function was defined. This is completely different than all other function calls.
There's sort of a seventh method, via a callback function, but it isn't really its own scheme, but rather the function calling the callback uses one of the above schemes and that determines what the value of this
will be when the callback is called. You have to consult either the documentation or the code for the calling function or test it yourself to determine what this
will be set to in a callback.
What is important to understand in JavaScript is that every single function or method call in JavaScript sets a new value for this
. And, which value is set is determined by how the function is called.
So, if you pass a method as a plain callback, that method will not, by default, get called as obj.method()
and thus will not have the right value of this
set for it. You can use .bind()
to work around that issue.
It's also useful to know that some callback functions (such as DOM event handlers) are called with a specific value of this
as set by the infrastructure that calls the callback function. Internally, they all use .call()
or .apply()
so this isn't a new rule, but is something to be aware of. The "contract" for a callback function may include how it sets the value of this
. If it does not explicitly set the value of this
, then it will be set according to rule #1.
In ES6, calling a function via an arrow function, maintains the current lexical value of this
. Here's an example of the array function maintaining the lexical this
from MDN:
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| properly refers to the person object
}, 1000);
}
var p = new Person();
Your example of obj.prepareRandomFunction();
is rule #2 above so this
will be set to obj
.
Your example of randomFunction(this.sumData.bind(this))
is rule #1 above so this
inside of randomFunction
will be set to the global object or undefined
(if in strict mode).
Since randomFunction is calling a callback function which itself used .bind()
, then the value of this
inside the callback function when it is called will be set to the value of this
that was passed to .bind()
in this.sumData.bind(this)
as via rule #5 above. .bind()
actually creates a new function who's job it is to call the original function AFTER setting a custom value of this
.
Here are a couple other references on the topic:
How to avoid "this" refering to the DOM element, and refer to the object
A better understanding of this
How does the "this" keyword work?
Note, that with the use of .apply()
or .call()
or .bind()
, you can create all sorts of somewhat odd things and sometimes quite useful things that could never be done in something like C++. You can take any function or method in the world and call it as if it were a method of some other object.
For example, this is often used to make a copy of the items in the arguments
object into an array:
var args = Array.prototype.slice.call(arguments, 0);
or similarly:
var args = [].slice.call(arguments, 0);
This takes the array's .slice()
method and calls it, but supplies it with an arguments object as the this
pointer. The arguments
object (though not an actual array), has just enough array-like functionality that the .slice()
method can operate on it and it ends up making a copy of the arguments
items into an actual array which can then be operated on directly with real array operations. This type of chicanery can't be done willy-nilly. If the array .slice()
method relied on other array methods that are not present on the arguments
object, then this trick would not work, but since it only relies on []
and .length
, both of which the arguments
object has, it does actually work.
So, this trick can be used to "borrow" methods from any object and apply them to another object as long as the object you are applying them to supports whatever methods or properties that the method actually uses. This can't be done in C++ because methods and properties are "hard bound" at compile time (even virtual methods in C++ are bound to a specific v-table location established at compile time), but can be easily done in JavaScript because properties and methods are looked up live at runtime via their actual name so any object that contains the right properties and methods will work with any method that operates on those.
Solution 2:
Step by step.
-
this
insideprepareRandomFunction
isobj
obj.prepareRandomFunction()
-
randomFunction
takes a function:randomFunction(this.sumData);
-
That function gets called:
callback(data);
Notice
callback
is called without a dot, which means it has no value forthis
, which meansthis
is the global object (orundefined
in strict mode). -
sumData
gets called:var sum = this.initialData + data;
this
is the global object,initialData
doesn't exist, you addundefined
todata
. Unexpected results.Solution: bind
this
permanently:randomFunction(this.sumData.bind(this));
-
sumData
runs,this
isobj
,obj.initialData
is20
. It works.