1

(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:

  • When a new anonymous function() { } is created, wrapped, or passed around?
  • When using a class member function, a class getter/setter function, or an oldschool prototype.function "member function" (of a function acting as a class)?
  • In the global scope?
  • In a for or forEach loop, or any of their variants?
  • In a closure outer or inner function?
  • In various JS operations like Array.prototype.forEach.call() or [].forEach.call()?
  • In various JS libraries and scripts, which may have their own custom implementation of things?

The main reason I ask is to be aware of potential pitfalls and to avoid having to rely on trial and error.

Andrew
  • 3,330
  • 1
  • 33
  • 53
  • 1
    At least related: [*How does the `this` keyword work?*](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work). – T.J. Crowder Aug 29 '19 at 16:25
  • you only need to use it if your function code use `this` and it's not working otherwise. minor exception apply, like using it for currying, but let's keep it simple. – dandavis Aug 29 '19 at 16:31
  • 1
    Also at least related: [*How to access the correct `this` inside a callback?*](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – T.J. Crowder Aug 29 '19 at 16:31
  • 1
    @dandavis No, that's **exactly** what I **don't** want to do. Trial and error is not a good way to write code, especially as it scales. I want to understand what's going on. – Andrew Aug 29 '19 at 16:34
  • main point: you don't often need it. Once you get past the basics, trial and error is the only way to learn. In TDD, errors are actually purposeful: you want it to not work before it works, to verify your assumptions. The short answer to all your listed questions is "no", and they suggest you actually more need to understand how `this` is determined at runtime (ex: nothing to do with scope). The short list of common "yes" situations would be on promises using methods and event handlers to reach triggering elements (not e.target). – dandavis Aug 29 '19 at 16:45

3 Answers3

2

The Mozilla Developer Network has some great documentation on this specifying the different cases:

  1. Global context
  2. Function context
  3. using bind
  4. with arrow functions
  5. in object methods
  6. in an object constructor
  7. in DOM event handlers

Check out the links to get an idea of how this works in the different contexts and therefore when bind should be used to force bind a different this context for functions.

Usually, bind is used to transfer 'ownership' of a function. Specifically, in my experience, it was used back before classes were created to force an object method to be tied to the object in question. It is also useful for when using arrow functions because arrow functions have different contexts.

dodgez
  • 511
  • 1
  • 5
  • I would have included the link to arrow functions with [different contexts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_separate_this) but I am a new contributor and was limited to the number of links I had. – dodgez Aug 29 '19 at 16:41
1

You need to use bind (or a similar approach) when:

  • The function is a traditional (function keyword) function or method (in a class or object literal), and
  • That function will be called either in a way that doesn't set this explicitly or that sets it to an incorrect value

The reason is that with a traditional function or method, the value of this is set by the caller, not a part of the function itself. (Details here and here.)

For example, consider:

const obj = {
    method() {
        console.log(this === obj);
    }
};

Now, when we do obj.method(), we're using syntax (calling the result of a property accessor operation) to specify what this will be, so:

obj.method();
// => true

But suppose we do this:

const m = obj.method;

Now, just calling m() will set this to the default this (undefined in strict mode, the global object in loose mode):

m();
// => false

Another way we can explicitly set this for the call is via call (and its cousin apply):

m.call(obj);
// => true

Some functions that call callbacks let you specify what this to use. forEach does, as an argument just after the callback:

[1].forEach(m, obj);
//          ^  ^^^---- the value to use as `this` in callback
//           \-------- the callback to call
// => true

Here's a live example of those:

const obj = {
    method() {
        console.log(this === obj);
    }
};

obj.method();
// => true, `this` was set to `obj` because you did the call on the
// result of a property accessor
const m = obj.method;

m();
// => false, `this` was the default `this` used when `this` isn't
// specified explicitly via syntax or `call`

m.call(obj);
// => true, `this` was explicitly set via `call`

[1].forEach(m, obj);
// => true, `this` was explicitly set via `forEach`'s `thisArg` argument

So any time you have a function (such as the callback of a forEach, or an event handler), you need bind or a similar mechanism to ensure that the correct this is used.

This isn't true for some other kinds of functions, just traditional (function keyword) functions and methods (such as obj.method above). An arrow function closes over this instead of using the one supplied by the caller, and a bound function (result of using bind) has this bound to it and so ignores any this supplied by the caller.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
0

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:

  • The function's containing scope
  • The immediate predecessor in the call chain
  • Whether the function is called directly or indirectly

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:

  • By default, this is the global object, which in browser world is window.
  • Inside a function in the global scope, this will still be window, it does not change.
  • Inside a member function within a 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.
  • Inside any closure's inner function (any function within a function), inside a returned function, inside a function called with a plain variable reference as its immediate predecessor in the call chain (regardless of where it actually lives!), or inside a function called indirectly (i.e. passed to a function (e.g. .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).
    • There is... however... one exception...: if inside a 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.

Andrew
  • 3,330
  • 1
  • 33
  • 53