(I'm aware of this question, but the answers don't quite tell me what I need to know.)
I've come across cases where I need to use .bind()
on a function in JavaScript in order to pass this
or local/class variables to a function. However, I still don't really know when it's needed.
What is the criteria for knowing when this
or local/class variables will or will not be available in a function, exactly? How do you reason about this?
For instance:
function() { }
is created, wrapped, or passed around?class
member function, a class
getter/setter function, or an oldschool prototype.function
"member function" (of a function acting as a class)?for
or forEach
loop, or any of their variants?Array.prototype.forEach.call()
or [].forEach.call()
?The main reason I ask is to be aware of potential pitfalls and to avoid having to rely on trial and error.
Credit to T.J. Crowder and Zapparatus for their answers, which provided helpful info. Also helpful were these 4 answers/articles: 1 2 3 4
However, these were either not entirely complete and/or very long-winded. So I have decided to combine all of my findings into one answer, along with code examples.
There are several considerations to factor in when determining whether this
or local/class variables will be available in a function:
Note: there is also strict mode (which yields undefined
's rather than window
objects) and arrow functions (which do not change this
from the containing scope).
Here are the explicit rules:
this
is the global object, which in browser world is window
.this
will still be window
, it does not change.class
or function-class (new function() { }
), inside a function-class's prototype (funcClass.prototype.func = function() { }
), inside a function called by a neighboring member function with this
, or inside a function mapped in an object ({ key: function() { } }
) or stored in an array ([ function() { } ]
), if the function is called directly with the class/object/array as the immediate predecessor in the call chain (class.func()
, this.func()
, obj.func()
, or arr[0]()
), this
refers to the class/object/array instance..forEach(function() { })
) or set to handle an event), this
reverts back to window
or to whatever the caller may have bound it to (e.g. events may bind it to the triggering object instance).
class
's member function (and only a class
, not a function-class), if a function within that loses its this
context (e.g. by being a closure's inner function), it becomes undefined
, rather than window
...Here is a JSFiddle with a bunch of code examples:
outputBox = document.getElementById("outputBox");
function print(printMe = "") {
outputBox.innerHTML += printMe;
}
function printLine(printMe = "") {
outputBox.innerHTML += printMe + "<br/>";
}
var someVar = "someVar";
function func(who) {
printLine("Outer func (" + who + "): " + this);
var self = this;
(function() {
printLine("Inner func (" + who + "): " + this);
printLine("Inner func (" + who + ") self: " + self);
})();
}
func("global");
printLine();
func.call(someVar, "someVar");
printLine();
function funcTwo(who) {
printLine("Outer funcTwo (" + who + "): " + this);
var self = this;
return function funcThree() {
printLine("Inner funcThree (" + who + "): " + this);
printLine("Inner funcThree (" + who + ") self: " + self);
};
}
funcTwo("global")();
printLine();
f = funcTwo("global f");
f();
printLine();
funcTwo.call(someVar, "someVar")();
printLine();
object = {
func: function(who) {
printLine("Object outer (" + who + "): " + this);
var self = this;
(function() {
printLine("Object inner (" + who + "): " + this);
printLine("Object inner (" + who + ") self: " + self);
})();
}
}
object.func("good");
printLine();
bad = object.func;
bad("bad");
printLine();
function funcClass(who) {
printLine("funcClass (" + who + "): " + this);
}
funcClass.prototype.func = function() {
printLine("funcClass.prototype.func: " + this);
self = this;
(function() {
printLine("funcClass.func inner: " + this);
printLine("funcClass.func inner self: " + self);
})();
}
fc = funcClass("bad");
printLine();
fc = new funcClass("good");
fc.func("good");
printLine();
class classClass {
constructor() {
printLine("classClass constructor: " + this);
}
func() {
printLine("classClass.func: " + this);
self = this;
(function() {
printLine("classClass.func inner: " + this);
printLine("classClass.func inner self: " + self);
})();
}
funcTwo() {
this.func();
}
}
cc = new classClass();
cc.func();
printLine();
printLine("Calling funcTwo:");
cc.funcTwo();
printLine();
[0].forEach(function(e) {
printLine("[0].forEach: " + this);
printLine("[0].forEach someVar: " + someVar);
});
[0].forEach(function(e) {
printLine("[0].forEach with [0]: " + this);
}, [0]);
printLine();
arr = [
function(who) {
printLine("Array (" + who + "): " + this);
},
1,
10,
100
];
arr[0]("good");
arrFunc = arr[0];
arrFunc("bad");
printLine();
var button = document.getElementById("button");
button.onclick = function() {
printLine("button: " + this);
}
button.click();
button.onclick = func;
button.click();
setTimeout(function() {
printLine();
printLine("setTimeout: " + this);
printLine("setTimeout someVar: " + someVar);
}, 0);
setTimeout(fc.func, 0);
setTimeout(cc.func, 0);
<input id="button" type="button" value="button"/>
<br/><br/>
<div id="outputBox" />
Conclusion: So yeah that's pretty simple.